zgy地震数据体的显示

python 复制代码
# -*- coding: utf-8 -*-
"""
show_zgy_section.py
读取 ZGY(Schlumberger/OpenZGY)格式三维地震数据体,并显示
inline / crossline / 时间(深度)切片剖面。

依赖安装:
    pip install openzgy numpy matplotlib

用法示例:
    python show_zgy_section.py data.zgy                  # 打印数据体信息
    python show_zgy_section.py data.zgy --inline 500     # 显示某条 inline 剖面
    python show_zgy_section.py data.zgy --crossline 300  # 显示某条 crossline 剖面
    python show_zgy_section.py data.zgy --timeslice 2000 # 显示某时间切片
    python show_zgy_section.py data.zgy --inline 500 --cmap seismic --save inline500.png
"""

import argparse
import sys

import numpy as np
import matplotlib.pyplot as plt


def open_reader(zgy_file):
    """打开 ZGY 文件,返回 reader 对象(失败时给出中文提示)。"""
    try:
        from openzgy.api import ZgyReader
    except ImportError:
        sys.exit("缺少依赖库 openzgy,请先执行:pip install openzgy")
    try:
        return ZgyReader(zgy_file)
    except Exception as e:
        sys.exit(f"无法打开 ZGY 文件 {zgy_file}: {e}")


def _geometry(reader):
    """返回 (il_start, il_step, xl_start, xl_step, z_start, z_step, z_unit)。"""
    il0, xl0 = reader.annotstart          # 起始真实坐标 (inline, crossline)
    dil, dxl = reader.annotinc            # 步长
    z0, dz, zunit = reader.zstart, reader.zinc, reader.zunitname or "ms"
    return il0, dil, xl0, dxl, z0, dz, zunit


def print_info(reader):
    """打印数据体基本 geometry 信息。"""
    il0, dil, xl0, dxl, z0, dz, zunit = _geometry(reader)
    ni, nx, nz = reader.size
    print("=" * 60)
    print(f"数据体大小 (inline, crossline, samples): {ni} x {nx} x {nz}")
    print(f"Inline 范围:    {il0} ~ {il0 + (ni - 1) * dil} (step {dil})")
    print(f"Crossline 范围: {xl0} ~ {xl0 + (nx - 1) * dxl} (step {dxl})")
    print(f"Z 范围:         {z0} ~ {z0 + (nz - 1) * dz} (step {dz}, 单位 {zunit})")
    print("=" * 60)


def _real_to_index(real, start, step):
    """把真实道号/采样值换算成数组下标。"""
    idx = int(round((real - start) / step))
    return idx


def read_section(reader, kind, value):
    """
    读取一个剖面,返回 (data, x轴标签, y轴标签)。
    kind: 'inline' / 'crossline' / 'timeslice'
    """
    il0, dil, xl0, dxl, z0, dz, zunit = _geometry(reader)
    ni, nx, nz = reader.size

    if kind == "inline":
        idx = _real_to_index(value, il0, dil)
        if not (0 <= idx < ni):
            sys.exit(f"Inline {value} 超出范围 (数组下标 {idx},共 {ni} 条)")
        # read(start, data): start 为起点下标,data 为预先分配的 float32 缓冲
        buf = np.zeros((1, nx, nz), dtype=np.float32)
        reader.read((idx, 0, 0), buf)
        data = buf[0, :, :].T  # (nz, nx)
        x = xl0 + np.arange(nx) * dxl
        y = z0 + np.arange(nz) * dz
        return data, x, y, f"Inline {value}"

    if kind == "crossline":
        idx = _real_to_index(value, xl0, dxl)
        if not (0 <= idx < nx):
            sys.exit(f"Crossline {value} 超出范围 (数组下标 {idx},共 {nx} 条)")
        buf = np.zeros((ni, 1, nz), dtype=np.float32)
        reader.read((0, idx, 0), buf)
        data = buf[:, 0, :].T  # (nz, ni)
        x = il0 + np.arange(ni) * dil
        y = z0 + np.arange(nz) * dz
        return data, x, y, f"Crossline {value}"

    if kind == "timeslice":
        idx = _real_to_index(value, z0, dz)
        if not (0 <= idx < nz):
            sys.exit(f"时间切片 {value} 超出范围 (数组下标 {idx},共 {nz} 个采样)")
        buf = np.zeros((ni, nx, 1), dtype=np.float32)
        reader.read((0, 0, idx), buf)
        data = buf[:, :, 0]  # (ni, nx)
        x = xl0 + np.arange(nx) * dxl
        y = il0 + np.arange(ni) * dil
        return data, x, y, f"Time slice {value} {zunit}"

    sys.exit(f"未知剖面类型: {kind}")


def plot_section(data, x, y, title, xlabel, ylabel, cmap, save=None, clip=99):
    """绘制二维剖面:imshow + 色标;clip 为振幅截断百分位,压制异常值。"""
    vmax = np.percentile(np.abs(data[np.isfinite(data)]), clip) if np.isfinite(data).any() else 1
    vmax = vmax if vmax > 0 else 1.0

    fig, ax = plt.subplots(figsize=(12, 6))
    extent = [x[0], x[-1], y[-1], y[0]]  # y 轴默认上小下大(深度向下)
    im = ax.imshow(data, cmap=cmap, aspect="auto", extent=extent,
                   vmin=-vmax, vmax=vmax, interpolation="bilinear")
    ax.set_title(title)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    fig.colorbar(im, ax=ax, label="Amplitude")
    fig.tight_layout()

    if save:
        fig.savefig(save, dpi=200)
        print(f"剖面已保存到: {save}")
    else:
        plt.show()


def main():
    p = argparse.ArgumentParser(description="显示 ZGY 地震体剖面")
    p.add_argument("zgy_file", help="ZGY 文件路径")
    p.add_argument("--inline", type=float, default=None, help="要显示的 inline 号")
    p.add_argument("--crossline", type=float, default=None, help="要显示的 crossline 号")
    p.add_argument("--timeslice", type=float, default=None, help="要显示的时间/深度切片值")
    p.add_argument("--cmap", default="seismic", help="colormap,默认 seismic")
    p.add_argument("--save", default=None, help="保存为图片文件(如 out.png),不填则弹出窗口")
    args = p.parse_args()

    reader = open_reader(args.zgy_file)
    print_info(reader)
    _, _, _, _, _, _, zunit = _geometry(reader)

    sections = [
        ("inline", args.inline, "Crossline", zunit),
        ("crossline", args.crossline, "Inline", zunit),
        ("timeslice", args.timeslice, "Crossline", "Inline"),
    ]
    shown = False
    n_sections = sum(1 for _, v, _, _ in sections if v is not None)
    for kind, value, xlab, ylab in sections:
        if value is None:
            continue
        data, x, y, title = read_section(reader, kind, value)
        # 多个剖面同时保存时,自动在文件名中加剖面标识避免互相覆盖
        save = args.save
        if save and n_sections > 1:
            base, dot, ext = save.rpartition(".")
            base = base or save
            ext = dot + ext if dot else ""
            save = f"{base}_{kind}{int(value)}{ext}"
        plot_section(data, x, y, title, xlab, ylab, args.cmap, save, clip=99)
        shown = True

    if not shown:
        print("未指定剖面:使用 --inline / --crossline / --timeslice 之一来显示剖面。")
        print("提示:可先只传文件名查看数据体范围,再选取合适的剖面号。")


if __name__ == "__main__":
    main()
相关推荐
“AI国潮设计-小江”1 小时前
【Python/SDXL实战】潮汕国潮IP视觉落地:普宁美食猫IP & 创意甜品设计(附ComfyUI工作流与商用授权思路)
开发语言·人工智能·python·prompt·aigc
L@ncor2 小时前
第一章 初识智能体 · 学习笔记
人工智能·python
宁渡AI大模型2 小时前
河南宁渡科技有限公司|宁渡课堂 AI 全栈面试分享,大模型 API 开发与流式输出面试题
人工智能·python·ai·大模型
Java后端的Ai之路2 小时前
Git冲突完整排查与实战:本地修改覆盖报错到成功推送全流程复盘
开发语言·人工智能·git·python·pop
javartisan2 小时前
AI Agent 中 Tool / Skill 的调用链路设计
python
钱栈up2 小时前
大屏Logo定位调了8次才合格:响应式定位的经验总结安装Unsloth桌面端报PyTorch失败:Python 3.13的ABI不兼容所致
python
wuhuhuan2 小时前
Case Generator 测试自动化平台
python·测试工具·自动化
Flynt3 小时前
Astra 自主跑了 35 小时烧掉 40 亿 token,产出为零:我给 Agent 加了三道闸
python·aigc·agent
mldong3 小时前
Python 开发者也有自己的轻量工作流引擎了:pip install 一行,5 分钟跑通一条审批流
后端·python·架构