3. 卷头关键字段核查
SEG-Y 文件的卷头(EBCDIC 3200 字节)与二进制文件头(400 字节)是整份数据的“身份证”。野外采集系统、处理系统、观测系统定义软件在写盘时,往往各自按自己的约定填写这些字段,导致同一批数据在进入处理流程前就埋下隐患。卷头核查的目标不是把 400 字节全部读一遍,而是抓住那些一旦出错就会直接导致道头解释错误、时间轴错位或振幅解码失败的关键字段,逐项比对、交叉验证。
需要先明确一个前提:SEG-Y 标准对二进制文件头中多数字段只给出“建议含义”,并未强制统一。例如采样间隔字段在标准修订版中定义为微秒,但部分老系统按毫秒写入;测量系统字段(Measurement System)用 1 表示米、2 表示英尺,可有些软件写 0 或直接留空。因此核查时必须坚持“以实际数据反推、以多字段互证”的原则,而不能只看单个字段的标称值。
关键字段的核查内容与判据
作业标识类字段包括 Job ID(字节 3205–3208)、Line Number(3209–3212)、Reel Number(3213–3216)以及卷头中的作业名、测线名文本。核查要点是:同一测线、同一束数据的作业号是否一致;测线号是否与观测系统定义文件(SPS 或 UKOOA 的 relation 表)中的 line name 对应;卷号是否在多次写盘时被重复使用。实践中常见的问题是野外班报记录的线号与卷头写入的线号相差一位或大小写不一致,导致后续按线号索引时漏掉整条测线。
采样间隔(Sample Interval,字节 3217–3218,单位微秒)与采样点数(Samples per Trace,字节 3221–3222)必须成对核查。判据有两条:其一,采样间隔乘以采样点数应等于记录长度,且与道头中 delay recording time 及道尾时间相吻合;其二,采样间隔应等于采集设计值,例如设计 2 ms 则字段值应为 2000。若字段值为 2,说明写入方用了毫秒,需在读取时乘以 1000 修正,否则时间轴会缩短 1000 倍。采样点数则要与实际道数据字节数反算:单道字节数 = 采样点数 × 每样点字节数,若反算结果与文件总长除以道数不符,说明采样点数字段被错误覆盖。
格式码(Data Sample Format Code,字节 3225–3226)决定每个样点的编码方式,是解码的“钥匙”。1 表示 4 字节 IBM 浮点,2 表示 4 字节整型,3 表示 2 字节整型,5 表示 4 字节 IEEE 浮点,8 表示 1 字节整型。核查时不能只信字段值,应结合文件总长做反算校验:若格式码为 5、采样点数 1000、道数 500,则数据区应为 500×1000×4 = 2 000 000 字节,加上 3600 字节文件头应等于文件实际大小。若反算值只有实际值的一半,很可能是格式码被写成了 3(2 字节)而实际数据是 4 字节。这类“格式码与实际字节宽度不符”的错误在跨系统转储时极为常见。
测量系统字段(字节 3255–3256)影响坐标与高程的单位解释。值为 1 表示米制,2 表示英尺。核查时应与道头中的坐标缩放因子(scalar)以及观测系统文件中的坐标单位对照:若卷头标米制而 SPS 文件坐标以英尺给出,或道头坐标经缩放后量级明显异常(如 X 坐标出现 3×10⁶ 量级),则测量系统字段很可能填错。此外还应检查固定长度道头标志(字节 3501–3502),值为 1 表示所有道头长度一致,这是按固定偏移读取道头的前提;若该值为 0 而实际道头等长,读取程序仍可按固定长度处理,但需在日志中记录这一不一致。
用反算校验代替“看一眼字段值”
最可靠的核查方式是写一段短脚本,把卷头字段读出来,同时用文件系统给出的文件大小做反算。以下示例用 Python 读取二进制文件头并做三项一致性检查,参数为教学示例,实际运行时以真实文件为准:
import struct, os
path = "LINE_A_001.sgy"
size = os.path.getsize(path)
with open(path, "rb") as f:
f.seek(3200)
bh = f.read(400)
job_id = struct.unpack(">i", bh[4:8])[0] # 3205
line_no = struct.unpack(">i", bh[8:12])[0] # 3209
samp_int = struct.unpack(">H", bh[16:18])[0] # 3217, μs
samp_num = struct.unpack(">H", bh[20:22])[0] # 3221
fmt_code = struct.unpack(">H", bh[24:26])[0] # 3225
meas_sys = struct.unpack(">H", bh[54:56])[0] # 3255
fixed_len= struct.unpack(">H", bh[300:302])[0] # 3501
bytes_per_sample = {1:4, 2:4, 3:2, 5:4, 8:1}.get(fmt_code)
trace_bytes = 240 + samp_num * bytes_per_sample
n_traces = (size - 3600) // trace_bytes
print("Job", job_id, "Line", line_no)
print("dt(us)", samp_int, "ns", samp_num, "fmt", fmt_code)
print("meas_sys", meas_sys, "fixed_len", fixed_len)
print("反算道数", n_traces, "余数", (size - 3600) % trace_bytes)
若输出的“余数”不为 0,说明采样点数、格式码或道头长度三者中至少有一个与实际数据不符。此时可固定道头长度 240 字节,分别假设格式码为 3 和 5 重算,看哪一种假设能使余数为 0,从而定位真正的编码方式。这种“假设—反算—排除”的做法,比反复询问数据提供方更直接有效。
不一致情形的记录与处置
核查结果应落到一张可执行的记录表上,而不是停留在口头确认。表中至少包含:字段名、字节位置、卷头标称值、反算或比对得到的期望值、是否一致、处置方式(修正读取参数 / 回退原始数据 / 联系采集方确认)。对于采样间隔单位错误、格式码与字节宽度不符这两类问题,通常可以在读取程序中通过参数覆盖解决,无需改动原始文件;对于线号、作业号不一致,则应以观测系统定义文件为准建立映射表,并在后续道头索引时统一使用映射后的名称。测量系统字段缺失时,可依据坐标量级和 SPS 文件单位反推,但必须在处理日志中注明推断依据,避免后续环节误用。
需要强调的是,卷头字段之间并非彼此独立。采样间隔、采样点数、格式码共同决定了单道字节数,而单道字节数又必须与文件总长、道数吻合;测量系统与道头坐标 scalar 共同决定坐标的物理量级。任何一项被单独修改而其余未同步,都会在反算中暴露出来。因此核查的本质是建立一组相互约束的等式,用数据本身去检验卷头声明,而不是逐字段孤立地“看一眼”。