mirror of
https://github.com/wassname/segpy.git
synced 2026-08-04 13:14:04 +08:00
Much improved design of header definition with automagical generation of a header DTO from the format.
This commit is contained in:
-197
@@ -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, '<unknown-field>'))
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
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, '<unknown-field>'))))
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
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, '<unknown-field>'))))
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
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)
|
||||
+336
-217
@@ -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 <brief>" 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):
|
||||
|
||||
+6
-5
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user