简介:面向石油勘探与测井数据处理场景,该工具用于将斯伦贝谢公司专有的WIS格式转换为行业标准LAS 2.0文件,可帮助地质工程师、测井解释人员和软件开发者在不同系统间顺畅共享与分析数据。压缩包共36个文件、7.58MB,内部为完整VC++工程,既包含6个头文件与5个C++源文件,便于阅读解析逻辑,也包含编译生成的exe程序、obj与pdb等调试中间文件,以及两个txt说明文档,可直接运行也可二次开发。转换过程覆盖WIS文件分段解析、数据字段向CURVE/PARAMETER段映射、单位与精度归一化、LAS 2.0结构生成和结果校验等关键环节;资源还附带TEST.wis测试样例,方便验证转换器效果。目前已有1054人学习,适合需要实现或定制测井数据格式转换工具的中高级技术人员。
1. 项目背景:WIS和LAS,为什么总有人在倒腾这两种格式
做测井解释和地质工作的人,几乎每天都要跟LAS文件打交道。LAS(Log ASCII Standard)是测井行业通用的标准文本格式,从上世纪90年代开始就被各大软件和仪器厂商支持,几乎所有商业化解释软件——不管是Petrel、Techlog还是国内的Forward、Lead——都能直接拖进去用。可以说,LAS就是测井数据交换的“普通话”。
但你翻翻手头的旧资料,尤其是一些老井、老区块的数据备份,很容易见到后缀为.WIS的文件。这玩意儿的来历比较复杂,通常是一些早期测井解释软件或特定仪器厂家自定义的存储格式,有的基于文本、有的则是二进制结构,字段含义、曲线排列、深度规则各个软件还不太一样。问题就来了:你想把WIS文件里的数据导入主流解释平台,或者把老井数据重新处理一遍,但软件根本不认这个格式。
我最初上手这个需求,就是帮朋友整理一批老井资料。十几口井的WIS文件,全是2000年前后建井时存下来的,数据本身没问题,但新软件打不开。用文本编辑器硬看,二进制部分全是乱码;网上找转换工具,找来找去不是收费就是格式支持不全。磨了两天,最后决定自己写一个转换器,把WIS完整地转成LAS 2.0,让数据能在主流软件里流畅跑起来。这篇文章就把完整思路、格式要点、踩过的坑和可用的代码骨架都整理出来,给同样被数据格式折腾过的人一个参照。
这个项目不需要特别高深的技术背景,核心就三件事:搞懂WIS格式的底层结构、清楚LAS格式的写入规范、把两者的字段映射关系做扎实。另外如果你是自己写代码,Python加正则表达式和基本文件读写,一两天就能出一个可用的版本。适合的人群比较宽泛:测井解释工程师、地质数据管理岗、油藏工程师,以及所有被老数据格式卡住过的人。
2. LAS格式先吃透:转换的终点站必须门儿清
2.1 LAS 2.0的骨架长什么样
写转换器之前,得先明确目标格式的结构。LAS文件本质上是一个带标记的ASCII文本文件,用“~”符号(波浪线)分节定义不同内容。版本的差异主要在节的数量和内容上——LAS 2.0是最经典最通用的版本,绝大多数测井软件都支持,所以转换成2.0是兼容性最好的选择。
一个标准的LAS 2.0文件由下面几节组成:
~V:版本信息。记录VERS.(版本号)和WRAP.(行宽是否折行),比如:~VERSION INFORMATION VERS. 2.0: WRAP. NO:~W:井信息。记录井名、井号、位置、油田、国家、测井日期等元数据。每条记录由关键词.开头,后面跟数据值,再跟单位和对该字段的描述。~WELL INFORMATION WELL. WELL_A-01: FIELD. EXAMPLE_FIELD: COMP. LOGGING COMPANY:~C:曲线信息。定义每条曲线的名字、单位、曲线描述,以及ASCII数据区对应的列位置。比如深度曲线是第一个数据列,然后依次是GR、RT等测井曲线。~CURVE INFORMATION DEPT. M : 1 DEPTH GR. GAPI : 2 GAMMA RAY RT. OHMM : 3 RESISTIVITY~A:ASCII数据区,实际测井数据。按~C定义的顺序,以空格或Tab分隔每一列,逐行排列。深度值通常从浅到深递增。
如果文件里还有~P(参数信息)和~O(其他信息)节,LAS 2.0也支持,但它们不是必需的,我的转换器里先不写,后续有需要再加。
这里有个细节值得注意:LAS 2.0对井信息字段的格式要求并不严格,不同软件解析时对冒号位置、空格数量很挑剔。实测下来,关键词.要顶格写,值后面要跟冒号和空格,注释部分随意,但别把数字和单位搞混。很多转换工具转出来的LAS打不开,问题就出在井信息那一节格式不规范。
2.2 版本选型:为什么优先选LAS 2.0而不是3.0
LAS 3.0在结构上引入了~P参数节、~O其他信息节,还允许更灵活的曲线定义,但带来的兼容性问题也不少。老版本的Petrel能读2.0,等到3.0出来后才逐步支持;某些国产软件对3.0的解析更是时好时坏。在“数据要长期保存、跨平台使用”这个场景下,2.0是最稳妥的。
另外还有一个重要原因:WIS格式覆盖面广,但年代普遍偏老,里面的曲线信息、井头信息往往不够完整,强行映射到LAS 3.0的复杂模型中反而容易出现信息错位。LAS 2.0的结构足够简单,数据区定义清晰,对WIS这种“老数据”是天然的适配。
3. WIS格式解析:最难啃的骨头在哪
3.1 别指望一种方法搞定所有WIS文件
这是整个项目里最需要重视的一点:WIS不是一个统一的标准格式,它更像一个“家族”。不同来源的WIS文件,内部结构可能完全不同。有些是纯文本,用逗号或空格分隔,井头信息写在前面,数据区从某一行开始;有些是半二进制结构,头部含井名、曲线名等字符串信息,数据部分用二进制浮点数存储;还有些干脆就是厂商私有导出格式,中间夹杂大量无关的系统信息。
所以在动手写转换器之前,第一步永远是“探路”:用文本编辑器打开WIS文件,看前几百个字节的内容。如果能看到可读的英文单词和数字,大概率是文本类;如果全是乱码但有规律,那就是二进制类。这个判断决定了后续的解析策略。
我的做法是专门写了一个wis_detect函数,读取文件头部256字节,统计可打印字符占比。可打印字符超过60%就按文本模式解析,否则走二进制模式。这个比例阈值是经验值,实测下来比较可靠。
3.2 文本型WIS的解析套路
文本型WIS解析起来相对轻松。关键是要找对三个东西:
- 井头信息标记:通常是
Well、WELL、井名等关键词,位置一般在文件最前几行; - 曲线定义标记:比如
Curves、Channels、曲线等,后面跟曲线名和单位; - 数据区起始标记:比如
Data、~A、DATA START等,从这里开始每一行就是一条深度记录。
定位到数据区后,用正则表达式或者split按空白符拆列,就能得到每一列的数据。注意深度可能不是第一列,需要通过曲线定义块来确认。
3.3 二进制型WIS的解析策略
二进制型WIS才是真正的麻烦。通常结构是:头部一段固定长度的元数据(井名、曲线数、起始深度、结束深度、采样间隔等),后面跟着按列存储或按行存储的浮点数数组。
解析这类文件的思路是:
- 找出元数据区的准确偏移:一般可以通过搜索
DEPT、DEPTH等关键词,找到曲线定义位置; - 确认数据区的起始偏移:根据元数据里的长度字段推算,或者直接搜索大量连续4字节浮点数序列;
- 确定浮点数的字节序:是Big Endian还是Little Endian,这个判断可能决定读出来的数据是一堆天文数字还是正常的测井值。
我的经验是,如果元数据里没有明确写出字节序,就先按Little Endian读一遍,再做值域检查——正常的测井数据深度在0到10000之间、GR在0到500之间,如果读出来的值有上千万甚至乱码,就换字节序。这个方法屡试不爽。
4. 转换器核心实现:写代码的具体思路
4.1 整体架构设计
转换器用Python写,原因很简单:文件处理方便、正则表达式强大、跨平台。整个工具分成四个模块:
wis_reader.py:负责识别WIS类型并解析元数据;las_writer.py:负责生成LAS 2.0格式文件;mapper.py:维护曲线名映射表和单位换算逻辑;main.py:命令行入口,控制批量转换流程。
这个分层是为了后期好维护。比如遇到一个新变种的WIS文件,通常只需要改wis_reader.py里的解析逻辑,映射和写入部分完全不用动。
4.2 关键代码:LAS写入模板
LAS写入是转换器的“最后一公里”,格式必须严谨。我自己维护了一套经过多个软件验证的写入模板,核心逻辑如下:
def write_las(filepath, well_info, curves_data): """ curves_data: list of dict, 每个元素包含 name, unit, desc, values """ depth = curves_data[0]['values'] n_samples = len(depth) n_curves = len(curves_data) with open(filepath, 'w', encoding='ascii', errors='replace') as f: # ~V 版本信息 f.write("~VERSION INFORMATION\n") f.write(" VERS. 2.0:\n") f.write(" WRAP. NO:\n") # ~W 井信息 f.write("~WELL INFORMATION\n") f.write(" WELL. {}:\n".format(well_info.get('well', 'UNKNOWN'))) f.write(" FIELD. {}:\n".format(well_info.get('field', 'UNKNOWN'))) f.write(" LOC. {}:\n".format(well_info.get('location', ''))) # ~C 曲线信息 f.write("~CURVE INFORMATION\n") for i, curve in enumerate(curves_data, start=1): f.write(" {}. {} : {} {}\n".format( curve['name'].ljust(8), curve['unit'].ljust(5), i, curve['desc'] )) # ~A ASCII数据 f.write("~A\n") for row in range(n_samples): line_parts = [] for curve in curves_data: value = curve['values'][row] line_parts.append(format_value(value)) f.write(" ".join(line_parts) + "\n")这里有几个细节:写入文件时用ascii编码并将无法编码的字符替换掉,避免中文井名导致编码崩溃;每个曲线名用ljust做左对齐,保证列格式整齐;format_value函数统一控制小数位数。
4.3 数值格式化:精度和空值的拿捏
数值格式化直接影响下游软件识别的准确度。常见的坑有两个:
坑一是深度精度丢失。如果你把深度的有效数字写得太多,文件会变大;写太少又会导致深度曲线有台阶。我的做法是深度列固定保留4位小数(或根据原始数据精度自适应),曲线值保留3位小数。这个精度对大多数测井曲线足够,文件体积也不算大。
坑二是空值(NULL值)的处理。LAS 2.0没有强制规定空值符号,但默认约定是-999.25,绝大多数软件都能识别。转换时遇到的空值(WIS里可能是NaN、-9999、或者一串星号)需要统一替换成-999.25。这里要注意,不能直接把所有小于某个阈值的数都当空值处理——电阻率曲线在某些地层本来就是低值(比如泥岩层段电阻率可低至1欧姆米以下),误判会把有效数据变成空白。
5. 实操过程:从WIS到LAS的完整流转
5.1 第一步:摸清WIS文件的“脾气”
拿到一个WIS文件,不要急着写转换器,先做人工侦察。我一般按三步走:
- 用UltraEdit或VS Code以十六进制模式打开文件;
- 看头部256字节,抄下可读关键词和可能的偏移位置;
- 观察数据区的前20行,确认数据排列方式。
举个例子,我处理过的一个WIS文件,头部是这种结构:
WIS-LOG 1.0 WELL: 江X5-3 FIELD: XX构造 CURVES: DEPT(0), GR(1), RT(2) DATA START: 1750.000, 65.230, 12.500 1750.125, 65.410, 12.600这就是非常典型的文本型WIS,半天就能解析完。另一个文件则是二进制,头部有56字节的固定结构,前4字节存文件标识,接着4字节是浮点数个数,第9到24字节是井名ASCII字符串,其余是数据。遇到这种文件,就只能用十六进制编辑器手工确认偏移,然后写专门的解析函数。
我的经验是:第一次处理一种新的WIS变种时,至少预留半天的时间做格式侦察。格式搞清楚了,写代码只需要一两个小时;格式没搞清楚,写一天代码也是白搭。
5.2 第二步:配置映射表
WIS里的曲线名五花八门,什么“自然伽马”“GR”“GAMMA”“伽马曲线”都可能是同一条曲线。转换时能不能正确识别这些别名,直接决定转换质量。我的做法是维护一份映射表:
| WIS曲线名 | LAS标准名 | 单位换算 | 说明 |
|---|---|---|---|
| GR / GAMMA / 自然伽马 | GR | GAPI,原值直写 | 自然伽马 |
| RT / RES / 电阻率 | RT | OHMM,原值直写 | 电阻率 |
| SP / 自然电位 | SP | MV,原值直写 | 自然电位 |
| AC / DT / 声波时差 | AC | US/M,原值直写 | 声波时差 |
| DEN / 密度 | DEN | G/CC,原值直写 | 体积密度 |
| CN / 中子 | CN | %, 若原始值为小数则乘100 | 中子孔隙度 |
映射表要用JSON或YAML单独存放,方便后期增补。遇到表里没有的曲线名,工具默认生成一个规范化名字(去掉特殊字符、转大写),并在转换报告里打上“未识别曲线”的警告。
5.3 第三步:转换的二次校验
转换完成后一定要做两件事:重新读回LAS数据、和原始WIS做数据一致性对比。我用的是数据点级对比:抽取10个随机深度,比较深度值完全一致、曲线值绝对误差小于0.001。
这一步极其重要。我见过有同事用现成的转换工具批量转了一百多个文件,结果有个井的LAS深度从2000米开始,比实际偏了30米——原因是原始WIS的起始深度是井口海拔换算值,需要一个补偿系数。如果没有对比校验,这种错误会一路用到底,到解释阶段才发现,返工成本就大了。
6. 常见问题与排查实录
6.1 转换后LAS在软件里打不开
这是最常遇到的问题。多数原因是LAS文件的~V节格式不对,或者~C节的列号定义和数据区实际列数不一致。
排查步骤:
- 先用文本编辑器打开生成的LAS文件,人工核对
~V、~W、~C、~A四节是否齐全; - 数一数
~C节定义了几条曲线,再看~A数据行每行是几列,两边必须一致; - 检查文件末尾是否有空行或乱码字符,部分软件对此很敏感。
如果前两项都没问题,那就可能是软件的特殊解析要求。比如Petrel对LAS井名里的特殊字符(空格、斜杠)很挑剔,需要做转义处理。
6.2 深度不连续或乱跳
转换后的LAS如果相邻数据点的深度差忽大忽小,通常是原始WIS数据里有“坏道”(数据采集时的设备故障段)。有的坏道会记录成一致深度重复好几行,有的则是深度倒退。
处理策略是:在数据写入前做一个深度单调性检查,发现深度倒退或重复超过3次就在报告中标记该井段,并保留原始数据不动,只在LAS里标注为警告。千万不要自作主张把数据删掉——有时候那几段“坏数据”在后续处理中反而是有用的原始记录。
6.3 曲线值整体偏移或者像个位数错位
这大概率是字节序识别错误。我在3.3节提到过,如果二进制模式读出的值超出正常范围,就要尝试切换字节序。还有一个可能原因是WIS数据区里有8字节的Double类型,而你按4字节Float去读了——这样每个数值都会读错一位。
判断方法是算两列数据的自相关性:如果相邻两个“认为的Float”数值构成的规律性很强(比如总是第一列是小数、第二列是大数),而整体不符合深度和曲线的对应关系,就值得怀疑是类型读错了。
6.4 中文井头信息写入后变成乱码
LAS的标准存储方式推荐用ASCII编码,但中文井名无法用ASCII表示。实测下来最好的做法是:在~W节的井名字段中直接用中文字符,但要指定文件编码为GBK或UTF-8,并保证读取软件支持。绝大多数现代软件(Petrel、Techlog、Forward)都能正常读取UTF-8编码的LAS文件,但老版本软件对UTF-8支持不佳,可能显示成乱码或直接报错。
如果兼容性要求极严(比如要发给外部合作方,不确定对方用什么软件),那就把中文井名改成拼音或井号代号,保证ASCII纯净。这是最保守但绝对稳妥的方案。
6.5 批量转换时中途报错
批量转换几十上百个WIS文件时,最怕某个文件格式特殊导致程序崩溃。我的做法是:单个文件转换包在try/except里,出错了记录下来、继续下一个文件,最后生成一份总的转换报告。报告内容包括每个文件是“成功”“警告”“失败”三种状态,失败的原因(格式不支持、数据区为空、曲线定义缺失)单列一行。这样即便有局部失败,也不影响整体进度。
7. 几点实操体会
这个转换器做下来,我最大的感受是:格式转换这个事情的难点根本不在“写代码”,而在“摸清格式的脾气”。WIS文件就像一箱从不同地方寄来的旧物件,每件的外包装都不一样,里面装的可能是好东西也可能是杂碎。你有耐心一个个拆、一个个整理,才可能还原出原本有价值的数据。
另外一个小建议是,转换完成的LAS文件最好归档在一个独立的目录里,不要覆盖原始WIS。数据备份习惯看似麻烦,但在行业里待久了就会明白,原始数据永远比处理后的数据值钱,因为你未来永远不知道哪种软件又会要求你换成别的格式。留好原始档案,万一时过境迁,还能再转一次。
最后再说一个实用技巧:转换器做完以后,建议写一个简单的“冒烟测试”脚本,随机生成一小段符合LAS规范的测试数据,再走一遍自己的转换流程,确保核心功能没有在后续代码迭代中被改坏。工具这东西,用起来顺手是一回事,长期靠得住是另一回事。希望这份经验能帮你少踩几个坑。
本文还有配套的精品资源,点击获取