tardigrade1001/ivex
GitHub: tardigrade1001/ivex
一个将Keysight B2900系列源表专有数据文件批量转换为CSV和图表的Python工具,解决原装软件不可用时的数据访问问题。
Stars: 0 | Forks: 0
# ivex
在任意个人电脑上,无需原装B291x实用软件,即可将Keysight B2900系列源表的专有 `.QIVD` 二进制文件转换为纯CSV(以及快速预览的PNG图表)。
| 单次扫描 (`.QIVD`) | 三次扫描叠加 |
|:---:|:---:|
|  |  |
| 恒流偏压下气体暴露时的电压弛豫 | 同一样品三次运行的叠加图 |
## 此工具解决的问题
Keysight B2900系列源表默认以其专有的 `.QIVD` 二进制格式保存数据。该仪器软件 **B291x实用软件**("Quick I-V")在保存时提供一个选项框,可同时导出纯CSV文件。如果该选项框未勾选,生成的 `.QIVD` 文件在你的笔记本电脑上就无法被其他任何程序打开。
通常的变通方法是,物理上回到仪器所在的电脑,在B291x实用软件中逐一打开每个文件,并使用其"另存为CSV"命令。当仪器电脑正忙、在另一个房间,或者运行着一个你宁愿不去碰的Windows版本时,这就变得很麻烦。
此脚本可直接在任意个人电脑上读取这些二进制文件。将它指向你自己的笔记本电脑、共享驱动器或旧测量存档,它便能一次性转换整个文件树。
## 背景
目标仪器:
- **Keysight B2900系列精密源表** (B2901, B2902, B2911, B2912 及等效型号)。写入 `.QIVD` 文件。由 *B291x实用软件* 控制。
关于原始软件:
- 磁盘上的名称:**QuickIV** (B291x实用软件)。版本信息中的产品名称仅为"Quick IV"。
- 已验证的版本:**4.2.2045.2760** (安装程序构建于2017-07-27,由Keysight Technologies签名;程序内版权2011-2016)。
- 架构:32位Windows **.NET / WPF** 应用程序。拥有测量持久化功能的两个库是 `SamplingLib.dll`(采样模式)和 `SweepLib.dll`(扫描模式)。文件格式的魔数和反序列化错误字符串位于 `TestMain.dll` 中。程序内部的绘图使用 OxyPlot。安装程序是一个76 MB的InstallShield封装包 (`QuickIV-4-2-2045-2760.EXE`)。
- 硬依赖项:**Keysight IO Libraries Suite**(提供VISA驱动程序;其安装程序是一个单独的约260 MB的包)。没有它,QuickIV无法与仪器通信。
- 发行方式:不提供公共下载。获取安装程序的唯一途径是通过Keysight支持请求或从已拥有它的另一个实验室获取。
存在两种文件扩展名:
- `.qivd` 是 **Quick IV数据文件**(测量结果)。这是ivex转换的对象。
- `.qivm` 是 **Quick IV设置文件**(保存的测试方案:源模式、量程、合规值、采样数、时序)。它不包含测量数据,不在ivex的处理范围内。
`.qivm` 文件内部的样子(通过对测试存档中13个文件的抽检):
- 与 `.qivd` 具有相同的魔数头和整体TLV结构。
- 所有抽样的 `.qivm` 文件大小固定为9364字节。模式是严格固定的:相同的参数槽位存在于每个文件中,值被原地写入。
- 列块内的tag-7 "data" 记录长度为零(无捕获的样本)。
- 可直接读取的纯文本字段:已配置仪器的VISA地址和序列号、测试模式(`Sampling`、`Sweepd` 或 `Source & Sampling`),以及为测试注册的通道名称。
- 采集参数本身(源电平、合规值、量程、采样数、时序)存储在一个固定索引的参数表中,二进制文件中没有嵌入字段名称。索引到标签的映射存在于 `TestMain.dll` 的反序列化代码中。提取它需要使用.NET反编译器(如ILSpy或dnSpy)反编译该DLL。此工作尚未在此完成。
`.QIVD` 格式没有公开文档。获取数据的唯一内置方法是程序的"另存为CSV"命令,一次只能处理一个文件。
此工具逆向工程了该格式,并可一次性转换整个文件树。
## 它的功能
当指向一个文件夹时,`ivex.py`:
1. 递归查找其下的所有 `.QIVD` 文件。
2. 对于每个文件:
- 解析二进制头(仪器VISA地址、列元数据)。
- 提取记录的每个通道:电压、电流、电阻、功率、时间和状态。
- 在原始文件旁写入一个CSV(相同基础名,`.csv` 扩展名)。
- 在其旁写入一个单次扫描PNG。该图通过检查实际变化的通道来选择绘制电压或电流。另一个通道是被源出的且保持恒定,绘制它只会显示ADC噪声。
3. 在 `plots/` 文件夹中生成一个叠加PNG,在一个坐标轴上显示所有扫描,以便快速跨样品比较。
4. 将所有内容记录到根目录的 `ivex.log` 中。
## 快速开始
要求:Python 3.8+ 和 `matplotlib`。
```
pip install matplotlib
```
将 `ivex.py` 放入包含你数据的文件夹(子目录会自动遍历),然后:
```
python ivex.py
```
或者将其指向一个特定文件夹:
```
python ivex.py "C:/path/to/data"
```
仓库包含一个 `samples/` 文件夹,其中包含三个 `.QIVD` 文件,加上其中两个的原始Keysight CSV导出(作为 `.qivd.csv`),因此你可以开箱即用,与基准真值进行比较:
```
python ivex.py samples
```
输出看起来像:
```
OK iv_demo_1.qivd: 1720 pts x 6 cols -> iv_demo_1.csv
OK iv_demo_2.qivd: 1786 pts x 6 cols -> iv_demo_2.csv
OK iv_demo_3.qivd: 1986 pts x 6 cols -> iv_demo_3.csv
-> plots/_overlay.png
Done. 3 succeeded, 0 failed. Log: ivex.log
```
重新运行脚本会重新生成所有内容。CSV和PNG输出可以随时删除,并在下次运行时重建。
## 输出格式
每个CSV都有一个简短的注释头,后跟数据:
```
# 来源:Keysight B291x SMU (B291x Utility Software .qivd)
# 样本:iv_demo_1
# 仪器 VISA:USB0::2391::52504::MY00000000::0::INSTR
# 数据点数:1720
# 采样步长:0.1 s 时长:171.9 s
# 数据列:CH1 电压, CH1 电流, CH1 电阻, CH1 功率, CH1 时间, CH1 状态
voltage_V,current_A,resistance_ohm,power_W,time_s,state
0.558983,0.1,5.58983,0.0558983,3,4230
0.558977,0.1,5.58977,0.0558977,3.1,4230
0.558972,0.1,5.58972,0.0558972,3.2,4230
...
```
只要二进制文件中存在,所有六个通道都会被输出,无论用户在B291x实用软件的CSV导出对话框中勾选了哪些列。二进制文件总是存储所有内容。
## 准确性
已通过来自真实实验室实验的17个基准真值Keysight CSV导出进行验证。观察到的最差相对误差:**~5 × 10⁻⁶**。这正是Keysight自身工程计数法CSV的5位有效数字精度。该脚本在显示精度上与Keysight的导出位对应位完全一致,并将底层二进制文件的完整float64精度带入输出。
测试覆盖范围跨越从1,000到10,000点的扫描长度,以及实际中见过的每种导出模式(5列、6列和7列导出)。
## 格式工作原理
如果有人想扩展此工具或编写自己的解析器,这是重要的部分。所有数据均为小端序。
```
offset field bytes
0 magic "'B291x Utility Software Save File Header" 41
... header strings: var
VISA address (e.g. USB0::2391::52504::MY...::INSTR)
mode strings ("Sampling", "Sweepd", ...)
... a sequence of TLV records, each:
uint32 tag
uint32 length
(length) bytes payload
```
在主体内,整个文件是一个**TLV(标签-长度-值)记录**流。我们需要识别的标签如下:
| 标签 | 含义 |
|----:|---------|
| `1` | ASCII列名(例如 `CH1 Voltage 1`) |
| `4`, `5`, `6` | 4字节uint32元数据。从 `TestMain.dll` 提取的字符串将其命名为 `COMMON_REV_KEY`(格式版本)、`COMMON_COUNT_KEY`(记录数)和整型与双精度类型的标记。 |
| `7` | 该列的数据数组(当类型标记为双精度时,`length / 8` 个小端序float64值)。 |
上述标签名称已与 `TestMain.dll` 中编译的反序列化错误消息(`File format error (COMMON_REV_KEY)`, `File format error (COMMON_COUNT_KEY)`, `File format error (invalid integer data length)`, `File format error (invalid double data length)`)进行交叉核对。文件格式还在程序中携带其自身的COM CLSID:`DB8CBF1C-D6D3-11D4-AA51-00A024EE30BD`。
每个测量列的布局如下:
```
tag=1 | name_len | "CH1 Voltage 1"
tag=4 | 4 |
tag=5 | 4 |
tag=6 | 4 | (data-type marker, 1 = float64)
tag=7 | n_bytes |
```
B291x实用软件写入的标准列集为 `CH1 Voltage`, `CH1 Current`, `CH1 Resistance`, `CH1 Power`, `CH1 Time`, `CH1 State`。有些文件还包含一个 `CH1 Source` 块,大多数文件在末尾附近追加了每个列的副本,用于程序的"保存视图"缓存。解析器保留每个名称的第一个出现。
关键事实:
- 数据按**采集顺序**存储(低时间索引到高时间索引)。无需反转。
- 所有值均为**小端序IEEE-754 float64**。
- B291x实用软件的CSV导出将每个文件填充到固定的最大行数(通常为80,000),尾部为空行。二进制文件仅存储实际捕获的点数。`ivex.py` 的CSV头中的 `Points` 字段反映了真实的捕获数量。
- `CH1 State` 通道存储仪器状态寄存器,在单次运行中通常是常数。为完整性而输出它。
## 稳健性
在为B2900系列 + B291x实用软件系统构建此工具的约束范围内,它可以处理:
- 任意的样品名称和文件名(包括空格、逗号和括号)。
- 电压源模式和电流源模式(每个文件的图会自动检测哪个通道是被测的)。
- 从几百到数万个点的扫描长度。
- 除了标准六个通道外,还包含"Source"块的文件。
- 包含尾部"保存视图"重复列块的文件。
会导致它失败的情况(以及错误消息将显示的内容):
- 具有不同魔数头的不同仪器或主要软件版本:`unrecognized magic bytes (not a B2900 .qivd file)`。
- 不包含可识别的 `CH ` 列记录的文件:`no column data blocks found`。
每个失败都会被标记上阶段(`[parse]`、`[csv]`、`[png]`)和人类可读的原因。意外的异常也会输出Python回溯信息。所有内容都会同时发送到标准输出和 `ivex.log`。
## 项目布局
对数据文件夹的一次典型运行最终看起来像这样:
```
your-data-folder/
├── ivex.py
├── ivex.log
├── plots/
│ └── _overlay.png
├── 2025-09-03/
│ ├── sample1.qivd
│ ├── sample1.csv # generated
│ ├── sample1.png # generated
│ └── ...
└── 2025-09-04/
├── sample2.qivd
├── sample2.csv # generated
├── sample2.png # generated
└── ...
```
脚本不关心你的文件夹如何组织。它只是遍历树。
## 自定义
`ivex.py` 顶部附近有几个可调节项:
- `PLOT_COLOR`:单次扫描图的颜色。默认值 `#e91e63`(粉色)。叠加图使用matplotlib的默认颜色循环,以便区分每条轨迹。
- `STANDARD_COLUMNS`:写入CSV的列顺序。任何在给定文件中缺失的列都会被静默跳过。
## 贡献
如果你有此工具无法处理的相关Keysight源表的 `.QIVD` 文件,获得支持的最快方法是将样本文件加上来自原始程序的匹配CSV导出放到issue中。CSV提供了验证任何新格式变体所需的基准真值。
## 背后的故事
这始于一个工作流程的挫败感。我们实验室的Keysight B2900系列源表默认以其专有的 `.QIVD` 二进制格式保存数据。唯一能读取它的程序是B291x实用软件("Quick I-V"),该软件在网上任何地方都无法下载。要获得一个可工作的副本,你必须通过Keysight支持或一个已经拥有它的同事找到安装程序,然后同时安装Keysight IO Libraries Suite以便VISA驱动程序存在,再然后通过USB将程序指向仪器。在打开单个文件之前就有许多阻力。
即使程序运行了,导出步骤也是手动且缓慢的。打开Quick I-V,浏览文件浏览器,选择一个 `.QIVD` 文件,打开它,转到另存为,选择CSV,勾选你想要的列复选框,点击保存。如果你忘记了一列,你就要回去重新执行整个序列。乘以一个包含五十个测量的文件夹,一个下午就消失了。
转折点出现在当一个旧数据文件夹需要重新分析,而仪器电脑被占用时。在实验室笔记本电脑上从头破解文件格式看起来比排队等机器更容易。结果证明这是一个单下午的工作量。
技术步骤如下:
1. **十六进制转储一个 `.QIVD` 文件。** 它以ASCII魔数 `'B291x Utility Software Save File Header` 开头,后跟一个USB VISA资源字符串 (`USB0::2391::52504::MY...::INSTR`)。供应商ID `2391` 是Keysight的,这本身就确定了仪器系列。之后是可读的标记,如 `Sampling`, `Sweepd`, `Voltage`, `CH1 Voltage`。高层布局已经可见。
2. **获取CSV基准真值。** 其中一个数据文件夹有两个 `.qivd` 文件对应的 `.csv` 导出。CSV中的第一个数据行是 `V = 558.983 mV`, `I = 100 mA`, `t = 3.0 s`。这给出了一个明确的搜索值。
3. **在二进制文件中定位第一个样本。** 扫描每个字节偏移量以查找等于 `0.558983` 的float64,恰好在偏移量 `0x39A` 处找到一个命中。对 `0.1`(电流)做同样的操作命中在 `0x3AB4`,`3.0`(时间)命中在 `0xDFFC`。命中点之间的间隔各约为14100字节,大小相似。文件是**列主序**的:每个通道的完整数组连续存储,然后是下一个通道的完整数组,依此类推。
4. **找到数组长度标记。** 每个列的第一个样本之前的四个字节总是 `C0 35 00 00`。这解码为 `0x35C0 = 13760`,即 `1720 × 8`。CSV有1720行填充数据。因此,每个列头都携带一个紧跟在float64数组之前的 `uint32` 字节计数。
5. **发现TLV模式。** 从每个数组长度标记回看40字节,揭示了一个重复的记录结构:`04 00 00 00 04 00 00 00 <4字节值>`,然后是标签 `05` 的相同结构,然后是 `06`,然后是 `07`(标记数据数组本身)。整个文件主体是一个TLV(标签-长度-值)记录流:
- 标签 `1` 是ASCII格式的列名(例如 `CH1 Voltage 1`)
- 标签 `4`, `5`, `6` 是小型uint32元数据
- 标签 `7` 携带float64数据数组
6. **构建一个遍历器**,扫描以 `CH` 开头的tag-1记录,然后前进到下一个tag-7记录并读取其 `length/8` 个双精度数。保留每个列名的第一次出现,因此解析器忽略某些文件追加的重复"保存视图"块。
7. **端到端验证。** 对实验室存档中的所有23个 `.qivd` / `.csv` 对运行了解析器。其中17个是标准的Keysight导出,在CSV的5位有效数字显示精度上位对应位完全匹配(最差相对误差4.95e-6)。其余6个是用户编辑的2列派生CSV,因此被排除在基准真值集之外。
过程中一个意外发现:B291x实用软件的CSV导出将每个文件填充到固定的80,000行表格,对于短运行,大多数行是空的。二进制文件保存了实际的捕获计数(例如1720)且没有其他内容。直接读取二进制文件可以得到更紧凑、更干净的输出,没有尾部的空行。
## 致谢
想法、需求、实验室知识和方向来自于我。逆向工程和Python代码是我与[Claude](https://claude.ai)作为AI编码助手在一个会话中协作产生的。每一步的验证都依赖于我从B291x实用软件生成的CSV导出。
## 许可证
MIT。使用它,分叉它,分享它。
标签:B2900, Keysight, PNG绘图, QIVD, 二进制文件解析, 仪器软件替代, 实验数据处理, 批处理工具, 数据转换, 文件格式, 测试测量, 源测量单元, 科学计算, 软件工具, 逆向工具