mirror of
https://github.com/wassname/segpy.git
synced 2026-07-17 11:32:53 +08:00
525 lines
15 KiB
Python
525 lines
15 KiB
Python
"""
|
|
A Python module for reading/writing/manipulating
|
|
SEG-Y formatted files
|
|
|
|
segy.readSegy : Read SEGY file
|
|
segy.read_reel_header : Get SEGY header
|
|
segy.read_trace_header : Get SEGY Trace header
|
|
segy.read_all_trace_headers : Get all SEGY Trace headers
|
|
segy.getSegyTrace : Get SEGY Trace header and trace data for one trace
|
|
|
|
segy.writeSegy : Write a data to a SEGY file
|
|
segy.writeSegyStructure : Writes a segpy data structure to a SEGY file
|
|
|
|
segy.read_binary_value : Get a value from a binary string
|
|
segy.ibm2ieee : Convert IBM floats to IEEE
|
|
|
|
segy.version : The version of SegyPY
|
|
segy.verbose : Amount of verbose information to the screen
|
|
"""
|
|
#
|
|
# segpy : A Python module for reading and writing SEG-Y formatted data
|
|
#
|
|
# Forked by Robert Smallshire from the original segypy by
|
|
#
|
|
# (C) Thomas Mejer Hansen, 2005-2006
|
|
#
|
|
# with contributions from Pete Forman and Andrew Squelch 2007
|
|
import os
|
|
|
|
import sys
|
|
import struct
|
|
import logging
|
|
|
|
from numpy import transpose
|
|
from numpy import reshape
|
|
from numpy import zeros
|
|
from numpy import arange
|
|
|
|
from revisions import SEGY_REVISION_1
|
|
from header_definition import SH_def
|
|
from trace_header_definition import STH_def
|
|
from ibm_float import ibm2ieee2
|
|
|
|
FORMAT = '%(asctime)-15s %(levelname)s %(message)s'
|
|
logging.basicConfig(level=logging.INFO, format=FORMAT)
|
|
logger = logging.getLogger('segpy.segypy')
|
|
|
|
version = '0.3.1'
|
|
|
|
REEL_HEADER_NUM_BYTES = 3600
|
|
TRACE_HEADER_NUM_BYTES = 240
|
|
|
|
l_char = struct.calcsize('c')
|
|
l_uchar = struct.calcsize('B')
|
|
l_float = struct.calcsize('f')
|
|
|
|
|
|
DATA_SAMPLE_FORMAT = {1: 'ibm',
|
|
2: 'l',
|
|
3: 'h',
|
|
5: 'f',
|
|
8: 'B'}
|
|
|
|
CTYPES = {'l': 'l', 'long': 'l', 'int32': 'l',
|
|
'L': 'L', 'ulong': 'L', 'uint32': 'L',
|
|
'h': 'h', 'short': 'h', 'int16': 'h',
|
|
'H': 'H', 'ushort': 'H', 'uint16': 'H',
|
|
'c': 'c', 'char': 'c',
|
|
'B': 'B', 'uchar': 'B',
|
|
'f': 'f', 'float': 'f',
|
|
'ibm': 'ibm'}
|
|
|
|
# TODO This is redundant with data in the SH_def below
|
|
CTYPE_DESCRIPTION = {'ibm': 'IBM float',
|
|
'l': '32 bit integer',
|
|
'h': '16 bit integer',
|
|
'f': 'IEEE float',
|
|
'B': '8 bit char'}
|
|
|
|
|
|
def size_in_bytes(ctype):
|
|
if ctype == 'l' and struct.calcsize(ctype) == 8:
|
|
return 4 # 64-bit issue?
|
|
return struct.calcsize(ctype) if ctype != 'ibm' else struct.calcsize('f')
|
|
|
|
|
|
def get_default_segy_header(ntraces=100, ns=100):
|
|
"""
|
|
SH = getDefaultSegyHeader()
|
|
"""
|
|
# TraceSequenceLine
|
|
SH = {"Job": {"pos": 3200, "type": "int32", "def": 0}}
|
|
|
|
for key in SH_def.keys():
|
|
|
|
tmpkey = SH_def[key]
|
|
if 'def' in tmpkey:
|
|
val = tmpkey['def']
|
|
else:
|
|
val = 0
|
|
SH[key] = val
|
|
|
|
SH["ntraces"] = ntraces
|
|
SH["ns"] = ns
|
|
|
|
return SH
|
|
|
|
|
|
def get_default_segy_trace_headers(ntraces=100, ns=100, dt=1000):
|
|
"""
|
|
SH = getDefaultSegyTraceHeader()
|
|
"""
|
|
# INITIALIZE DICTIONARY
|
|
STH = {"TraceSequenceLine": {"pos": 0, "type": "int32"}}
|
|
|
|
for key in STH_def.keys():
|
|
|
|
tmpkey = STH_def[key] # TODO: What is going on here?
|
|
STH[key] = zeros(ntraces)
|
|
|
|
for a in range(ntraces):
|
|
STH["TraceSequenceLine"][a] = a + 1
|
|
STH["TraceSequenceFile"][a] = a + 1
|
|
STH["FieldRecord"][a] = 1000
|
|
STH["TraceNumber"][a] = a + 1
|
|
STH["ns"][a] = ns
|
|
STH["dt"][a] = dt
|
|
return STH
|
|
|
|
|
|
def read_trace_header(f, reel_header, trace_header_name='cdp', endian='>'):
|
|
"""
|
|
read_trace_header(reel_header, TraceHeaderName)
|
|
"""
|
|
|
|
bps = get_byte_per_sample(reel_header)
|
|
|
|
# MAKE SOME LOOKUP TABLE THAT HOLDS THE LOCATION OF HEADERS
|
|
trace_header_pos = STH_def[trace_header_name]["pos"]
|
|
|
|
# TODO: Be consistent between 'type' and 'format' here.
|
|
trace_header_format = STH_def[trace_header_name]["type"]
|
|
ntraces = reel_header["ntraces"]
|
|
trace_header_values = zeros(ntraces)
|
|
binary_reader = create_binary_reader(f, trace_header_format, endian)
|
|
start_pos = trace_header_pos + REEL_HEADER_NUM_BYTES
|
|
stride = reel_header["ns"] * bps + TRACE_HEADER_NUM_BYTES
|
|
end_pos = start_pos + (ntraces - 1) * stride + 1
|
|
for i, pos in enumerate(xrange(start_pos, end_pos, stride)):
|
|
trace_header_values[i] = binary_reader(pos)
|
|
return trace_header_values
|
|
|
|
|
|
# TODO: Get the parameter ordering of reel_header and f to be consistent
|
|
def read_all_trace_headers(f, reel_header):
|
|
trace_headers = {'filename': reel_header["filename"]}
|
|
|
|
logger.debug('read_all_trace_headers : '
|
|
'trying to get all segy trace headers')
|
|
|
|
for key in STH_def.keys():
|
|
trace_header = read_trace_header(f, reel_header, key)
|
|
trace_headers[key] = trace_header
|
|
logger.info("read_all_trace_headers : " + key)
|
|
|
|
return trace_headers
|
|
|
|
|
|
def file_length(f):
|
|
pos = f.tell()
|
|
f.seek(0, os.SEEK_END)
|
|
file_size = f.tell()
|
|
f.seek(pos, os.SEEK_SET)
|
|
return file_size
|
|
|
|
|
|
def _filename(f):
|
|
return f.name if hasattr(f, 'name') else '<unknown>'
|
|
|
|
|
|
def read_segy(f, endian='>'):
|
|
"""
|
|
Data, SegyHeader, trace_headers = read_reel_header(f)
|
|
"""
|
|
|
|
# data = open(filename, 'rb').read()
|
|
|
|
filename = _filename(f)
|
|
file_size = file_length(f)
|
|
|
|
# file_size = len(data)
|
|
logger.debug("readSegy : Length of data : {0}".format(file_size))
|
|
|
|
reel_header = read_reel_header(f,
|
|
endian) # modified by A Squelch
|
|
|
|
# GET TRACE
|
|
index = REEL_HEADER_NUM_BYTES
|
|
bytes_per_sample = get_byte_per_sample(reel_header)
|
|
num_data = (file_size - REEL_HEADER_NUM_BYTES) / bytes_per_sample
|
|
|
|
Data, reel_header, trace_headers = read_traces(f,
|
|
reel_header,
|
|
num_data,
|
|
bytes_per_sample,
|
|
index,
|
|
endian)
|
|
|
|
logger.debug("readSegy : Read segy data") # modified by A Squelch
|
|
|
|
return Data, reel_header, trace_headers
|
|
|
|
|
|
def read_traces(f,
|
|
reel_header,
|
|
num_data,
|
|
bytes_per_sample,
|
|
index,
|
|
endian='>'): # added by A Squelch
|
|
"""Read the trace data.
|
|
|
|
values, SegyHeader, SegyTraceHeaders = read_traces(data,
|
|
reel_header,
|
|
num_data,
|
|
bytes_per_sample,
|
|
index)
|
|
"""
|
|
|
|
# Calculate number of dummy samples needed to account for Trace Headers
|
|
num_dummy_samples = TRACE_HEADER_NUM_BYTES / bytes_per_sample
|
|
logger.debug("read_traces : num_dummy_samples = " + str(num_dummy_samples))
|
|
|
|
# READ ALL SEGY TRACE HEADERS
|
|
trace_headers = read_all_trace_headers(f, reel_header)
|
|
|
|
logger.info("read_traces : Reading segy data")
|
|
|
|
dsf = reel_header["DataSampleFormat"]
|
|
ctype = DATA_SAMPLE_FORMAT[dsf]
|
|
description = CTYPE_DESCRIPTION[ctype]
|
|
logger.debug("read_traces : Assuming DSF = {0}, {1}".format(
|
|
dsf, description))
|
|
values, _ = read_binary_value(f, index, ctype, endian, num_data)
|
|
|
|
logger.debug("read_traces : - reshaping")
|
|
values = reshape(values,
|
|
(reel_header['ntraces'],
|
|
reel_header['ns'] + num_dummy_samples))
|
|
logger.debug("read_traces : - stripping header dummy data")
|
|
values = values[:, num_dummy_samples:
|
|
(reel_header['ns'] + num_dummy_samples)]
|
|
logger.debug("read_traces : - transposing")
|
|
values = transpose(values)
|
|
|
|
# SOMEONE NEEDS TO IMPLEMENT A NICER WAY DO DEAL WITH DSF = 8
|
|
if reel_header["DataSampleFormat"] == 8:
|
|
for i in arange(reel_header['ntraces']):
|
|
for j in arange(reel_header['ns']):
|
|
if values[i][j] > 128:
|
|
values[i][j] = values[i][j] - 256
|
|
|
|
logger.debug("read_traces : Finished reading segy data")
|
|
|
|
return values, reel_header, trace_headers
|
|
|
|
|
|
def read_reel_header(f, endian='>'):
|
|
"""
|
|
reel_header = read_reel_header(file_handle)
|
|
"""
|
|
filename = _filename(f)
|
|
reel_header = {'filename': filename}
|
|
for key in SH_def.keys():
|
|
pos = SH_def[key]["pos"]
|
|
format = SH_def[key]["type"]
|
|
|
|
reel_header[key], index = read_binary_value(f, pos, format, endian)
|
|
|
|
logger.debug(str(pos) + " " +
|
|
str(format) +
|
|
" Reading " + key +
|
|
"=" + str(reel_header[key]))
|
|
|
|
# SET NUMBER OF BYTES PER DATA SAMPLE
|
|
bps = get_byte_per_sample(reel_header)
|
|
|
|
file_size = file_length(f)
|
|
ntraces = (file_size - REEL_HEADER_NUM_BYTES) / \
|
|
(reel_header['ns'] * bps + TRACE_HEADER_NUM_BYTES)
|
|
reel_header["ntraces"] = ntraces
|
|
|
|
logger.debug('read_reel_header : successfully read ' + filename)
|
|
|
|
return reel_header
|
|
|
|
|
|
def write_segy(filename, Data, dt=1000, STHin=None, SHin=None):
|
|
"""
|
|
writeSegy(filename, Data, dt)
|
|
|
|
Write SEGY
|
|
|
|
See also readSegy
|
|
|
|
(c) 2005, Thomas Mejer Hansen
|
|
|
|
MAKE OPTIONAL INPUT FOR ALL SEGYHTRACEHEADER VALUES
|
|
|
|
"""
|
|
if SHin is None: SHin = {}
|
|
if STHin is None: STHin = {}
|
|
|
|
logger.debug("writeSegy : Trying to write " + filename)
|
|
|
|
N = Data.shape
|
|
ns = N[0]
|
|
ntraces = N[1]
|
|
print ntraces, ns
|
|
|
|
SH = get_default_segy_header(ntraces, ns)
|
|
STH = get_default_segy_trace_headers(ntraces, ns, dt)
|
|
|
|
# ADD STHin, if exists...
|
|
for key in STHin.keys():
|
|
print key
|
|
for a in range(ntraces):
|
|
STH[key] = STHin[key][a]
|
|
|
|
# ADD SHin, if exists...
|
|
for key in SHin.keys():
|
|
print key
|
|
SH[key] = SHin[key]
|
|
|
|
write_segy_structure(filename, Data, SH, STH)
|
|
|
|
|
|
def write_segy_structure(filename,
|
|
Data,
|
|
SH,
|
|
STH,
|
|
endian='>'): # modified by A Squelch
|
|
"""
|
|
writeSegyStructure(filename, Data, SegyHeader, SegyTraceHeaders)
|
|
|
|
Write SEGY file using SegyPy data structures
|
|
|
|
See also readSegy
|
|
|
|
(c) 2005, Thomas Mejer Hansen
|
|
|
|
"""
|
|
|
|
logger.debug("writeSegyStructure : Trying to write " + filename)
|
|
|
|
f = open(filename, 'wb')
|
|
|
|
# VERBOSE INF
|
|
revision = SH["SegyFormatRevisionNumber"]
|
|
dsf = SH["DataSampleFormat"]
|
|
|
|
try: # block added by A Squelch
|
|
DataDescr = SH_def["DataSampleFormat"]["descr"][revision][dsf]
|
|
except KeyError:
|
|
logging.critical(" An error has ocurred interpreting a SEGY binary"
|
|
"header key")
|
|
logging.critical(" Please check the Endian setting for this "
|
|
"file: {0}".format(SH["filename"]))
|
|
sys.exit()
|
|
|
|
logger.debug("writeSegyStructure : SEG-Y revision = " + str(revision))
|
|
logger.debug("writeSegyStructure : DataSampleFormat = " +
|
|
str(dsf) +
|
|
"(" + DataDescr + ")")
|
|
|
|
# WRITE SEGY HEADER
|
|
|
|
for key in SH_def.keys():
|
|
pos = SH_def[key]["pos"]
|
|
format = SH_def[key]["type"]
|
|
value = SH[key]
|
|
put_value(value, f, pos, format, endian)
|
|
|
|
# SEGY TRACES
|
|
ctype = SH_def['DataSampleFormat']['datatype'][revision][dsf]
|
|
bps = SH_def['DataSampleFormat']['bps'][revision][dsf]
|
|
|
|
sizeT = TRACE_HEADER_NUM_BYTES + SH['ns'] * bps
|
|
|
|
for itrace in range(SH['ntraces']):
|
|
index = REEL_HEADER_NUM_BYTES + itrace * sizeT
|
|
logger.debug('Writing Trace #' +
|
|
str(itrace + 1) +
|
|
'/' + str(SH['ntraces']))
|
|
# WRITE SEGY TRACE HEADER
|
|
for key in STH_def.keys():
|
|
pos = index + STH_def[key]["pos"]
|
|
format = STH_def[key]["type"]
|
|
value = STH[key][itrace]
|
|
logger.debug(str(pos) + " " +
|
|
str(format) +
|
|
" Writing " + key +
|
|
"=" + str(value))
|
|
put_value(value, f, pos, format, endian)
|
|
|
|
# Write Data
|
|
cformat = endian + ctype
|
|
for s in range(SH['ns']):
|
|
strVal = struct.pack(cformat, Data[s, itrace])
|
|
f.seek(index +
|
|
TRACE_HEADER_NUM_BYTES +
|
|
s * struct.calcsize(cformat))
|
|
f.write(strVal)
|
|
|
|
f.close()
|
|
|
|
|
|
def put_value(value, fileid, index, ctype='l', endian='>', number=1):
|
|
"""
|
|
putValue(data, index, ctype, endian, number)
|
|
"""
|
|
ctype = CTYPES[ctype]
|
|
|
|
cformat = endian + ctype*number
|
|
|
|
logger.debug('putValue : cformat : ' + cformat + ' ctype = ' + ctype)
|
|
|
|
strVal = struct.pack(cformat, value)
|
|
fileid.seek(index)
|
|
fileid.write(strVal)
|
|
|
|
return 1
|
|
|
|
|
|
def create_binary_reader(f, ctype='l', endian='>'):
|
|
"""Create a unary callable which reads a given binary data type from a file.
|
|
"""
|
|
ctype = CTYPES[ctype]
|
|
size = size_in_bytes(ctype)
|
|
|
|
cformat = endian + ctype
|
|
|
|
def reader(index):
|
|
f.seek(index, os.SEEK_SET)
|
|
data = f.read(size)
|
|
# TODO: Check the content of data before proceeding
|
|
value = struct.unpack(cformat, data)
|
|
return value[0]
|
|
|
|
return reader
|
|
|
|
|
|
def read_binary_value(f, index, ctype='l', endian='>', number=1):
|
|
"""
|
|
read_binary_value(data, index, ctype, endian, number)
|
|
"""
|
|
|
|
ctype = CTYPES[ctype]
|
|
|
|
size = size_in_bytes(ctype)
|
|
|
|
cformat = endian + ctype * number
|
|
|
|
logger.debug('read_binary_value : cformat : ' + cformat)
|
|
|
|
index_end = index + size * number
|
|
|
|
f.seek(index, os.SEEK_SET)
|
|
data = f.read(size * number)
|
|
if ctype == 'ibm':
|
|
# ASSUME IBM FLOAT DATA
|
|
Value = range(number)
|
|
for i in arange(number):
|
|
index_ibm = i * 4
|
|
Value[i] = ibm2ieee2(data[index_ibm: index_ibm + 4])
|
|
# this returns an array as opposed to a tuple
|
|
else:
|
|
# TODO: Check the content of data before proceeding
|
|
Value = struct.unpack(cformat, data)
|
|
|
|
if ctype == 'B':
|
|
logger.warning('read_binary_value : '
|
|
'Inefficient use of 1 byte Integer...', 1)
|
|
|
|
logger.debug('read_binary_value : ' +
|
|
'start = ' + str(index) +
|
|
' size = ' + str(size) +
|
|
' number = ' + str(number) +
|
|
' Value = ' + str(Value) +
|
|
' cformat = ' + str(cformat))
|
|
|
|
if number == 1:
|
|
return Value[0], index_end
|
|
else:
|
|
return Value, index_end
|
|
|
|
|
|
def get_byte_per_sample(SH):
|
|
revision = SH["SegyFormatRevisionNumber"]
|
|
|
|
if revision == 100:
|
|
revision = SEGY_REVISION_1
|
|
|
|
dsf = SH["DataSampleFormat"]
|
|
|
|
try: # block added by A Squelch
|
|
bps = SH_def["DataSampleFormat"]["bps"][revision][dsf]
|
|
except KeyError:
|
|
# TODO: This should not be a critical failure - should just convert
|
|
# exception
|
|
logging.critical(" An error has occurred interpreting a SEGY "
|
|
"binary header key")
|
|
logging.critical("Please check the Endian setting for "
|
|
"this file: {0}".format(SH["filename"]))
|
|
sys.exit()
|
|
|
|
logger.debug("getBytePerSample : bps = " + str(bps))
|
|
|
|
return bps
|
|
|
|
if __name__ == '__main__':
|
|
filename = r'C:\Users\rjs\opendtectroot\Blake_Ridge_Hydrates_3D'\
|
|
r'\stack_final_scaled50_int8.sgy'
|
|
with open(filename, 'rb') as segy:
|
|
Data, SH, SegyTraceHeaders = read_segy(segy, filename)
|