Progress on the infrastructure for header definitions and instances.

This commit is contained in:
Robert Smallshire
2015-02-27 16:19:40 +01:00
parent 19c89d9029
commit b6fda775c2
4 changed files with 458 additions and 362 deletions
+41
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:]