diff --git a/segpy/packer.py b/segpy/packer.py index 78650bf..9de5253 100644 --- a/segpy/packer.py +++ b/segpy/packer.py @@ -112,19 +112,28 @@ def compile_struct(header_format_class, start_offset=0, length_in_bytes=None, en return cformat, field_name_allocations +def make_header_packer(header_format_class, endian='>'): + cformat, field_name_allocations = compile_struct( + header_format_class, + header_format_class.START_OFFSET_IN_BYTES, + header_format_class.LENGTH_IN_BYTES, + endian) + structure = Struct(cformat) + + one_to_one = all(len(fields) == 1 for fields in field_name_allocations) + + if one_to_one: + return BijectiveHeaderPacker(header_format_class, structure, field_name_allocations) + return SurjectiveHeaderPacker(header_format_class, structure, field_name_allocations) + + class HeaderPacker: """Packing and unpacking header instances.""" - def __init__(self, header_format_class, endian='>'): + def __init__(self, header_format_class, structure, field_name_allocations): self._header_format_class = header_format_class - self._format, self._field_name_allocations = compile_struct( - header_format_class, - header_format_class.START_OFFSET_IN_BYTES, - header_format_class.LENGTH_IN_BYTES, - endian) - self._struct = Struct(self._format) - self._one_to_one = all(len(fields) == 1 for fields in self._field_name_allocations) - pass + self._structure = structure + self._field_name_allocations = field_name_allocations @property def header_format_class(self): @@ -140,7 +149,16 @@ class HeaderPacker: header.__class__.__name__ )) values = [getattr(header, names[0]) for names in self._field_name_allocations] - return self._struct.pack(*values) + return self._structure.pack(*values) + + def __repr__(self): + return "{}({})".format( + self.__class__.__name__, + self._header_format_class.__name__) + + +class BijectiveHeaderPacker(HeaderPacker): + """One-to-one packing/unpacking of serialised values to header fields.""" def unpack(self, buffer): """Unpack a header into a header object. @@ -151,10 +169,23 @@ class HeaderPacker: Returns: The header object. """ - values = self._struct.unpack(buffer) + values = self._structure.unpack(buffer) + return self._header_format_class(*values) - if self._one_to_one: - return self._header_format_class(*values) + +class SurjectiveHeaderPacker(HeaderPacker): + """One-to-many unpacking of serialised values to header fields.""" + + def unpack(self, buffer): + """Unpack a header into a header object. + + Overwrites any existing header field values with new values + obtained from the buffer. + + Returns: + The header object. + """ + values = self._structure.unpack(buffer) kwargs = {name: value for names, value in zip(self._field_name_allocations, values) @@ -162,11 +193,6 @@ class HeaderPacker: return self._header_format_class(**kwargs) - def __repr__(self): - return "{}({})".format( - self.__class__.__name__, - self._header_format_class.__name__) - def main(): from segpy.trace_header import TraceHeaderRev0 diff --git a/segpy/reader.py b/segpy/reader.py index 14efcb1..3b0b58a 100644 --- a/segpy/reader.py +++ b/segpy/reader.py @@ -1,6 +1,6 @@ from __future__ import print_function from segpy.encoding import ASCII -from segpy.packer import HeaderPacker +from segpy.packer import make_header_packer from segpy.trace_header import TraceHeaderRev1 from segpy.util import file_length, filename_from_handle @@ -175,7 +175,7 @@ class SegYReader(object): self._binary_reel_header = binary_reel_header self._extended_textual_headers = extended_textual_headers - self._trace_header_packer = HeaderPacker(trace_header_format, endian) + self._trace_header_packer = make_header_packer(trace_header_format, endian) self._trace_offset_catalog = trace_offset_catalog self._trace_length_catalog = trace_length_catalog diff --git a/segpy/toolkit.py b/segpy/toolkit.py index 3d7e4b8..c10b75e 100644 --- a/segpy/toolkit.py +++ b/segpy/toolkit.py @@ -15,7 +15,7 @@ from segpy.catalog import CatalogBuilder from segpy.datatypes import SEG_Y_TYPE_TO_CTYPE, size_in_bytes, DATA_SAMPLE_FORMAT_TO_SEG_Y_TYPE, CTYPE_TO_SIZE from segpy.encoding import guess_encoding, is_supported_encoding, UnsupportedEncodingError from segpy.ibm_float import IBMFloat -from segpy.packer import HeaderPacker +from segpy.packer import make_header_packer from segpy.revisions import canonicalize_revision from segpy.trace_header import TraceHeaderRev1 from segpy.util import file_length, batched, pad, complementary_intervals, NATIVE_ENDIANNESS, EMPTY_BYTE_STRING @@ -168,7 +168,7 @@ def read_binary_reel_header(fh, endian='>'): endian: '>' for big-endian data (the standard and default), '<' for little-endian (non-standard) """ - header_packer = HeaderPacker(BinaryReelHeader, endian) + header_packer = make_header_packer(BinaryReelHeader, endian) fh.seek(TEXTUAL_HEADER_NUM_BYTES) # Consider using from_one_based(BinaryReelHeader.START_OFFSET_IN_BYTES) buffer = fh.read(BinaryReelHeader.LENGTH_IN_BYTES) reel_header = header_packer.unpack(buffer) @@ -336,7 +336,7 @@ def catalog_traces(fh, bps, trace_header_format=TraceHeaderRev1, endian='>', pro if not callable(progress_callback): raise TypeError("catalog_traces(): progress callback must be callable") - trace_header_packer = HeaderPacker(trace_header_format, endian) + trace_header_packer = make_header_packer(trace_header_format, endian) length = file_length(fh) @@ -637,7 +637,7 @@ def write_binary_reel_header(fh, binary_reel_header, endian='>'): The file pointer for fh will be positioned at the first byte following the binary reel header. """ - header_packer = HeaderPacker(BinaryReelHeader, endian) # TODO: Hard wiring + header_packer = make_header_packer(BinaryReelHeader, endian) # TODO: Hard wiring buffer = header_packer.pack(binary_reel_header) fh.write(buffer) fh.seek(REEL_HEADER_NUM_BYTES)