Some progress towards complete support for writing SEG Y

This commit is contained in:
Robert Smallshire
2014-12-03 20:38:08 +01:00
parent 2ee69544de
commit 91e325c95a
3 changed files with 311 additions and 2 deletions
+147
View File
@@ -0,0 +1,147 @@
from revisions import SEGY_REVISION_0, SEGY_REVISION_1
TEMPLATE = """
C 1 CLIENT { client } COMPANY { company } CREW NO {crew }
C 2 LINE { line } AREA { area } MAP ID { map_id }
C 3 REEL NO {reelnum} DAY-START OF REEL {d} YEAR {yr} OBSERVER {observer }
C 4 INSTRUMENT: MFG { mfg } MODEL { model } SERIAL NO { serial }
C 5 DATA TRACES/RECORD {dtpr} AUXILIARY TRACES/RECORD {atpr } CDP FOLD {cdpfold}
C 6 SAMPLE INTERVAL {intvl} SAMPLES/TRACE {spt} BITS/IN {bi} BYTES/SAMPLE {bps }
C 7 RECORDING FORMAT {rfmt} FORMAT THIS REEL {ftr } MEASUREMENT SYSTEM {measmnt}
C 8 SAMPLE CODE: FLOATING PT {f} FIXED PT {x} FIXED PT-GAIN {g} CORRELATED {cor}
C 9 GAIN TYPE: FIXED {i} BINARY {b} FLOATING POINT {c} OTHER {other }
C10 FILTERS: ALIAS {a }HZ NOTCH {n }HZ BAND {b1}-{b2 }HZ SLOPE {s}-{s2}DB/OCT
C11 SOURCE: TYPE {type } NUMBER/POINT {npt } POINT INTERVAL {point_interval }
C12 PATTERN: {source_pattern } LENGTH {lent} WIDTH {width }
C13 SWEEP: START {ss}HZ END {se}HZ LENGTH {lms}MS CHANNEL NO {q} TYPE {cd }
C14 TAPER: START LENGTH {tsl }MS END LENGTH {tel }MS TYPE {taper_type }
C15 SPREAD: OFFSET {off } MAX DISTANCE {md } GROUP INTERVAL {group_interval }
C16 GEOPHONES: PER GROUP {p} SPACING {y} FREQUENCY {r} MFG {gmfg } MODEL {gmod}
C17 PATTERN: {geophone_pattern } LENGTH {glen} WIDTH {geophone_width }
C18 TRACES SORTED BY: RECORD {u} CDP {v} OTHER { sort_other }
C19 AMPLITUDE RECOVERY: NONE {ar} SPHERICAL DIV {sd } AGC {} OTHER {ar_other }
C20 MAP PROJECTION {map_projection } ZONE ID {zid} COORDINATE UNITS {co_units }
C21 PROCESSING: { processing1 }
C22 PROCESSING: { processing2 }
C23 { unassigned1 }
C24 { unassigned2 }
C25 { unassigned3 }
C26 { unassigned4 }
C27 { unassigned5 }
C28 { unassigned6 }
C29 { unassigned7 }
C30 { unassigned8 }
C31 { unassigned9 }
C32 { unassigned10 }
C33 { unassigned11 }
C34 { unassigned12 }
C35 { unassigned13 }
C36 { unassigned14 }
C37 { unassigned15 }
C38 { unassigned16 }
C39 { unassigned17 }
C40 { end_marker }
"""
END_TEXTUAL_HEADER = 'END TEXTUAL HEADER'
END_EBCDIC = 'END EBCDIC'
END_MARKERS = {SEGY_REVISION_0: END_EBCDIC,
SEGY_REVISION_1: END_TEXTUAL_HEADER}
TEMPLATE_FIELD_NAMES = {'client': 'client',
'company': 'company',
'crew': 'crew_number',
'line': 'line',
'area': 'area',
'map_id': 'map_id',
'reelnum': 'reel_number',
'd': 'day_start_of_reel',
'yr': 'year',
'observer': 'observer',
'mfg': 'instrument_manufacturer',
'model': 'instrument_model',
'serial': 'instrument_serial',
'dtpr': 'data_traces_per_record',
'atpr': 'auxiliary_traces_per_record',
'cdpfold': 'cdp_fold',
'intvl': 'sample_interval',
'spt': 'samples_per_trace',
'bi': 'bits_per_inch',
'bps': 'bytes_per_sample',
'rfmt': 'recording_format',
'ftr': 'format_this_reel',
'measmnt': 'measurement_system',
'f': 'sample_code_floating_point',
'x': 'sample_code_fixed_point',
'g': 'sample_code_fixed_point_gain',
'cor': 'sample_code_correlated',
'i': 'gain_type_fixed',
'b': 'gain_type_binary',
'c': 'gain_type_fixed_point_gain',
'other': 'gain_type_other',
'a': 'filters_alias_hz',
'n': 'filters_notch_hz',
'b1': 'filters_band_lower_hz',
'b2': 'filters_band_upper_hz',
's': 'filters_slope_lower_db_per_oct',
's2': 'filters_slope_upper_db_per_oct',
'type': 'source_type',
'npt': 'source_number_per_point',
'point_interval': 'source_point_interval',
'source_pattern': 'source_pattern',
'lent': 'source_length',
'width': 'source_width',
'ss': 'sweep_start_hz',
'se': 'sweep_end_hz',
'lms': 'sweep_length_ms',
'q': 'sweep_channel_number',
'cd': 'sweep_type',
'tsl': 'taper_start_length_ms',
'tel': 'taper_end_length_ms',
'taper_type': 'taper_type',
'off': 'spread_offset',
'md': 'spread_max_distance',
'group_interval': 'spread_group_interval',
'p': 'geophones_per_group',
'y': 'geophone_spacing',
'r': 'geophone_frequency',
'gmfg': 'geophone_manufacturer',
'gmod': 'geophone_model',
'geophone_pattern': 'geophone_pattern',
'glen': 'geophone_length',
'geophone_width': 'geophone_width',
'u': 'traces_sorted_by_record',
'v': 'traces_sorted_by_cdp',
'sort_other': 'traces_sorted_by_other',
'ar': 'amplitude_recovery_none',
'sd': 'amplitude_recovery_spherical_div',
'': 'amplitude_recovery_agc',
'ar_other': 'amplitude_recovery_other',
'map_projection': 'map_projection',
'zid': 'zone_id',
'co_units': 'coordinate_units',
'processing1': 'processing1',
'processing2': 'processing2',
'unassigned1': 'unassigned1',
'unassigned2': 'unassigned2',
'unassigned3': 'unassigned3',
'unassigned4': 'unassigned4',
'unassigned5': 'unassigned5',
'unassigned6': 'unassigned6',
'unassigned7': 'unassigned7',
'unassigned8': 'unassigned8',
'unassigned9': 'unassigned9',
'unassigned10': 'unassigned10',
'unassigned11': 'unassigned11',
'unassigned12': 'unassigned12',
'unassigned13': 'unassigned13',
'unassigned14': 'unassigned14',
'unassigned15': 'unassigned15',
'unassigned16': 'unassigned16',
'unassigned17': 'unassigned17',
'end_marker': 'end_marker'
}
INV_TEMPLATE_FIELD_NAMES = dict((v, k) for k, v in TEMPLATE_FIELD_NAMES.items())
+109 -2
View File
@@ -1,8 +1,10 @@
from array import array
from collections import namedtuple
from collections import namedtuple, OrderedDict
import itertools
from itertools import zip_longest
import os
import struct
import re
from catalog import CatalogBuilder
from datatypes import CTYPES, size_in_bytes
@@ -10,10 +12,13 @@ from encoding import guess_encoding
from binary_reel_header_definition import HEADER_DEF
from ibm_float import ibm2ieee, ieee2ibm
from revisions import canonicalize_revision
import textual_reel_header_definition
from trace_header_definition import TRACE_HEADER_DEF
from util import file_length, batched, pad, round_up
from util import file_length, batched, pad, round_up, complementary_slices
from portability import EMPTY_BYTE_STRING
CARD_LENGTH = 80
CARDS_PER_HEADER = 40
@@ -476,6 +481,95 @@ def unpack_values(buf, count, item_size, fmt, endian='>'):
# swapping ourselves.
def format_standard_textual_header(revision, **kwargs):
"""Produce a standard SEG Y textual header.
Args:
revision: The SEG Y revision.
**kwargs: Named arguments corresponding to the values in the
textual_reel_header_definition.TEMPLATE_FIELD_NAMES dictionary,
which in turn correspond to the placeholders in the
textual_reel_header_definition.TEMPLATE string. Any omitted
arguments will result in placeholders being replaced by spaces.
If the end_marker argument is not supplied, an appropriate end
marker will be selected based on the SEG Y revision. For standard
end markers consider using textual_reel_header_definition.END_TEXTUAL_HEADER
or textual_reel_header_definition.END_EBCDIC.
Returns:
A list of forty Unicode strings.
Usage:
header = format_standard_textual_header(1,
client="Lundin",
company="Western Geco",
crew_number=123,
processing1="Sixty North AS",
sweep_start_hz=10,
sweep_end_hz=1000,
sweep_length_ms=10000,
sweep_channel_number=3,
sweep_type='spread')
"""
kwargs.setdefault('end_marker', textual_reel_header_definition.END_MARKERS[revision])
template = textual_reel_header_definition.TEMPLATE
placeholder_slices = parse_template(template)
background_slices = complementary_slices(placeholder_slices.values(), 0, len(template))
chunks = []
for bg_slice, placeholder in zip_longest(background_slices, placeholder_slices.items()):
if bg_slice is not None:
chunks.append(template[bg_slice])
if placeholder is not None:
ph_name, ph_slice = placeholder
ph_arg_name = textual_reel_header_definition.TEMPLATE_FIELD_NAMES[ph_name]
ph_value = kwargs.pop(ph_arg_name, '')
ph_len = ph_slice.stop - ph_slice.start
substitute = str(ph_value)[:ph_len].ljust(ph_len, ' ')
chunks.append(substitute)
if len(kwargs) > 0:
raise TypeError("The following keyword arguments did not correspond to template placeholders: {!r}"
.format(list(kwargs.keys())))
concatenation = ''.join(chunks)
lines = concatenation.splitlines(keepends=False)
return lines[1:] # Omit the first and last lines, which are artifacts of the multiline string template
def parse_template(template):
"""Parse a template to produce a dictionary of placeholders.
Args:
template: The template string containing { field-name } style fixed-width fields.
Returns:
A OrderedDict mapping field names to slices objects which can be used to index
into the template string. The order of the entries is the same as the order within
which they occur in the template.
"""
PATTERN = r'\{\s*(\w*)\s*\}'
regex = re.compile(PATTERN)
matches = regex.finditer(template)
fields = OrderedDict()
for match in matches:
name = match.group(1)
start = match.start()
end = match.end()
fields[name] = slice(start, end)
return fields
def write_textual_reel_header(fh, lines, encoding):
"""Write the SEG Y card image header, also known as the textual header
@@ -721,3 +815,16 @@ def _compile_trace_header_record():
TraceHeader = _compile_trace_header_record()
if __name__ == '__main__':
from pprint import pprint as pp
header = format_standard_textual_header(1,
client="Lundin",
company="Western Geco",
crew_number=123,
processing1="Sixty North AS",
sweep_start_hz=10,
sweep_end_hz=1000,
sweep_length_ms=10000,
sweep_channel_number=3,
sweep_type='spread')
pp(header, width=200)
+55
View File
@@ -42,6 +42,61 @@ def pad(iterable, padding=None, size=None):
return itertools.islice(pad(iterable, padding), size)
def complementary_slices(slices, start=None, stop=None):
"""Compute a complementary set of slices which alternate with given slices to form a contiguous range.
Given,
Start Stop
[-----) [-----) [----)
produces,
[--) [----) [-) [---)
Args:
slices: An sequence of existing slices
start: An optional start index, defaults to the start of the first slice.
stop: An optional one-beyond-the-end index, defaults to the stop attribute of the last slice.
Yields:
A complementary series of slices which alternate with the supplied slices. The number of returned
slices will always be len(slices) + 1 since both leading and trailing slices will always be returned.
Note the some of the returned slices may be 'empty' (having zero length).
"""
if start is None:
start = slices[0].start
if stop is None:
stop = slices[-1].stop
index = start
for s in slices:
yield slice(index, s.start)
index = s.stop
yield slice(index, stop)
def roundrobin(*iterables):
"""Take items from each iterable in turn until all iterables are exhausted.
roundrobin('ABC', 'D', 'EF') --> A D E B F C
"""
# Recipe credited to George Sakkis
pending = len(iterables)
nexts = itertools.cycle(iter(it).__next__ for it in iterables)
while pending:
try:
for n in nexts:
print
yield n()
except StopIteration:
pending -= 1
nexts = itertools.cycle(itertools.islice(nexts, pending))
def contains_duplicates(sorted_iterable):
"""Determine in an iterable series contains duplicates.