Example #1
0
def format_standard_textual_header(revision, **kwargs):
    """Produce a standard SEG Y textual header.

    Args:
        revision: The SEG Y revision.

        **kwargs: Named arguments corresponding to the values in the
            textual_reel_header.TEMPLATE_FIELD_NAMES dictionary,
            which in turn correspond to the placeholders in the
            textual_reel_header.TEMPLATE string.  Any omitted
            arguments will result in placeholders being replaced by spaces.
            If the end_marker argument is not supplied, an appropriate end
            marker will be selected based on the SEG Y revision. For standard
            end markers consider using textual_reel_header.END_TEXTUAL_HEADER
            or textual_reel_header.END_EBCDIC.  Any values which are longer
            than their placeholder will be truncated to the placeholder length.

    Returns:
        A list of forty Unicode strings.

    Usage:
        header = format_standard_textual_header(1,
                                            client="Lundin",
                                            company="Western Geco",
                                            crew_number=123,
                                            processing1="Sixty North AS",
                                            sweep_start_hz=10,
                                            sweep_end_hz=1000,
                                            sweep_length_ms=10000,
                                            sweep_channel_number=3,
                                            sweep_type='spread')

    """
    # Consider making the template module an argument with a default.
    kwargs.setdefault('end_marker', textual_reel_header.END_MARKERS[revision])

    template = textual_reel_header.TEMPLATE

    placeholder_slices = parse_template(template)
    background_slices = complementary_intervals(list(placeholder_slices.values()), 0, len(template))

    chunks = []
    for bg_slice, placeholder in zip_longest(background_slices,
                                             placeholder_slices.items()):
        if bg_slice is not None:
            chunks.append(template[bg_slice])

        if placeholder is not None:
            ph_name, ph_slice = placeholder
            ph_arg_name = textual_reel_header.TEMPLATE_FIELD_NAMES[ph_name]
            ph_value = kwargs.pop(ph_arg_name, '')
            ph_len = ph_slice.stop - ph_slice.start
            substitute = str(ph_value)[:ph_len].ljust(ph_len, ' ')
            chunks.append(substitute)

    if len(kwargs) > 0:
        raise TypeError("The following keyword arguments did not correspond to template placeholders: {!r}"
                        .format(list(kwargs.keys())))

    concatenation = ''.join(chunks)
    lines = list(''.join(s) for s in batched(concatenation, CARD_LENGTH))

    return lines
Example #2
0
def compile_struct(header_format_class,
                   start_offset=0,
                   length_in_bytes=None,
                   endian='>'):
    """Compile a struct description from a record.

    Args:
        header_format_class: A header_format class.

        start_offset: Optional start offset for the header in bytes.  Indicates the position of the start of
            the header in the same reference frame as which the field offsets are given.

        length_in_bytes: Optional length in bytes for the header. If the supplied header described a format shorter
            than this value the returned format will be padded with placeholders for bytes to be discarded. If the
            value is less than the minimum required for the format described by header_format_class an error will be
            raised.

        endian: '>' for big-endian data (the standard and default), '<'
            for little-endian (non-standard).

    Returns:
        A two-tuple containing in the zeroth element a format string which can be used with the struct.unpack function,
        and in the second element containing a list-of-lists for field names.  Each item in the outer list corresponds
        to an element of the tuple of data values returned by struct.unpack(); each name associated with that index is a
        field to which the unpacked value should be assigned.

    Usage:

        format, allocations = compile_struct(TraceHeaderFormat)
        values = struct.unpack(format)
        field_names_to_values = {}
        for field_names, value in zip(allocations, values):
            for field_name in field_names:
                field_names_to_values[field_name] = value
        header = Header(**field_names_to_values)

    Raises:
        ValueError: If header_format_class defines no fields.
        ValueError: If header_format_class contains fields which overlap but are not exactly coincident.
        ValueError: If header_format_class contains coincident fields of different types.
        ValueError: If header_format_class described a format longer than length_in_bytes.

    """
    if start_offset < 0:
        raise ValueError(
            "start_offset {} is less than zero".format(start_offset))

    if isinstance(length_in_bytes, int) and length_in_bytes < 1:
        raise ValueError(
            "length_in_bytes {} is less than one".format(length_in_bytes))

    fields = [
        getattr(header_format_class, name)
        for name in header_format_class.ordered_field_names()
    ]

    sorted_fields = sorted(fields, key=lambda f: f.offset)

    if len(sorted_fields) < 1:
        raise TypeError("Header format class {!r} defines no fields".format(
            header_format_class.__name__))

    if len(sorted_fields) > 1:
        for a, b in pairwise(sorted_fields):
            if intervals_partially_overlap(
                    range(a.offset, a.offset + size_of(a.value_type)),
                    range(b.offset, b.offset + size_of(b.value_type))):
                raise ValueError(
                    "Fields {!r} at offset {} and {!r} at offset {} of {} are distinct but overlap."
                    .format(a.name, a.offset, b.name, b.offset,
                            header_format_class.__name__))

    last_field = sorted_fields[-1]
    defined_length = (last_field.offset - start_offset) + size_of(
        last_field.value_type)
    specified_length = defined_length if (
        length_in_bytes is None) else length_in_bytes
    padding_length = specified_length - defined_length
    if padding_length < 0:
        raise ValueError(
            "Header length {!r} bytes defined by {!r} is less than specified length in bytes {!r}"
            .format(defined_length, header_format_class.__name__,
                    specified_length))

    offset_to_fields = OrderedDict()
    for field in sorted_fields:
        relative_offset = field.offset - start_offset  # relative_offser is zero-based
        if relative_offset not in offset_to_fields:
            offset_to_fields[relative_offset] = []
        if len(offset_to_fields[relative_offset]) > 0:
            if offset_to_fields[relative_offset][
                    0].value_type is not field.value_type:
                raise TypeError(
                    "Coincident fields {!r} and {!r} at offset {} have different types {} and {}"
                    .format(
                        offset_to_fields[relative_offset][0], field,
                        offset_to_fields[relative_offset][0].offset,
                        offset_to_fields[relative_offset]
                        [0].value_type.__name__, field.value_type.__name__))
        offset_to_fields[relative_offset].append(field)

    # Create a list of ranges where each range spans the byte indexes covered by each field
    field_spans = [
        range(offset, offset + size_of(fields[0].value_type))
        for offset, fields in offset_to_fields.items()
    ]

    gap_intervals = complementary_intervals(
        field_spans, start=0, stop=specified_length)  # One-based indexes

    # Create a format string usable with the struct module
    format_chunks = [endian]
    representative_fields = (fields[0] for fields in offset_to_fields.values())
    for gap_interval, field in zip_longest(gap_intervals,
                                           representative_fields,
                                           fillvalue=None):
        gap_length = len(gap_interval)
        if gap_length > 0:
            format_chunks.append('x' * gap_length)
        if field is not None:
            format_chunks.append(
                SEG_Y_TYPE_TO_CTYPE[field.value_type.SEG_Y_TYPE])
    cformat = ''.join(format_chunks)

    # Create a list of mapping item index to field names.
    # [0] -> ['field_1', 'field_2']
    # [1] -> ['field_3']
    # [2] -> ['field_4']
    field_name_allocations = [[field.name for field in fields]
                              for fields in offset_to_fields.values()]
    return cformat, field_name_allocations
Example #3
0
 def test_contiguous_with_offset_end(self, intervals, end_offset):
     last_interval_end = intervals[-1].stop
     end_index = last_interval_end + end_offset
     complements = list(complementary_intervals(intervals, stop=end_index))
     self.assertEqual(complements[-1], range(last_interval_end, end_index))
Example #4
0
def compile_struct(header_format_class, start_offset=0, length_in_bytes=None, endian='>'):
    """Compile a struct description from a record.

    Args:
        header_format_class: A header_format class.

        start_offset: Optional start offset for the header in bytes.  Indicates the position of the start of
            the header in the same reference frame as which the field offsets are given.

        length_in_bytes: Optional length in bytes for the header. If the supplied header described a format shorter
            than this value the returned format will be padded with placeholders for bytes to be discarded. If the
            value is less than the minimum required for the format described by header_format_class an error will be
            raised.

        endian: '>' for big-endian data (the standard and default), '<'
            for little-endian (non-standard).

    Returns:
        A two-tuple containing in the zeroth element a format string which can be used with the struct.unpack function,
        and in the second element containing a list-of-lists for field names.  Each item in the outer list corresponds
        to an element of the tuple of data values returned by struct.unpack(); each name associated with that index is a
        field to which the unpacked value should be assigned.

    Usage:

        format, allocations = compile_struct(TraceHeaderFormat)
        values = struct.unpack(format)
        field_names_to_values = {}
        for field_names, value in zip(allocations, values):
            for field_name in field_names:
                field_names_to_values[field_name] = value
        header = Header(**field_names_to_values)

    Raises:
        ValueError: If header_format_class defines no fields.
        ValueError: If header_format_class contains fields which overlap but are not exactly coincident.
        ValueError: If header_format_class contains coincident fields of different types.
        ValueError: If header_format_class described a format longer than length_in_bytes.

    """
    if start_offset < 0:
        raise ValueError("start_offset {} is less than zero".format(start_offset))

    if isinstance(length_in_bytes, int) and length_in_bytes < 1:
        raise ValueError("length_in_bytes {} is less than one".format(length_in_bytes))

    fields = [getattr(header_format_class, name) for name in header_format_class.ordered_field_names()]

    sorted_fields = sorted(fields, key=lambda f: f.offset)

    if len(sorted_fields) < 1:
        raise TypeError("Header format class {!r} defines no fields".format(header_format_class.__name__))

    if len(sorted_fields) > 1:
        for a, b in pairwise(sorted_fields):
            if intervals_partially_overlap(range(a.offset, a.offset + size_of(a.value_type)),
                                           range(b.offset, b.offset + size_of(b.value_type))):
                raise ValueError("Fields {!r} at offset {} and {!r} at offset {} of {} are distinct but overlap."
                                  .format(a.name, a.offset, b.name, b.offset, header_format_class.__name__))

    last_field = sorted_fields[-1]
    defined_length = (last_field.offset - start_offset) + size_of(last_field.value_type)
    specified_length = defined_length if (length_in_bytes is None) else length_in_bytes
    padding_length = specified_length - defined_length
    if padding_length < 0:
        raise ValueError("Header length {!r} bytes defined by {!r} is less than specified length in bytes {!r}"
                         .format(defined_length, header_format_class.__name__, specified_length))

    offset_to_fields = OrderedDict()
    for field in sorted_fields:
        relative_offset = field.offset - start_offset  # relative_offser is zero-based
        if relative_offset not in offset_to_fields:
            offset_to_fields[relative_offset] = []
        if len(offset_to_fields[relative_offset]) > 0:
            if offset_to_fields[relative_offset][0].value_type is not field.value_type:
                raise TypeError("Coincident fields {!r} and {!r} at offset {} have different types {} and {}"
                                  .format(offset_to_fields[relative_offset][0],
                                          field,
                                          offset_to_fields[relative_offset][0].offset,
                                          offset_to_fields[relative_offset][0].value_type.__name__,
                                          field.value_type.__name__))
        offset_to_fields[relative_offset].append(field)

    # Create a list of ranges where each range spans the byte indexes covered by each field
    field_spans = [range(offset, offset + size_of(fields[0].value_type))
                   for offset, fields in offset_to_fields.items()]

    gap_intervals = complementary_intervals(field_spans, start=0, stop=specified_length)  # One-based indexes

    # Create a format string usable with the struct module
    format_chunks = [endian]
    representative_fields = (fields[0] for fields in offset_to_fields.values())
    for gap_interval, field in zip_longest(gap_intervals, representative_fields, fillvalue=None):
        gap_length = len(gap_interval)
        if gap_length > 0:
            format_chunks.append('x' * gap_length)
        if field is not None:
            format_chunks.append(SEG_Y_TYPE_TO_CTYPE[field.value_type.SEG_Y_TYPE])
    cformat = ''.join(format_chunks)

    # Create a list of mapping item index to field names.
    # [0] -> ['field_1', 'field_2']
    # [1] -> ['field_3']
    # [2] -> ['field_4']
    field_name_allocations = [[field.name for field in fields]
                              for fields in offset_to_fields.values()]
    return cformat, field_name_allocations
Example #5
0
 def test_contiguous(self, intervals):
     complements = complementary_intervals(intervals)
     interleaved = list(roundrobin(complements, intervals))
     self.assertTrue(intervals_are_contiguous(interleaved))
Example #6
0
 def test_contiguous_with_offset_start(self, intervals, start_offset):
     first_interval_start = intervals[0].start
     start_index = first_interval_start - start_offset
     complements = list(complementary_intervals(intervals, start=start_index))
     self.assertEqual(complements[0], range(start_index, first_interval_start))
Example #7
0
 def test_contiguous_with_offset_end(self, intervals, end_offset):
     last_interval_end = intervals[-1].stop
     end_index = last_interval_end + end_offset
     complements = list(complementary_intervals(intervals, stop=end_index))
     self.assertEqual(complements[-1], range(last_interval_end, end_index))
Example #8
0
 def test_contiguous_with_offset_start(self, intervals, start_offset):
     first_interval_start = intervals[0].start
     start_index = first_interval_start - start_offset
     complements = list(complementary_intervals(intervals, start=start_index))
     self.assertEqual(complements[0], range(start_index, first_interval_start))
Example #9
0
 def test_contiguous(self, intervals):
     complements = complementary_intervals(intervals)
     interleaved = list(roundrobin(complements, intervals))
     self.assertTrue(intervals_are_contiguous(interleaved))
Example #10
0
 def test_empty_intervals_raises_value_error(self):
     with raises(ValueError):
         complementary_intervals([])
Example #11
0
 def test_empty_intervals_raises_value_error(self):
     with raises(ValueError):
         complementary_intervals([])