mirror of
https://github.com/wassname/segpy.git
synced 2026-09-09 11:34:00 +08:00
Some progress towards complete support for writing SEG Y
This commit is contained in:
@@ -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
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user