资讯动态

用Python构建测井曲线清洗与地质剖面图绘制工作流

发布时间:2026/10/14 17:42:16 来源:尧图企业网站定制
简介geolog-app 是一份基于 Python/Django 开发的地质类 Web 应用源码包面向初入门的 Web 开发者或想了解 Django 项目结构的读者。压缩包仅 14KB共 22 个文件以 14 个 .py 文件为核心覆盖路由、视图、模型、配置等标准 Django 模块另含 HTML/CSS 前端页面、requirements.txt 依赖清单与 SQLite 数据库文件结构精简便于直接运行和二次修改。目前已有 162 人浏览学习。通过学习这份资源可以快速掌握一个小型 Django 项目的目录布局、数据库表与视图联动方式以及地质数据在网页中的展示与交互处理思路适合作为入门级实战样例。若想从零理解 Python Web 应用的基本工程组织这份代码包能提供清晰参照。1. geolog-app 的本质一套把测井曲线、岩性描述和分层数据揉在一起的 Python 工作流干地质或者搞过录井的人都有这种体验甲方发来一个压缩包里面是几口井的 LAS 文件、几张岩性描述的 Excel 表、还有一堆画得乱七八糟的地质剖面图。资料是齐的但格式是乱的换一口井就要重新整理一次数据等真正开始画图、算储量的时候时间已经耗掉一大半。geolog-app 就是朝这个痛点来的一个 Python 项目它把测井曲线解析、岩性录井表清洗、地层分层和剖面图绘制串成了一条完整的本地工作流目标用户是地质工程师、录井技术人员和刚转行写脚本的石油专业学生。它不是用来做精细解释或建模的大型平台而是让你手里那些半结构化的地质数据变得规整、可复现、能批量处理的一个工具包。适合的场景很明确数据整理阶段反反复复、出图效率低、每口井的处理流程都不一致。这个项目最大的特点是用脚本替代手工整理。代码思路非常符合工程师的直觉——先定数据标准再做转换最后输出图纸和统计表。全文按照数据从哪来 → 怎么清洗 → 如何出图 → 哪些坑会卡住你的顺序展开下面每一步都会给出可以直接改用的代码。2. 测井曲线等深度重采样把乱糟糟的 LAS 文件变成统一深度域2.1 为什么要先做深度对齐拿到 LAS 文件后第一件事永远不是画图而是检查深度采样。不同仪器、不同井段测出来的深度步长经常不一样有的是 0.1 米一个点有的是 0.125 米还有的是 0.2 米。如果直接把两条曲线叠在一起画分辨率不匹配会导致后面分层时出现错位最典型的表现就是同一套地层的边界在 GR 曲线上是一个深度在电阻率曲线上又差了几十厘米。这种情况手工调整很痛苦但用脚本做等深度重采样却是十分钟的事。geolog-app 里的思路很简单选定一个基准深度序列然后对每一条曲线做线性插值。常见的做法是选步长最小、起始深度最早的那条曲线作为基准这样原始信息不丢失。比如基准是 1000 米到 2000 米、步长 0.1 米另一条曲线步长 0.2 米那就把后者插值到 0.1 米的网格上。2.2 使用 lasio 读取并统一采样网格这里用到一个广泛使用的测井数据解析库——lasio它是读取 LAS 文件的事实标准工具之一能够处理 LAS 2.0 和 3.0 格式也能处理常见的编码问题。import lasio import pandas as pd import numpy as np def resample_las_to_grid(las_path, step0.1): # 读取 LAS 文件lasio 会自动识别曲线名和深度列 las lasio.read(las_path) # 取出原始深度序列 depth_old las.index # 构造目标深度网格从顶深到底深步长 step depth_new np.arange(depth_old[0], depth_old[-1] step, step) # 对每一条曲线做线性插值 df_resampled pd.DataFrame({DEPTH: depth_new}) for curve in las.curves: curve_name curve.mnemonic # 跳过深度列本身 if curve_name.upper() in (DEPT, DEPTH): continue values las[curve_name] # np.interp 是 numpy 的线性插值要求 x 递增 df_resampled[curve_name] np.interp(depth_new, depth_old, values) return df_resampled # 示例对一口井的 LAS 做 0.1 米重采样 df_one_well resample_las_to_grid(well_01.las, step0.1) print(df_one_well.head())这段代码的核心是两个函数lasio.read负责把 LAS 文件里的文本元数据和曲线数值读成 Python 对象np.interp负责把离散的采样点映射到新的网格上。参数上需要注意step的取值不要小于原始数据的最小采样间隔否则插值等于在猜数据没有新增任何地下信息。如果原始数据是 0.2 米采样你却重采样到 0.05 米除了文件变大之外没有任何意义。2.3 曲线名统一与单位检查重采样只是第一步真正让人头疼的是不同井的曲线命名规则不一样。某口井的 GR 曲线叫GR另一口井叫GR_R还有一口井直接叫GAPI你要是写死了一个名字换井就崩。geolog-app 里维护了一个别名映射表建议你本地也建一个。curve_alias { GR: [GR, GR_R, GAPI, GR_EDTC], RT: [RT, RES, LLD, RT_DEEP], DEN: [DEN, RHOB, DEN_COMP], CN: [CN, NPHI, CNC], } def normalize_curve_name(raw_name): # 先转大写再去掉特殊符号 key raw_name.strip().upper().replace(-, _) for standard, aliases in curve_alias.items(): if key in aliases: return standard return key # 没匹配上就保留原名这个函数逻辑很直接做成一个字典键是标准名值是别名列表。匹配时先把原始曲线名转大写、去掉连字符再逐一比对。实际使用中你还需要把 LAS 文件里的单位和深度基准加进一个 Excel 元数据表里方便后续跟踪——哪个文件是哪口井、谁的曲线被重命名了、单位是什么。这些信息不写下来三个月以后你自己都记不清。3. 岩性录井表清洗从人写的变成机器认的3.1 自由文本岩性描述的坑岩性录井表是地质数据里最脏的部分。录井人员填写的时候经常用简称比如细砂岩写成细砂、粉砂质泥岩写成粉泥同一个层段的描述在不同井里风格完全不一致。更麻烦的是一张表里可能是多井合并导出A 井用的是中文B 井是英文缩写C 井还有拼音首字母缩写。直接把这些文本当成数据用后面画图时分类统计就是灾难。3.2 构建岩性字典实现标准化映射geolog-app 的做法是把岩性描述映射到一个标准分类集合上而不是试图理解复杂的自然语言。这个思路更务实录井岩性描述虽然口语化但实际岩性大类是有限的比如泥岩、砂岩、粉砂岩、石灰岩、白云岩、膏岩等加上少量过渡类型就够了。import re litho_dict { 泥岩: [泥岩, 泥, MUD, CLAYSTONE, MUDST], 粉砂质泥岩: [粉砂质泥岩, 粉泥, SILTY MUDST], 泥质粉砂岩: [泥质粉砂岩, 泥粉, MUDDY SILTST], 粉砂岩: [粉砂岩, 粉砂, SILTSTONE, SILT], 细砂岩: [细砂岩, 细砂, FINE SAND, F.SAND], 中砂岩: [中砂岩, 中砂, MED SAND, M.SAND], 粗砂岩: [粗砂岩, 粗砂, COARSE SAND, C.SAND], 石灰岩: [石灰岩, 灰岩, LIMESTONE, LIME], 白云岩: [白云岩, DOLOMITE, DOLO], } def normalize_litho(raw_text): if not isinstance(raw_text, str): return UNKNOWN # 去掉数字、空格和特殊符号 text re.sub(r[\d\s\(\)·], , raw_text).upper() for standard, aliases in litho_dict.items(): for alias in aliases: if alias.upper() in text: return standard return UNKNOWN逻辑上这段代码的关键点是包含匹配而不是完全相等。因为原始描述经常是浅灰色细砂岩或者灰绿色泥岩夹薄层粉砂岩你期望的是提取出主岩性。把标准词别名做成列表的好处是一旦遇到新的叫法只需要往列表里加一行不需要改任何逻辑。但也要注意这种映射策略只能应对主岩性单一的情况如果一段描述里有两种岩性它会返回第一个匹配到的结果后面你还需要决定是取主还是取次——这是有歧义的部分我一般在映射之后加一道人工抽查。3.3 深度区间合并与空白段补全录井表通常是不等间距的比如 1000.0 到 1002.5 米是泥岩1002.5 到 1007.0 是细砂岩深度连续但分段长度不一。清洗后的数据要转成连续的深度域方便和测井曲线深度对齐。def merge_litho_intervals(df, depth_start_coldepth_top, depth_end_coldepth_bottom, litho_collitho_std): # 先按顶深排序 df_sorted df.sort_values(depth_start_col).reset_index(dropTrue) merged [] for _, row in df_sorted.iterrows(): if merged and merged[-1][litho_col] row[litho_col] and merged[-1][depth_end_col] row[depth_start_col]: # 相邻且岩性相同合并区间取最大底深 merged[-1][depth_end_col] max(merged[-1][depth_end_col], row[depth_end_col]) else: merged.append(row.to_dict()) return pd.DataFrame(merged)这里的逻辑是相邻检查如果当前区间的顶深小于等于上一个区间的底深同时岩性相同就把两个区间合并成一个底深取较大值。这个步骤会消除录井表里因为人工分段造成的同一个岩性被拆成好几段的问题。合并之后建议检查一下有没有空隙段如果录井表的深度和测井曲线的深度范围不一致后期画图会出现断层。4. 地质剖面图绘制matplotlib 出图与四个常见排查项4.1 为什么不用专业绘图软件非要自己画很多单位买了商业地质软件但实际用起来有门槛——学习成本高、许可证不在手、投影方式不灵活。geolog-app 选择用 matplotlib 做剖面图并不寒酸它的好处在于所有图件直接从清洗后的 DataFrame 生成换参数重跑一遍就行不需要在软件界面上手动挪井位、改刻度。而且脚本出图图例、颜色、深度比例都处于可控状态领导要改配色改一个字典重跑而不是在软件里一层层点菜单。4.2 深度-岩性柱状图的核心代码按标准化后的岩性分类填色深度轴从顶到底右侧画岩性柱左轴叠上 GR 曲线这是最常见的一版图件。import matplotlib.pyplot as plt from matplotlib.patches import Rectangle litho_color { 泥岩: #4C4C4C, 粉砂质泥岩: #8C8C8C, 泥质粉砂岩: #BEBEBE, 粉砂岩: #E8D154, 细砂岩: #F5DEB3, 中砂岩: #D2B48C, 粗砂岩: #A0522D, 石灰岩: #87CEEB, 白云岩: #FFA500, } def plot_well_profile(ax_depth, well_df, litho_df, gr_curve_nameGR): # 左轴画 GR 曲线 ax_depth.plot(well_df[DEPTH], well_df[gr_curve_name], colorblue, lw0.8, labelGR) ax_depth.set_xlabel(GR (API)) ax_depth.invert_yaxis() # 深度轴必须反转浅在上、深在下 ax_depth.set_ylabel(DEPTH (m)) # 右轴叠岩性柱 ax_litho ax_depth.twiny() for _, interval in litho_df.iterrows(): top interval[depth_top] bottom interval[depth_bottom] litho interval[litho_std] # Rectangle 的坐标是 (y_left_bottom, width, height) rect Rectangle((0, top), 1, bottom - top, facecolorlitho_color.get(litho, #CCCCCC)) ax_litho.add_patch(rect) ax_litho.set_xlim(0, 1) ax_litho.set_xticks([]) ax_litho.set_xlabel(LITHOLOGY) plt.show() # 示例调用 fig, ax_depth plt.subplots(figsize(6, 12)) plot_well_profile(ax_depth, df_one_well, df_merged_litho)这里的核心图形学逻辑是左边一个正常轴用来画 GR 曲线twiny()复制出一个共享 y 轴的叠加轴加矩形块来画岩性。参数上要注意Rectangle的第一个坐标元组是左下角y 方向是深度所以top要放在 y 的起点位置高度是bottom - top。invert_yaxis一定不能省略否则整个图上下颠倒地层顺序完全反掉。4.3 常见排查项一GR 曲线反向叠加时被岩性柱遮挡现象曲线画在下面岩性色块把曲线盖住了根本看不出来对应关系。 原因绘图顺序是先画曲线、后画矩形默认后绘制的对象覆盖先绘制的对象。 解决在ax_litho.add_patch(rect)前给矩形设置透明度或者调整 zorder。推荐透明度rect Rectangle((0, top), 1, bottom - top, facecolorlitho_color.get(litho, #CCCCCC), alpha0.5, zorder1) ax_depth.plot(..., zorder3)4.4 常见排查项二深度范围不重合导致剖面缺段现象录井表的顶深比测井曲线起始深度浅导致曲线有一段空白。 原因录井表和测井曲线的深度起始点不一致比如 LAS 文件是从 1000 米开始测的但录井表从 990 米就有描述。 解决在画图前先取交集深度范围直接裁剪数据。top_limit max(well_df[DEPTH].min(), litho_df[depth_top].min()) bottom_limit min(well_df[DEPTH].max(), litho_df[depth_bottom].max()) well_df well_df[(well_df[DEPTH] top_limit) (well_df[DEPTH] bottom_limit)] litho_df litho_df[(litho_df[depth_bottom] top_limit) (litho_df[depth_top] bottom_limit)]4.5 常见排查项三中文标签乱码现象图例和坐标轴中文显示成方块。 原因matplotlib 默认字体不含中文字符。 解决在代码开头指定一个系统已有的中文字体。常见做法是plt.rcParams[font.sans-serif] [SimHei] # 或者 [Microsoft YaHei] plt.rcParams[axes.unicode_minus] False如果字体指定后仍然报错找不到字族可以用matplotlib.font_manager列出所有可用字体名再挑一个实际的加载进去不要硬抄网上的配置。4.6 常见排查项四图例重复现象同一套图用循环画了很多条曲线图例上出现几十行重复的 GR。 原因label参数被赋给了循环里的每一条曲线但曲线名称相同导致图例合并逻辑失效。 解决只在第一个数据项上加 label其余不设或者画完后用ax.legend().set_visible(False)手动关掉。for idx, well_name in enumerate(well_list): if idx 0: ax.plot(depth, curve, labelGR) else: ax.plot(depth, curve)这四条排查项是我实际绘图时最常遇到的翻车现场尤其是透明度遮挡和深度范围裁剪属于看起来不难但第一次画必错的点。5. 进阶技巧多井批量成图与地层对比图的坐标轴联动5.1 批量处理多口井用配置清单驱动绘图当手头有三口井以上的数据时靠一份脚本反复改路径的方式效率太低我一般会用一份 CSV 配置清单来驱动整个绘图流程。每一行就是一个井号记录它的 LAS 文件路径、录井表路径、画图颜色、显示顺序。脚本只需要读这份清单然后循环调用上面写过的resample_las_to_grid和plot_well_profile输出到独立的图片文件。import pandas as pd config_df pd.read_csv(wells_config.csv, encodinggbk) for _, row in config_df.iterrows(): las_path row[las_path] litho_path row[litho_path] well_name row[well_name] df_curve resample_las_to_grid(las_path, step0.1) df_litho pd.read_excel(litho_path) df_litho[litho_std] df_litho[litho_description].map(normalize_litho) df_litho merge_litho_intervals(df_litho) fig, ax plt.subplots(figsize(6, 12)) plot_well_profile(ax, df_curve, df_litho) plt.title(well_name) plt.savefig(fprofile_{well_name}.png, dpi300, bbox_inchestight) plt.close()encodinggbk这个参数不能省很多本地 Excel 导出 CSV 文件默认是 GBK 编码用默认 utf-8 读会被乱码卡住。bbox_inchestight保证保存的图片四周不留大片白边这个参数决定了打印出来是否美观。5.2 地层对比图多井深度轴联动单井剖面图之外geolog-app 最实用的场景是多井对比图。做法是横向排列若干口井各井都有自己的深度轴同时把同一个标志层的底界深度连成一条等时线。多井对比图的关键在于每个子图的 y 轴刻度必须一致否则地层对应关系在视觉上是扭曲的。fig, axes plt.subplots(1, N, figsize(4*N, 14), shareyTrue) for ax, well_name in zip(axes, well_names): df well_data[well_name] ax.plot(df[GR], df[DEPTH], colorblue, lw0.6) ax.invert_yaxis() ax.set_title(well_name)shareyTrue是参数设置的核心它让所有子图共享同一个深度轴刻度。如果没有这个参数各井因为深度范围不同会自动错开刻度对比图就从科学对比退化成形状比对这就失去了地层对比的意义。对比图上还可以额外加一个横向色带当标志层用ax.axhspan(1005, 1012, color#FFD700, alpha0.3)画一个半透明带子提示地层对比的参考意义。5.3 加入复核清单的工程习惯把所有中间输出的 CSV 文件、图件整理成一个固定目录结构并在跑完脚本后自动生成一个简单的复核报告。这个报告只列两件事哪些井输出成功哪些井出现了 UNKNOWN 岩性映射失败或深度不匹配警告。report_lines [] for well in well_list: unknown_count len(data[well][data[well][litho_std] UNKNOWN]) if unknown_count 0: report_lines.append(f{well}: {unknown_count} 段岩性未识别) if report_lines: with open(check_report.txt, w) as f: f.write(\n.join(report_lines))这个习惯非常有效它把问题从画图之后肉眼看前移到跑完脚本顺手看报告。每次拿到一批新井数据我都强制自己走一遍清洗、成图、看报告这个流程能省掉不少熬夜调图的后悔药希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑