From 25d2387d39adbb94091db3a1566b177e17fc718d Mon Sep 17 00:00:00 2001 From: Robert Smallshire Date: Sat, 18 Apr 2015 07:29:13 +0100 Subject: [PATCH] Much improved design of header definition with automagical generation of a header DTO from the format. --- segpy/field.py | 197 --------------- segpy/trace_header.py | 553 +++++++++++++++++++++++++----------------- segpy/types.py | 11 +- 3 files changed, 342 insertions(+), 419 deletions(-) delete mode 100644 segpy/field.py diff --git a/segpy/field.py b/segpy/field.py deleted file mode 100644 index f0c2ab0..0000000 --- a/segpy/field.py +++ /dev/null @@ -1,197 +0,0 @@ -from weakref import WeakKeyDictionary - -from segpy.util import underscores_to_camelcase, first_sentence, lower_first, UNSET - - -def field_mangler(descriptor): - """A factory function for creating a metaclass which detects descriptors which subclass a certain archetype. - - Args: - descriptor: The type (class) of a descriptor used to identity descriptor instances which should be renamed. - """ - - class NamedDescriptorMangler(type): - """A metaclass for assigning descriptor names. - """ - def __new__(mcs, class_name, bases, class_dict): - for name, attr in class_dict.items(): - - # This shenanigans is necessary so we can have all the following work is a useful way - # help(class), help(instance), help(class.property) and help(instance.property) - - # Set the _name attribute of the field instance if it hasn't already been set - if isinstance(attr, descriptor): - if attr._name is None: - attr._name = name - - # We rename the *class* and set its docstring so help() works usefully - # when called with a class containing such fields. - attr_class = attr.__class__ - if issubclass(attr_class, descriptor) and attr_class is not descriptor: - attr_class.__name__ = underscores_to_camelcase(name) - attr_class.__doc__ = attr.assemble_docstring() - - return super(NamedDescriptorMangler, mcs).__new__(mcs, class_name, bases, class_dict) - - return NamedDescriptorMangler - - -class NamedField(object): - """Instances of NamedField can be detected by the NamedDescriptorResolver metaclass.""" - - def __init__(self, docstrings=None): - # These fields can be set by the NamedDescriptorResolver metaclass - self._name = None - self._docstrings = docstrings - - def assemble_docstring(self): - if self._name is None: - raise RuntimeError("Field name is not set.") - return first_sentence(self._docstrings.get(self._name, '')) - -# ---------------------------------------------------------------------------------------------------------------------- - -class BinaryField(object): - """Aggregates the name, type and offset of a field within a binary format. - """ - - def __init__(self, field): - self._field = field - - @property - def name(self): - """The field name (read-only).""" - if self._field._name is not None: - return self._field._name - raise AttributeError("Field is unnamed") - - @property - def ftype(self): - """The type of the field described using the classes in segpy.types""" - return self._field._field_type - - @property - def offset(self): - """The one-based (IMPORTANT!) offset in bytes of the field from the beginning of the structure""" - return self._field._offset - - -class BinaryFieldDescriptor(NamedField): - """A field of a binary format, specifying type, file offset, value and default. - - Attributes: - ftype: The type of the field. - offset: The byte offset of the start of the field. - """ - - def __init__(self, ftype, offset, docstrings): - super(BinaryFieldDescriptor, self).__init__(docstrings) - self._ftype = ftype - self._offset = offset - self._data = WeakKeyDictionary() - - def __get__(self, instance, owner): - if instance is None: - return self - return self._data.setdefault(instance, BinaryField(self)) - - def __set__(self, instance, value): - raise AttributeError("Can't set binary field attribute. Did you intend to set field.ftype or field.offset instead?") - - def __delete__(self, instance): - raise AttributeError("Can't delete field attribute. Did you intend to set field.ftype or field.offset instead?") - - def assemble_docstring(self): - if self._name is None: - raise RuntimeError("Field name is not set.") - return "A BinaryField containing the type and byte offset for the {}".format( - first_sentence(lower_first(self._docstrings.get(self._name, '')))) - - -#----------------------------------------------------------------------------------------------------------------------- - - -class ValueField(object): - """Aggregates the name, type and offset of a field within a binary format. - """ - - def __init__(self, field): - self._field = field - self._value = UNSET - - @property - def name(self): - """The field name (read-only).""" - if self._field._name is not None: - return self._field._name - raise AttributeError("Field is unnamed") - - @property - def default(self): - return self._field._default - - @property - def value(self): - """The type of the field described using the classes in segpy.types""" - return self._value if self._value is not UNSET else self.default - - @value.setter - def value(self, v): - self._value = v - - -class ValueFieldDescriptor(NamedField): - """A field of a binary format, specifying type, file offset, value and default. - - Attributes: - value: The value of the field. - """ - - def __init__(self, default, docstrings): - super(ValueFieldDescriptor, self).__init__(docstrings) - self._default = default - self._data = WeakKeyDictionary() - - def __get__(self, instance, owner): - if instance is None: - return self - return self._data.setdefault(instance, ValueField(self)) - - def __set__(self, instance, value): - raise AttributeError("Can't set field attribute. Did you intend to set field.value instead?") - - def __delete__(self, instance): - raise AttributeError("Can't delete field attribute. Did you intend to set field.value instead?") - - def assemble_docstring(self): - if self._name is None: - raise RuntimeError("Field name is not set.") - return "A ValueField containing the value and default for the {}".format( - first_sentence(lower_first(self._docstrings.get(self._name, '')))) - -# ---------------------------------------------------------------------------------------------------------------------- - - -def field(descriptor, docstrings, **kwargs): - """ - Args: - descriptor: The type (class) of the descriptor be be created. - - docstrings: A dictionary of strings where the docstring for the descriptor can be looked up using the - descriptor name. For this to work correctly, the class hosting the descriptors should use a metaclass - created with field_manger(descriptor). e.g. class Foo(metaclass=field_mangler(descriptor)) - - **kwargs: Any other arguments will be forwarded to the descriptor constructor. - - Returns: - An instance of the descriptor class. - """ - - # Create a class specifically for this field. This class will later get - # renamed when the NamedDescriptorMangler metaclass does its job, to - # a class name based on the field name. - - class SpecificField(descriptor): - pass - - return SpecificField(docstrings=docstrings, **kwargs) \ No newline at end of file diff --git a/segpy/trace_header.py b/segpy/trace_header.py index db0ecc1..cca570a 100644 --- a/segpy/trace_header.py +++ b/segpy/trace_header.py @@ -1,235 +1,354 @@ -from segpy.binary_headers import BinaryFormat, Header -from segpy.field import field, BinaryFieldDescriptor, ValueFieldDescriptor -from segpy.types import Int32 + +from collections import OrderedDict +import textwrap +from weakref import WeakKeyDictionary +from segpy.docstring import docstring_property +from segpy.types import Int32, Int16 + +from segpy.util import underscores_to_camelcase, first_sentence -# Field descriptions use by both the TraceHeaderFormat and TraceHeader classes, which extract their documentation -# from this dictionary. The first sentence of each docstring (up to the first stop) must be usable as a brief -# description and make sense in the sentence "This field is the " when the first character is lower-cased. -TRACE_HEADER_DOCS = { - 'line_sequence_num': "Trace sequence number within line. Numbers continue to increase if the same line continues " - "across multiple SEG Y files. Highly recommended for all types of data.", - - 'file_sequence_num': "Trace sequence number within SEG Y file. Each file starts with trace sequence one.", - - 'field_record_num': "Original field record number. Highly recommended for all types of data.", - - 'trace_num': "Trace number within the original field record. Highly recommended for all types of data." -} +def is_magic_name(name): + return len(name) > 4 and name.startswith('__') and name.endswith('__') -def trace_header_format_field(ftype, offset): - return field(descriptor=BinaryFieldDescriptor, docstrings=TRACE_HEADER_DOCS, ftype=ftype, offset=offset) +class FormatMeta(type): + """A metaclass for header format classes. + """ + + @classmethod + def __prepare__(mcs, name, bases): + return OrderedDict() + + def __new__(mcs, name, bases, namespace): + + # TODO: This is a good point to validate that the fields are in order and that the + # TODO: format specification is valid. We shouldn't even build the class otherwise. + + namespace['ORDERED_FIELD_NAMES'] = tuple(name for name in namespace.keys() + if not is_magic_name(name)) + + for name, attr in namespace.items(): + + # This shenanigans is necessary so we can have all the following work is a useful way + # help(class), help(instance), help(class.property) and help(instance.property) + + # Set the _name attribute of the field instance if it hasn't already been set + if isinstance(attr, NamedField): + if attr._name is None: + attr._name = name + + # We rename the *class* and set its docstring so help() works usefully + # when called with a class containing such fields. + attr_class = attr.__class__ + if issubclass(attr_class, NamedField) and attr_class is not NamedField: + attr_class.__name__ = underscores_to_camelcase(name) + attr_class.__doc__ = attr.documentation + + return super().__new__(mcs, name, bases, namespace) -class TraceHeaderFormat(BinaryFormat): +class NamedField: + """Instances of NamedField can be detected by the NamedDescriptorResolver metaclass.""" - line_sequence_num = trace_header_format_field(Int32, offset=1) - file_sequence_num = trace_header_format_field(Int32, offset=5) - field_record_num = trace_header_format_field(Int32, offset=9) - trace_num = trace_header_format_field(Int32, offset=13) + def __init__(self, value_type, offset, default, documentation): + self._name = None # Set later by the metaclass + self._value_type = value_type + self._offset = offset + self._default = self._value_type(default) + self._documentation = documentation + + @property + def name(self): + "The field name." + return self._name + + @property + def value_type(self): + "The field value type (e.g. Int32)" + return self._value_type + + @property + def offset(self): + "The offset it bytes from the beginning of the header." + return self._offset + + @property + def default(self): + "The default value of the field. Must be convertible to value_type." + return self._default + + @property + def documentation(self): + "A descriptive text string." + return self._documentation + + @docstring_property(__doc__) + def __doc__(self): + return first_sentence(self._documentation) + + def __repr__(self): + return first_sentence(self._documentation) -def trace_header_field(default): - return field(descriptor=ValueFieldDescriptor, docstrings=TRACE_HEADER_DOCS, default=default) +def field(value_type, offset, default, documentation): + """ + Args: + value_type: The type of the field (e.g. Int32) + + offset: The offset in bytes for this field from the start of the header. + + default: The default value for this field. + + documentation: A docstring for the field. The first sentence should be usable + as a brief description. + + Returns: + An instance of a subclass of NamedField class. + """ + + # Create a class specifically for this field. This class will later get + # renamed when the NamedDescriptorMangler metaclass does its job, to + # a class name based on the field name. + + class SpecificField(NamedField): + pass + + return SpecificField(value_type, offset, default, documentation) -class TraceHeader(Header): +class TraceHeaderFormat(metaclass=FormatMeta): - line_sequence_num = trace_header_field(default=0) - file_sequence_num = trace_header_field(default=0) - field_record_num = trace_header_field(default=0) - trace_num = trace_header_field(default=0) + line_sequence_num = field( + Int32, offset=1, default=0, documentation= + "Trace sequence number within line — Numbers continue to increase if the same line " + "continues across multiple SEG Y files. Highly recommended for all types of data.") + + file_sequence_num = field( + Int32, offset=5, default=0, documentation= + "Trace sequence number within SEG Y file — Each file starts with trace sequence one.") + + field_record_num = field( + Int32, offset=9, default=0, documentation= + "Original field record number. Highly recommended for all types of data.") + + trace_num = field( + Int32, offset=13, default=0, documentation= + "Trace number within the original field record. Highly recommended for all types of data.") + + energy_source_point_num = field( + Int32, offset=17, default=0, documentation= + "Energy source point number — Used when more than one record occurs at the same " + "effective surface location. It is recommended that the new entry defined in Trace " + "Header bytes 197-202 be used for shotpoint number.") + + ensemble_num = field( + Int32, offset=21, default=0, documentation= + "Ensemble number (i.e. CDP , CMP , CRP , etc)") + + ensemble_trace_num = field( + Int32, offset=25, default=0, documentation= + "Trace number within the ensemble — Each ensemble starts with trace number one.") + + trace_identification_code = field( + Int16, offset=29, default=0, documentation= + "Trace identification code") + + num_vertically_summed_traces = field( + Int16, offset=31, default=1, documentation= + "Number of vertically summed traces yielding this trace. (1 is one trace, 2 is two summed traces, etc.)") + + num_horizontally_stacked_traces = field( + Int16, offset=33, default=1, documentation= + "Number of horizontally stacked traces yielding this trace. (1 is one trace, 2 is two stacked traces, etc.)") + + data_use = field( + Int16, offset=35, default=1, documentation= + "Data use: 1 = Production, 2 = Test") + + source_receiver_offset = field( + Int32, offset=37, default=0, documentation= + "Distance from center of the source point to the center of the receiver group (negative if opposite to " + "direction in which line is shot).") + + receiver_group_elevation = field( + Int32, offset=41, default=0, documentation= + "Receiver group elevation (all elevations above the Vertical datum are positive and below are negative). The " + "elevation_scalar applies to this value.") + + surface_elevation_at_source = field( + Int32, offset=45, default=0, documentation= + "Surface elevation at source. The elevation_scalar applies to this value.") + + source_depth_below_surface = field( + Int32, offset=49, default=0, documentation= + "Source depth below surface (a positive number). The elevation_scalar applies to this value.") + + datum_elevation_at_receiver_group = field( + Int32, offset=53, default=0, documentation= + "Source depth below surface (a positive number). The elevation_scalar applies to this value.") + + datum_elevation_at_source = field( + Int32, offset=57, default=0, documentation= + "Datum elevation at source. The elevation_scalar applies to this value.") + + water_depth_at_source = field( + Int32, offset=61, default=0, documentation= + "Water depth at source. The elevation_scalar applies to this value.") + + water_depth_at_group = field( + Int32, offset=65, default=0, documentation= + "Water depth at group. The elevation_scalar applies to this value." + ) + + elevation_scalar = field( + Int16, offset=69, default=1, documentation= + "Scalar to be applied to the elevations and depths specified in: receiver_group_elevation, " + "surface_elevation_at_source, source_depth_below_surface, datum_elevation_at_receiver_group, " + "datum_elevation_at_source, water_depth_at_source and water_depth_at_group, to give the real value. " + "Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; if negative, scalar is " + "used as a divisor." + ) + + xy_scalar = field( + Int16, offset=71, default=1, documentation= + "Scalar to be applied to all coordinates specified in source_x, source_y, group_x, group_y, cdp_x and cdp_y to " + "give the real value. Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; " + "if negative, scalar is used as divisor." + ) + + source_x = field( + Int32, offset=73, default=0, documentation= + "Source coordinate - X. The xy_scalar applies to this value. The coordinate reference system should be " + "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " + "decimal degrees or DMS, the X values represent longitude. A positive value designates east of Greenwich " + "Meridian and a negative value designates west." + ) + + source_y = field( + Int32, offset=77, default=0, documentation= + "Source coordinate - Y. The xy_scalar applies to this value. The coordinate reference system should be " + "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " + "decimal degrees or DMS, the Y values represent latitude. A positive value designates north of the equator and " + "a negative value designates south." + ) + + group_x = field( + Int32, offset=73, default=0, documentation= + "Group coordinate - X. The xy_scalar applies to this value. The coordinate reference system should be " + "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " + "decimal degrees or DMS, the X values represent longitude. A positive value designates east of Greenwich " + "Meridian and a negative value designates west." + ) + + group_y = field( + Int32, offset=77, default=0, documentation= + "Source coordinate - Y. The xy_scalar applies to this value. The coordinate reference system should be " + "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " + "decimal degrees or DMS, the Y values represent latitude. A positive value designates north of the equator and " + "a negative value designates south." + ) + + coordinate_units = field( + Int16, offset=89, default=0, documentation= + "Coordinate units: 1 = Length (meters or feet), 2 = Seconds of arc, 3 = Decimal degrees, 4 = Degrees, minutes, " + "seconds (DMS). Note: To encode ±DDDMMSS bytes this value equals ±DDD*104 + MM*102 + SS with xy_scalar set to " + "1; To encode ±DDDMMSS.ss this value equals ±DDD*106 + MM*104 + SS*102 with xy_scalar set to -100." + ) + + weathering_velocity = field( + Int16, offset=91, default=0, documentation= + "Weathering velocity. (ft/s or m/s as specified in Binary File Header bytes 3255- 3256)" # TODO + ) + + subweathering_velocity = field( + Int16, offset=93, default=0, documentation= + "Subweathering velocity. (ft/s or m/s as specified in Binary File Header bytes 3255-3256)" # TODO + ) + + uphole_time_at_source = field( + Int16, offset=95, default=0, documentation= + "Uphole time at source in milliseconds. The time_scalar applies to this value." + ) + + uphole_time_at_group = field( + Int16, offset=97, default=0, documentation= + "Uphole time at group in milliseconds. The time_scalar applies to this value." + ) + + source_static_correction = field( + Int16, offset=99, default=0, documentation= + "Source static correction in milliseconds. The time_scalar applies to this value." + ) + + group_static_correction = field( + Int16, offset=101, default=0, documentation= + "Group static correction in milliseconds. The time_scalar applies to this value." + ) -# class TraceHeader(object): -# -# def __init__(self, trace_header_format=None): -# """Initialize a TraceHeader. -# -# Args: -# trace_header_format: An optional TraceHeaderFormat instance against which -# field values will be validated. If not provided, fields will not be validated. -# """ -# -# line_sequence_num = field(default=0, documentation= -# "Trace sequence number within line — Numbers continue to increase if the same line " -# "continues across multiple SEG Y files. Highly recommended for all types of data.") +class ValueField: + + def __init__(self, name, value_type, default, documentation): + self._name = name + self._value_type = value_type + self._default = default + self._documentation = documentation + self._instance_data = WeakKeyDictionary() + + def __get__(self, instance, owner): + if owner is None: + return self + if instance not in self._instance_data: + return self._default + return self._instance_data[instance] + + def __set__(self, instance, value): + try: + self._instance_data[instance] = self._value_type(value) + except ValueError as e: + raise ValueError("Assigned value {!r} for {} attribute must be convertible to {}: {}" + .format(value, self._name, self._value_type.__name__, e)) from e + + def __delete__(self, instance): + raise AttributeError("Can't delete {} attribute".format(self._name)) + + @docstring_property(__doc__) + def __doc__(self): + return self._documentation + + # TODO: Get documentation of these descriptors working correctly + + +class BuildFromFormat(type): + """A metaclass for building a data transfer object from a format definition.""" + + def __new__(mcs, name, bases, namespace, format_class): + """Create a new DTO class from a format class.""" + if not format_class.__class__ is FormatMeta: + raise TypeError("Format class {} specified for class {} does not use the FormatMeta metaclass" + .format(format_class.__name__, name)) + + for name in format_class.ORDERED_FIELD_NAMES: + format_field = getattr(format_class, name) + namespace[name] = ValueField(name=name, + value_type=format_field.value_type, + default=format_field.default, + documentation=format_field.documentation) + + return super().__new__(mcs, name, bases, namespace) + + def __init__(mcs, name, bases, namespace, format_class): + super().__init__(mcs, name, bases) + pass + + +class TraceHeader(metaclass=BuildFromFormat, format_class=TraceHeaderFormat): + pass + +# This would build the TraceHeader class from something like the original TraceHeader at the bottom of this file. + -# -# -# -# class TraceHeader(object, -# #metaclass=NamedDescriptorResolver -# ): -# -# line_sequence_num = field( -# Int32, offset=1, default=0, documentation= -# "Trace sequence number within line — Numbers continue to increase if the same line " -# "continues across multiple SEG Y files. Highly recommended for all types of data.") -# -# file_sequence_num = field( -# Int32, offset=5, default=0, documentation= -# "Trace sequence number within SEG Y file — Each file starts with trace sequence one.") -# -# field_record_num = field( -# Int32, offset=9, default=0, documentation= -# "Original field record number. Highly recommended for all types of data.") -# -# trace_num = field( -# Int32, offset=13, default=0, documentation= -# "Trace number within the original field record. Highly recommended for all types of data.") -# -# energy_source_point_num = field( -# Int32, offset=17, default=0, documentation= -# "Energy source point number — Used when more than one record occurs at the same " -# "effective surface location. It is recommended that the new entry defined in Trace " -# "Header bytes 197-202 be used for shotpoint number.") -# -# ensemble_num = field( -# Int32, offset=21, default=0, documentation= -# "Ensemble number (i.e. CDP , CMP , CRP , etc)") -# -# ensemble_trace_num = field( -# Int32, offset=25, default=0, documentation= -# "Trace number within the ensemble — Each ensemble starts with trace number one.") -# -# trace_identification_code = field( -# Int16, offset=29, default=0, documentation= -# "Trace identification code") -# -# num_vertically_summed_traces = field( -# Int16, offset=31, default=1, documentation= -# "Number of vertically summed traces yielding this trace. (1 is one trace, 2 is two summed traces, etc.)") -# -# num_horizontally_stacked_traces = field( -# Int16, offset=33, default=1, documentation= -# "Number of horizontally stacked traces yielding this trace. (1 is one trace, 2 is two stacked traces, etc.)") -# -# data_use = field( -# Int16, offset=35, default=1, documentation= -# "Data use: 1 = Production, 2 = Test") -# -# source_receiver_offset = field( -# Int32, offset=37, default=0, documentation= -# "Distance from center of the source point to the center of the receiver group (negative if opposite to " -# "direction in which line is shot).") -# -# receiver_group_elevation = field( -# Int32, offset=41, default=0, documentation= -# "Receiver group elevation (all elevations above the Vertical datum are positive and below are negative). The " -# "elevation_scalar applies to this value.") -# -# surface_elevation_at_source = field( -# Int32, offset=45, default=0, documentation= -# "Surface elevation at source. The elevation_scalar applies to this value.") -# -# source_depth_below_surface = field( -# Int32, offset=49, default=0, documentation= -# "Source depth below surface (a positive number). The elevation_scalar applies to this value.") -# -# datum_elevation_at_receiver_group = field( -# Int32, offset=53, default=0, documentation= -# "Source depth below surface (a positive number). The elevation_scalar applies to this value.") -# -# datum_elevation_at_source = field( -# Int32, offset=57, default=0, documentation= -# "Datum elevation at source. The elevation_scalar applies to this value.") -# -# water_depth_at_source = field( -# Int32, offset=61, default=0, documentation= -# "Water depth at source. The elevation_scalar applies to this value.") -# -# water_depth_at_group = field( -# Int32, offset=65, default=0, documentation= -# "Water depth at group. The elevation_scalar applies to this value." -# ) -# -# elevation_scalar = field( -# Int16, offset=69, default=1, documentation= -# "Scalar to be applied to the elevations and depths specified in: receiver_group_elevation, " -# "surface_elevation_at_source, source_depth_below_surface, datum_elevation_at_receiver_group, " -# "datum_elevation_at_source, water_depth_at_source and water_depth_at_group, to give the real value. " -# "Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; if negative, scalar is " -# "used as a divisor." -# ) -# -# xy_scalar = field( -# Int16, offset=71, default=1, documentation= -# "Scalar to be applied to all coordinates specified in source_x, source_y, group_x, group_y, cdp_x and cdp_y to " -# "give the real value. Scalar = 1, +10, +100, +1000, or +10,000. If positive, scalar is used as a multiplier; " -# "if negative, scalar is used as divisor." -# ) -# -# source_x = field( -# Int32, offset=73, default=0, documentation= -# "Source coordinate - X. The xy_scalar applies to this value. The coordinate reference system should be " -# "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " -# "decimal degrees or DMS, the X values represent longitude. A positive value designates east of Greenwich " -# "Meridian and a negative value designates west." -# ) -# -# source_y = field( -# Int32, offset=77, default=0, documentation= -# "Source coordinate - Y. The xy_scalar applies to this value. The coordinate reference system should be " -# "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " -# "decimal degrees or DMS, the Y values represent latitude. A positive value designates north of the equator and " -# "a negative value designates south." -# ) -# -# group_x = field( -# Int32, offset=73, default=0, documentation= -# "Group coordinate - X. The xy_scalar applies to this value. The coordinate reference system should be " -# "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " -# "decimal degrees or DMS, the X values represent longitude. A positive value designates east of Greenwich " -# "Meridian and a negative value designates west." -# ) -# -# group_y = field( -# Int32, offset=77, default=0, documentation= -# "Source coordinate - Y. The xy_scalar applies to this value. The coordinate reference system should be " -# "identified through an extended header Location Data stanza. If the coordinate units are in seconds of arc, " -# "decimal degrees or DMS, the Y values represent latitude. A positive value designates north of the equator and " -# "a negative value designates south." -# ) -# -# coordinate_units = field( -# Int16, offset=89, default=0, documentation= -# "Coordinate units: 1 = Length (meters or feet), 2 = Seconds of arc, 3 = Decimal degrees, 4 = Degrees, minutes, " -# "seconds (DMS). Note: To encode ±DDDMMSS bytes this value equals ±DDD*104 + MM*102 + SS with xy_scalar set to " -# "1; To encode ±DDDMMSS.ss this value equals ±DDD*106 + MM*104 + SS*102 with xy_scalar set to -100." -# ) -# -# weathering_velocity = field( -# Int16, offset=91, default=0, documentation= -# "Weathering velocity. (ft/s or m/s as specified in Binary File Header bytes 3255- 3256)" # TODO -# ) -# -# subweathering_velocity = field( -# Int16, offset=93, default=0, documentation= -# "Subweathering velocity. (ft/s or m/s as specified in Binary File Header bytes 3255-3256)" # TODO -# ) -# -# uphole_time_at_source = field( -# Int16, offset=95, default=0, documentation= -# "Uphole time at source in milliseconds. The time_scalar applies to this value." -# ) -# -# uphole_time_at_group = field( -# Int16, offset=97, default=0, documentation= -# "Uphole time at group in milliseconds. The time_scalar applies to this value." -# ) -# -# source_static_correction = field( -# Int16, offset=99, default=0, documentation= -# "Source static correction in milliseconds. The time_scalar applies to this value." -# ) -# -# group_static_correction = field( -# Int16, offset=101, default=0, documentation= -# "Group static correction in milliseconds. The time_scalar applies to this value." -# ) -# # # # def size_of(t): diff --git a/segpy/types.py b/segpy/types.py index 41eb022..fa17460 100644 --- a/segpy/types.py +++ b/segpy/types.py @@ -5,12 +5,13 @@ class Int16(int): SIZE = 2 def __new__(cls, *args, **kwargs): - instance = super(cls).__new__(cls, *args, **kwargs) + instance = super().__new__(cls, *args, **kwargs) if not (Int16.MINIMUM <= instance <= Int16.MAXIMUM): - raise ValueError("{} value {!r} outside range {}–{}".format(cls.__name__, instance, + raise ValueError("{} value {!r} outside range {} to {}".format(cls.__name__, instance, cls.MINIMUM, cls.MAXIMUM)) return instance + class Int32(int): MINIMUM = -2147483648 @@ -18,9 +19,9 @@ class Int32(int): SIZE = 4 def __new__(cls, *args, **kwargs): - instance = super(Int32, cls).__new__(cls, *args, **kwargs) - if not (Int16.MINIMUM <= instance <= Int16.MAXIMUM): - raise ValueError("{} value {!r} outside range {}–{}".format(cls.__name__, instance, + instance = super().__new__(cls, *args, **kwargs) + if not (Int32.MINIMUM <= instance <= Int32.MAXIMUM): + raise ValueError("{} value {!r} outside range {} to {}".format(cls.__name__, instance, cls.MINIMUM, cls.MAXIMUM)) return instance