mirror of
https://github.com/wassname/segpy.git
synced 2026-07-23 13:10:51 +08:00
Progress on the infrastructure for header definitions and instances.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
from segpy.field import field_mangler, BinaryFieldDescriptor, ValueFieldDescriptor
|
||||
|
||||
|
||||
class BinaryFormat(object,
|
||||
metaclass=field_mangler(descriptor=BinaryFieldDescriptor)):
|
||||
"""Base class for headers format definitions comprised of BinaryFields."""
|
||||
|
||||
def __init__(self, **field_types_and_offsets):
|
||||
"""Override default types and offsets.
|
||||
|
||||
Args:
|
||||
**field_types_and_offsets: Keywords arguments, with names corresponding to the
|
||||
binary_fields may be provided where each name is associated with a 2-tuple
|
||||
containing a field_type and an offset in bytes.
|
||||
"""
|
||||
for name, (field_type, offset) in field_types_and_offsets:
|
||||
if not hasattr(self, name):
|
||||
raise TypeError("Unknown attribute {!r}".format(name))
|
||||
fld = getattr(self, name)
|
||||
fld.ftype = field_type
|
||||
fld.offset = offset
|
||||
# TODO: Consider validating for overlaps
|
||||
|
||||
|
||||
class Header(object,
|
||||
metaclass=field_mangler(descriptor=ValueFieldDescriptor)):
|
||||
"""Base class for headers comprised of ValueFields."""
|
||||
|
||||
def __init__(self, **field_values):
|
||||
"""Set field values.
|
||||
|
||||
Args:
|
||||
**field_values: Keywords arguments, with names corresponding to the
|
||||
trace_header_fields may be provided where each name is associated with a
|
||||
value of the correct type.
|
||||
"""
|
||||
for name, value in field_values:
|
||||
if not hasattr(self, name):
|
||||
raise TypeError("Unknown attribute {!r}".format(name))
|
||||
fld = getattr(self, name)
|
||||
fld.value = value
|
||||
+150
-155
@@ -1,180 +1,161 @@
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from segpy.docstring import docstring_property
|
||||
from segpy.util import underscores_to_camelcase
|
||||
from segpy.util import underscores_to_camelcase, first_sentence, lower_first, UNSET
|
||||
|
||||
|
||||
# class FieldItems(object):
|
||||
#
|
||||
# def __init__(self, field):
|
||||
# self._field = field
|
||||
# self.__doc__ = field.__doc__
|
||||
#
|
||||
# @property
|
||||
# def name(self):
|
||||
# if self._field._name is not None:
|
||||
# return self._field._name
|
||||
# raise AttributeError("Field is unnamed")
|
||||
#
|
||||
# @property
|
||||
# def ftype(self):
|
||||
# return self._field._field_type
|
||||
#
|
||||
# @property
|
||||
# def value(self):
|
||||
# return self._value if hasattr(self, '_value') else self._field._default
|
||||
#
|
||||
# @value.setter
|
||||
# def value(self, value):
|
||||
# self._value = self._field._field_type(value)
|
||||
#
|
||||
# @property
|
||||
# def default(self):
|
||||
# return self._field._default
|
||||
#
|
||||
# @property
|
||||
# def offset(self):
|
||||
# return self._start_offset if hasattr(self, '_start_offset') else self._field._offset
|
||||
#
|
||||
# @docstring_property
|
||||
# def __doc__(self):
|
||||
# return self._field._documentation
|
||||
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 Field(object):
|
||||
# """A field of a binary format, specifying type, file offset, value and default.
|
||||
#
|
||||
# Attributes:
|
||||
# name: The name of the field
|
||||
# ftype: The type of the field.
|
||||
# value: The value of the field.
|
||||
# offset: The byte offset of the start of the field.
|
||||
# default: The default value of the field.
|
||||
# __doc__: The documentation of the field.
|
||||
# """
|
||||
#
|
||||
# def __init__(self, field_type, offset, default, documentation):
|
||||
# self._name = None # This field can be set by the NamedDescriptorResolverMetaClass
|
||||
# self._field_type = field_type
|
||||
# self._offset = offset
|
||||
# self._default = default
|
||||
# self._data = WeakKeyDictionary()
|
||||
# self._documentation = documentation
|
||||
#
|
||||
# def __get__(self, instance, owner):
|
||||
# if instance is None:
|
||||
# return self
|
||||
# return self._data.setdefault(instance, FieldItems(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?")
|
||||
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 field(field_type, offset, default, documentation):
|
||||
#
|
||||
# class SpecificField(Field):
|
||||
# pass
|
||||
#
|
||||
# SpecificField.__doc__ = documentation
|
||||
#
|
||||
# """Declare a field descriptor.
|
||||
#
|
||||
# Args:
|
||||
# field_type: The type of the field.
|
||||
# offset: The offset in bytes of this field in a binary stream.
|
||||
# default: The default value of this field.
|
||||
# documentation: Description of the field.
|
||||
#
|
||||
# Returns:
|
||||
# A Field descriptor.
|
||||
# """
|
||||
#
|
||||
# return SpecificField(field_type, offset, default, documentation)
|
||||
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 MetaField(object):
|
||||
|
||||
def __init__(self, field_type, offset, default, documentation):
|
||||
self._field_type = field_type
|
||||
self._offset = offset
|
||||
self._default = default
|
||||
self._documentation = documentation
|
||||
|
||||
@property
|
||||
def field_type(self):
|
||||
return self._field_type
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
return self._offset
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
return self._default
|
||||
|
||||
@property
|
||||
def documentation(self):
|
||||
return self._documentation
|
||||
|
||||
|
||||
def field(field_type, offset, default, documentation):
|
||||
return MetaField(field_type, offset, default, documentation)
|
||||
|
||||
|
||||
# class NamedDescriptorResolver(type):
|
||||
# """A metaclass for assigning descriptor names.
|
||||
# """
|
||||
# def __new__(cls, class_name, bases, class_dict):
|
||||
# for name, attr in class_dict.items():
|
||||
# attr_class = attr.__class__
|
||||
# if issubclass(attr_class, Field) and attr_class is not Field:
|
||||
# attr_class.__name__ = underscores_to_camelcase(name)
|
||||
# return type.__new__(cls, class_name, bases, class_dict)
|
||||
|
||||
|
||||
class FormatFieldItems(object):
|
||||
class BinaryField(object):
|
||||
"""Aggregates the name, type and offset of a field within a binary format.
|
||||
"""
|
||||
|
||||
def __init__(self, field):
|
||||
self._field = field
|
||||
self.__doc__ = field.__doc__
|
||||
|
||||
@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
|
||||
|
||||
@docstring_property
|
||||
def __doc__(self):
|
||||
return self._field._documentation
|
||||
|
||||
|
||||
class FormatField(object):
|
||||
class BinaryFieldDescriptor(NamedField):
|
||||
"""A field of a binary format, specifying type, file offset, value and default.
|
||||
|
||||
Attributes:
|
||||
name: The name of the field
|
||||
ftype: The type of the field.
|
||||
offset: The byte offset of the start of the field.
|
||||
__doc__: The documentation of the field.
|
||||
"""
|
||||
|
||||
def __init__(self, field_type, offset, documentation):
|
||||
self._name = None # This field can be set by the NamedDescriptorResolver
|
||||
self._field_type = field_type
|
||||
def __init__(self, ftype, offset, docstrings):
|
||||
super(BinaryFieldDescriptor, self).__init__(docstrings)
|
||||
self._ftype = ftype
|
||||
self._offset = offset
|
||||
self._data = WeakKeyDictionary()
|
||||
self._documentation = documentation
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return self._data.setdefault(instance, FormatFieldItems(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?")
|
||||
@@ -182,21 +163,35 @@ class FormatField(object):
|
||||
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 binary_format(header_definition):
|
||||
def field(descriptor, docstrings, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
descriptor: The type (class) of the descriptor be be created.
|
||||
|
||||
class BinaryFormat(type):
|
||||
"""A metaclass for specifying the field type."""
|
||||
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))
|
||||
|
||||
def __new__(cls, class_name, bases, class_dict):
|
||||
for name in class_dict:
|
||||
print("Found field : {}".format(name))
|
||||
attr = class_dict[name]
|
||||
if isinstance(attr, MetaField):
|
||||
print("Found meta field : {}".format(attr))
|
||||
meta_field = class_dict[name]
|
||||
class_dict[name] = FormatField(field_type=meta_field.ftype,
|
||||
offset=meta_field.offset,
|
||||
documentation=meta_field.doc)
|
||||
return type.__new__(cls, class_name, bases, class_dict)
|
||||
**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)
|
||||
+256
-206
@@ -1,226 +1,276 @@
|
||||
from collections import OrderedDict
|
||||
from segpy.field import field, BinaryFormat, binary_format
|
||||
from segpy.types import Int16, Int32
|
||||
from segpy.util import pairwise
|
||||
from segpy.binary_headers import BinaryFormat, Header
|
||||
from segpy.field import field, BinaryFieldDescriptor, ValueFieldDescriptor
|
||||
from segpy.types import Int32
|
||||
|
||||
|
||||
class TraceHeader(object,
|
||||
#metaclass=NamedDescriptorResolver
|
||||
):
|
||||
# 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.",
|
||||
|
||||
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': "Trace sequence number within SEG Y file. Each file starts with trace sequence one.",
|
||||
|
||||
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': "Original field record number. Highly recommended for all types of data.",
|
||||
|
||||
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."
|
||||
)
|
||||
'trace_num': "Trace number within the original field record. Highly recommended for all types of data."
|
||||
}
|
||||
|
||||
|
||||
class TraceHeaderFormat(metaclass=binary_format(traceheader)):
|
||||
|
||||
pass
|
||||
def trace_header_format_field(ftype, offset):
|
||||
return field(descriptor=BinaryFieldDescriptor, docstrings=TRACE_HEADER_DOCS, ftype=ftype, offset=offset)
|
||||
|
||||
|
||||
class TraceHeaderFormat(BinaryFormat):
|
||||
|
||||
def size_of(t):
|
||||
return t.SIZE
|
||||
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 compile_struct(record, num_bytes):
|
||||
"""Compile a struct description from a record.
|
||||
def trace_header_field(default):
|
||||
return field(descriptor=ValueFieldDescriptor, docstrings=TRACE_HEADER_DOCS, default=default)
|
||||
|
||||
Args:
|
||||
record: A record containing Field descriptors from which ftype and offset attributes can be obtained.
|
||||
|
||||
num_bytes: The total number of bytes to be retrieved.
|
||||
"""
|
||||
descriptors = (descriptor for descriptor in record.__class__.__dict__.values() if isinstance(descriptor, Field))
|
||||
class TraceHeader(Header):
|
||||
|
||||
# Sort those descriptors where offset is not None
|
||||
sorted_descriptors = sorted(descriptor for descriptor in descriptors if descriptor.offset is not None)
|
||||
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)
|
||||
|
||||
# Check for overlaps
|
||||
for a, b in pairwise(sorted_descriptors):
|
||||
if a.offset + size_of(a.ftype) > b.offset:
|
||||
raise ValueError("Record fields {!r} at offset {} and {!r} at offset {} are distinct but overlap."
|
||||
.format(a.name, a.offset, b.name, b.offset))
|
||||
|
||||
# Invert the mapping to give offset -> [fields]
|
||||
# Check that the types are the same
|
||||
offset_to_field = OrderedDict()
|
||||
for descriptor in descriptors:
|
||||
if descriptor.offset not in offset_to_field:
|
||||
offset_to_field[descriptor.offset] = []
|
||||
if len(offset_to_field) > 0:
|
||||
if offset_to_field[0].ftype is not descriptor.ftype:
|
||||
raise ValueError("Coincident fields {!r} and {!r} at offset {} have different types {!r} and {!r}"
|
||||
.format(offset_to_field[0], descriptor, descriptor.offset, offset_to_field[0].ftype,
|
||||
descriptor.ftype ))
|
||||
offset_to_field[descriptor.offset].append(descriptor)
|
||||
# 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.")
|
||||
|
||||
range_to_field = OrderedDict()
|
||||
for fields in offset_to_field.values():
|
||||
field = fields[0]
|
||||
range_to_field[range(field.offset, field.offset+size_of(field.ftype))] = fields
|
||||
#
|
||||
#
|
||||
#
|
||||
# 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):
|
||||
# return t.SIZE
|
||||
#
|
||||
#
|
||||
# def compile_struct(record, num_bytes):
|
||||
# """Compile a struct description from a record.
|
||||
#
|
||||
# Args:
|
||||
# record: A record containing Field descriptors from which ftype and offset attributes can be obtained.
|
||||
#
|
||||
# num_bytes: The total number of bytes to be retrieved.
|
||||
# """
|
||||
# descriptors = (descriptor for descriptor in record.__class__.__dict__.values() if isinstance(descriptor, Field))
|
||||
#
|
||||
# # Sort those descriptors where offset is not None
|
||||
# sorted_descriptors = sorted(descriptor for descriptor in descriptors if descriptor.offset is not None)
|
||||
#
|
||||
# # Check for overlaps
|
||||
# for a, b in pairwise(sorted_descriptors):
|
||||
# if a.offset + size_of(a.ftype) > b.offset:
|
||||
# raise ValueError("Record fields {!r} at offset {} and {!r} at offset {} are distinct but overlap."
|
||||
# .format(a.name, a.offset, b.name, b.offset))
|
||||
#
|
||||
# # Invert the mapping to give offset -> [fields]
|
||||
# # Check that the types are the same
|
||||
# offset_to_field = OrderedDict()
|
||||
# for descriptor in descriptors:
|
||||
# if descriptor.offset not in offset_to_field:
|
||||
# offset_to_field[descriptor.offset] = []
|
||||
# if len(offset_to_field) > 0:
|
||||
# if offset_to_field[0].ftype is not descriptor.ftype:
|
||||
# raise ValueError("Coincident fields {!r} and {!r} at offset {} have different types {!r} and {!r}"
|
||||
# .format(offset_to_field[0], descriptor, descriptor.offset, offset_to_field[0].ftype,
|
||||
# descriptor.ftype ))
|
||||
# offset_to_field[descriptor.offset].append(descriptor)
|
||||
#
|
||||
# range_to_field = OrderedDict()
|
||||
# for fields in offset_to_field.values():
|
||||
# field = fields[0]
|
||||
# range_to_field[range(field.offset, field.offset+size_of(field.ftype))] = fields
|
||||
|
||||
|
||||
|
||||
+11
-1
@@ -230,4 +230,14 @@ def round_up(integer, multiple):
|
||||
|
||||
def underscores_to_camelcase(s):
|
||||
"""Convert text_in_this_style to TextInThisStyle."""
|
||||
return ''.join(w.capitalize() for w in s.split('_'))
|
||||
return ''.join(w.capitalize() for w in s.split('_'))
|
||||
|
||||
|
||||
def first_sentence(s):
|
||||
sentence, stop, _ = s.partition('.')
|
||||
return sentence + stop
|
||||
|
||||
|
||||
def lower_first(s):
|
||||
"""Lower case the first character of a string."""
|
||||
return s[:1].lower() + s[1:]
|
||||
Reference in New Issue
Block a user