完善 LiDAR–双天线 RTK 手眼标定仓库:补充旧式及多传感器数据导出、small_gicp/Open3D GICP 标定、结果复核与3D可视化流程,并整理三批数据和标定结果说明。

This commit is contained in:
lichun.qu
2026-07-23 18:55:50 +08:00
parent 6b7844a977
commit f72fcb71cc
91 changed files with 39561 additions and 503 deletions
+194
View File
@@ -0,0 +1,194 @@
木兰宽松许可证,第2版
木兰宽松许可证,第2版
2020年1月 http://license.coscl.org.cn/MulanPSL2
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
0. 定义
“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
“法人实体” 是指提交贡献的机构及其“关联实体”。
“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是
指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
1. 授予版权许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可
以复制、使用、修改、分发其“贡献”,不论修改与否。
2. 授予专利许可
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定
撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡
献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软
件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“
关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或
其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权
行动之日终止。
3. 无商标许可
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定
的声明义务而必须使用除外。
4. 分发限制
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“
本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
5. 免责声明与责任限制
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对
任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于
何种法律理论,即使其曾被建议有此种损失的可能性。
6. 语言
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文
版为准。
条款结束
如何将木兰宽松许可证,第2版,应用到您的软件
如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
3, 请将如下声明文本放入每个源文件的头部注释中。
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan
PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
Mulan Permissive Software LicenseVersion 2
Mulan Permissive Software LicenseVersion 2 (Mulan PSL v2)
January 2020 http://license.coscl.org.cn/MulanPSL2
Your reproduction, use, modification and distribution of the Software shall
be subject to Mulan PSL v2 (this License) with the following terms and
conditions:
0. Definition
Software means the program and related documents which are licensed under
this License and comprise all Contribution(s).
Contribution means the copyrightable work licensed by a particular
Contributor under this License.
Contributor means the Individual or Legal Entity who licenses its
copyrightable work under this License.
Legal Entity means the entity making a Contribution and all its
Affiliates.
Affiliates means entities that control, are controlled by, or are under
common control with the acting entity under this License, control means
direct or indirect ownership of at least fifty percent (50%) of the voting
power, capital or other securities of controlled or commonly controlled
entity.
1. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to you a perpetual, worldwide, royalty-free, non-exclusive,
irrevocable copyright license to reproduce, use, modify, or distribute its
Contribution, with modification or not.
2. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to you a perpetual, worldwide, royalty-free, non-exclusive,
irrevocable (except for revocation under this Section) patent license to
make, have made, use, offer for sale, sell, import or otherwise transfer its
Contribution, where such patent license is only limited to the patent claims
owned or controlled by such Contributor now or in future which will be
necessarily infringed by its Contribution alone, or by combination of the
Contribution with the Software to which the Contribution was contributed.
The patent license shall not apply to any modification of the Contribution,
and any other combination which includes the Contribution. If you or your
Affiliates directly or indirectly institute patent litigation (including a
cross claim or counterclaim in a litigation) or other patent enforcement
activities against any individual or entity by alleging that the Software or
any Contribution in it infringes patents, then any patent license granted to
you under this License for the Software shall terminate as of the date such
litigation or activity is filed or taken.
3. No Trademark License
No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements in section 4.
4. Distribution Restriction
You may distribute the Software in any medium with or without modification,
whether in source or executable forms, provided that you provide recipients
with a copy of this License and retain copyright, patent, trademark and
disclaimer statements in the Software.
5. Disclaimer of Warranty and Limitation of Liability
THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR
COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT
LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING
FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO
MATTER HOW ITS CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGES.
6. Language
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION
AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF
DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION
SHALL PREVAIL.
END OF THE TERMS AND CONDITIONS
How to Apply the Mulan Permissive Software LicenseVersion 2
(Mulan PSL v2) to Your Software
To apply the Mulan PSL v2 to your work, for easy identification by
recipients, you are suggested to complete following three steps:
i. Fill in the blanks in following statement, including insert your software
name, the year of the first publication of your software, and your name
identified as the copyright owner;
ii. Create a file named "LICENSE" which contains the whole context of this
License in the first directory of your software package;
iii. Attach the statement to the appropriate annotated syntax at the
beginning of each source file.
Copyright (c) [Year] [name of copyright holder]
[Software Name] is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan
PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
+1 -1
View File
@@ -33,7 +33,7 @@ norm = 7.9716 cm / 0.547109 deg
查看原结果:
```powershell
$Repo = "D:\Outdoor Ackerman Cart Sensor Adaptation\LiDAR_RTK_Calibration_Rigorous_20260721"
$Repo = "D:\你的代码目录\calibration"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\view_open3d_result.ps1" -PairIndex 0
```
+177 -350
View File
@@ -1,399 +1,226 @@
# 双天线 RTK—3D 激光雷达严谨手眼标定
# LiDAR双天线 RTK 手眼标定
本仓库是当前建议提交和继续维护的正式版本。第二批 38 站高频 RTK 用于主要求解;第一批 38 站稀疏 RTK 只作辅助复核。Open3D GICP 和 small_gicp 分别生成雷达运动 (B),两套独立结果及跨后端共识结果均保留。
实际部署只读取 [`results/final_extrinsic_deployment.json`](results/final_extrinsic_deployment.json)。算法原始求解、残差、Bootstrap 和筛选记录保留在其他结果文件中,不应与部署文件混用。
## 标定总体流程
1. **准备静止站点数据**:每站车辆静止,保存一帧原始雷达点云和该站 RTK;程序只使用雷达传感器坐标系下的 `points_raw`
2. **由 RTK 生成车体运动 (A)**:将双天线 RTK 的位置、水平航向、杆臂和 heading offset 转为车体位姿 (T_{WB_i}),再计算
\[
A_{ij}=T_{WB_i}^{-1}T_{WB_j}.
\]
3. **由点云配准生成雷达运动 (B)**Open3D GICP 和 small_gicp 分别把第 j 站点云配准到第 i 站,得到
\[
B_{ij}=T_{L_iL_j}.
\]
4. **先验证和筛选 B,再求 X**:使用对称留出点、重叠率、留出点 RMSE、海森矩阵、正反向一致性、多初值稳定性、旋转共轭不变量和闭环误差检查 B。这个阶段不读取待求外参 X,也不使用 AX 残差筛选 B。
5. **建立跨后端共识 B**:只保留 Open3D 与 small_gicp 对同一运动对的 B 相差不超过 `0.05 m / 0.50°` 的边;最终使用 Open3D Bsmall_gicp 作为独立一致性门控。
6. **求解手眼外参 X**:固定 A 和通过筛选的 B 后,求解
\[
A_{ij}X=XB_{ij},\qquad X=T_{body\leftarrow lidar}.
\]
使用 Huber 鲁棒最小二乘、12 个初值、地面平面约束和 100 次 Bootstrap。
7. **结果复核**:第二批共识对用于主要求解和整体残差统计;第一批重新独立生成 B,只作为稀疏 RTK 辅助复核;3D 可视化分别比较模式 3 的 GICP B 与模式 4 的 (X^{-1}AX)。
8. **形成部署结果**:第二批跨后端共识结果给出完整的 `[x,y,z,roll,pitch,yaw]`;部署 JSON 直接采用该算法结果。。
### 验证逻辑
顺序固定为:
本仓库提供一套可从原始 Medulla 记录复现的静态站点标定流程,求解三维激光雷达到后轮轴中心车体系的外参
```text
先生成 B
-> 用不依赖 X 的点云与几何指标筛 B
-> 用另一个 GICP 后端复核同一 B
-> 固定共识 B
-> 最后才求 X
-> AX 残差只用于结果评价,不反过来选择 B
X = T_body_lidar
```
代码证据
- [`code/refine_pairs.py`](code/refine_pairs.py) 不读取外参文件,只使用留出点、正反向和旋转共轭不变量等指标;
- [`code/cross_backend_filter.py`](code/cross_backend_filter.py) 只比较两个后端的 B,不读取 X;
- [`code/rigorous_calibration.py`](code/rigorous_calibration.py) 在 B 集合固定后才执行 `calibrate` 求 X
- 第一批辅助复核的 B 也重新独立生成,且不使用 AX 残差筛选。
但这些检查仍不能把 B 变成绝对真值:重复结构、动态物体或错误局部最优仍可能让两个后端同时出错,所以必须结合模式 3 点云质量、留出集指标和多运动对整体统计判断。
## 最终部署外参
坐标变换定义:
\[
X=T_{body\leftarrow lidar}
\]
即将原始雷达点从雷达坐标系变换到以后轮轴中心为原点、X 前/Y 左/Z 上的车体坐标系。
部署值:
- 平移 `[x,y,z] = [1.297760, -0.000067, 0.720498] m`
- RPY `[roll,pitch,yaw] = [-0.785151, 1.202661, -0.835510] deg`
- 唯一部署文件:[`results/final_extrinsic_deployment.json`](results/final_extrinsic_deployment.json)
该值来自第二批 39 对跨后端共识、完整地面平面约束和 Bootstrap 稳定性检查。
算法在完整地面平面模型下的原始结果为:
- 平移 `[1.297760, -0.000067, 0.720498] m`
- RPY `[-0.785151, 1.202661, -0.835510] deg`
- 文件:[`results/final_extrinsic_recommended.json`](results/final_extrinsic_recommended.json)
`final_extrinsic_recommended.json` 保留完整求解、残差和 Bootstrap 记录,用于复现和审计;`final_extrinsic_deployment.json` 是唯一部署入口。
共识结果统计:
- 第二批 39 对 AX RMS`0.07985 m / 0.96118°`
- 第一批 22 对辅助复核:`0.06067 m / 1.00298°`
- 结果摘要:[`results/final_summary.json`](results/final_summary.json)
两套后端分别求得:
| 后端 | 平移 `[x,y,z]` m | RPY deg | 第二批 AX RMS |
|---|---|---|---|
| Open3D GICP | `[1.297883,-0.003100,0.721789]` | `[-0.757207,1.146042,-0.879781]` | `0.1002 m / 1.0115°` |
| small_gicp | `[1.299630,-0.003565,0.721886]` | `[-0.787156,1.141456,-0.935757]` | `0.1187 m / 1.1351°` |
两后端单独求出的 X 相差 1.81 mm、0.063°。共识求解的 100 次 Bootstrap 标准差 `[x,y,z,roll,pitch,yaw]` 为:
约定 `T_A_B` 将 B 系坐标变换到 A 系。对任意站点 i、j
```text
[0.00322 m, 0.00333 m, 0.00169 m, 0.0980°, 0.0746°, 0.1189°]
A_ij = T_W_Bi^-1 T_W_Bj # RTK 给出的车体相对运动
B_ij = T_Li_Lj # GICP 给出的雷达相对运动
A_ij X = X B_ij
```
这里的 Z Bootstrap 只描述当前地面模型条件下的内部稳定性,不代表机械高度的绝对精度
当前部署建议仍采用 [results/01_previous_two_batches/final_extrinsic_deployment.json](results/01_previous_two_batches/final_extrinsic_deployment.json)。data4 是一次独立重算,结果与部署值相差约 `1.592 cm / 0.234°`,但自身 AX 残差更高,因此只作为候选和稳定性证据,不自动替换部署值
重要限制:旋转 RMS 仍约 1°,所以这不是厘米级绝对精度认证。远距离点云仍可能看到角度错层;Bootstrap 也不包含 RTK 参考点、航向偏置或杆臂定义错误等系统误差。
### `code/` 文件职责
## 标定总流程
`code/` 中的 Python 脚本按“生成 B → 独立筛选 B → 求解 X → 汇总/诊断”的顺序组织。通常应通过 `run/` 下的 PowerShell 脚本调度,而不是手工跳过中间筛选步骤。
| 文件 | 职责 | 主要输入 → 主要输出 |
|---|---|---|
| [`rigorous_calibration.py`](code/rigorous_calibration.py) | **主求解器**,实现坐标变换、Open3D GICP/small_gicp 配准、质量计算和手眼优化。`ground``points_raw` 做 RANSAC 地面平面;`pairs` 将 RTK 车体轨迹构造成 A、将点云配准构造成 B,并输出海森矩阵、留出点、正反向和多初值质量报告;`calibrate` 用 Huber 鲁棒最小二乘、地面约束、多初值和 Bootstrap 求 `T_body_lidar``validate` 只计算给定 X 在独立 B 集上的 AX 残差。 | prepared 的 `frames_all`、车体位姿 CSV、B/地面文件 → `.npz` B 集、质量 JSON/CSV、外参 JSON、辅助复核 JSON。 |
| [`refine_pairs.py`](code/refine_pairs.py) | **第二级 B 精筛器**。按留出点重叠率/RMSE、旋转共轭不变量、正反向一致性剔除差的 B;不读取 X,也不用 AX 残差选 B。 | 初始 B `.npz` + 配准质量 JSON → `*_refined.npz` + `*.refinement.json` 审计记录。 |
| [`cross_backend_filter.py`](code/cross_backend_filter.py) | **跨后端一致性门控**。只保留 Open3D 与 small_gicp 对同一运动对的 B 差异不超过默认 `0.05 m / 0.50°` 的边;最终 B 本体采用 Open3D,small_gicp 仅作为独立门控。 | 两套 refined B `.npz``consensus/B_batch*_consensus.npz` + `*.consensus.json`。 |
| [`finalize_consensus.py`](code/finalize_consensus.py) | **发布共识解**。读取共识 B 求得的外参、第一批辅助复核和两单后端外参,写出带选择依据的 `final_extrinsic_recommended.json``final_summary.json`。 | 共识外参/复核 JSON、两后端外参 JSON → 推荐外参和最终摘要 JSON。 |
| [`summarize_results.py`](code/summarize_results.py) | **单后端阶段汇总器**。比较 Open3D 与 small_gicp 的外参差异、第二批拟合残差和第一批辅助复核,生成 `comparison_summary.json`;完整流程随后由 `finalize_consensus.py` 以共识解覆盖推荐结果。 | 两单后端外参、质量、辅助复核 JSON → 对比摘要 JSON 与阶段性推荐 JSON。 |
| [`scan_extrinsic_sensitivity.py`](code/scan_extrinsic_sensitivity.py) | **局部灵敏度诊断器**。对 X 在车体系左乘小的 roll/pitch/yaw 修正,比较某一对与全部 B 对的残差、改善/变差数量、地面指标和 Z 可观性。只生成扫描 JSON/CSV**绝不改写 X**。 | B `.npz`、外参 JSON、可选地面 CSV → `diagnostics/*_scan.json/.csv`。 |
| [`visualize_pair_3d.py`](code/visualize_pair_3d.py) | **交互式 3D 点云核查器**。蓝色为目标站 i、橙色为源站 j;按键 1/2/3/4 分别查看原始、RTK 初值 A、GICP B、外参预测 `X^-1 A X`,并打印 `B^-1(X^-1AX)` 的 cm/deg 增量。可选按键 5 显示临时 RPY 修正,修正不写入文件。 | 站点点云、B `.npz`、外参 JSON → Open3D 交互窗口与终端增量。 |
## 坐标与公式
\[
A_{ij}=T_{WB_i}^{-1}T_{WB_j},\qquad
B_{ij}=T_{L_iL_j},\qquad
A_{ij}X=XB_{ij}
\]
`B_ij` 把第 j 站雷达点变换到第 i 站雷达坐标系。程序只读取 `points_raw`,禁止使用已经变到车体坐标系的点。
RTK 轨迹使用 `body_poses_rear_gga_raw_rear_to_front.csv`,对应后天线 GGA 位置和双天线后到前方向。
地面约束采用完整平面变换:
\[
n_B=R_Xn_L,\qquad r_h=d_L-n_B^Tt_X-h_{body}
\]
不再使用旧近似式 `d_L-(t_z+h_body)`
## 详细求解流程
1. 第二批高频 RTK 生成 A;第一批稀疏 RTK 不参与主要求解。
2. 不使用手量外参作初值:生成 B 时 (X_0=I),所以 (B_0=A);求 X 也从单位变换和随机初值开始。
3. 两后端均采用三级粗到细:体素 0.30/0.15/0.08 m,对应距离 1.20/0.50/0.25 m。
4. 逐对检查对称留出点、海森矩阵、正反向一致性、多初值稳定性和 B 三角闭环。
5. 使用重叠率、留出点 RMSE 和旋转共轭不变量筛选 B;不使用 AX 残差筛选。
6. 两后端分别求 X;随后使用跨后端 B 一致性得到最终 39 对共识集合。
7. 使用 Huber 鲁棒最小二乘、12 个随机初值、完整地面约束和 100 次 Bootstrap 求算法 X。
8. 第一批重新独立生成 B,仅作辅助复核,不参与第二批求解。
9. 使用共识算法完整解形成精简部署 JSON,不覆盖算法原始结果。
## 目录
```text
code/
rigorous_calibration.py
refine_pairs.py
cross_backend_filter.py
summarize_results.py
finalize_consensus.py
scan_extrinsic_sensitivity.py
visualize_pair_3d.py
run/
run_all.ps1
run_consensus_finish.ps1
run_sensitivity_scan.ps1
view_open3d_result.ps1
view_small_gicp_result.ps1
tools/
frontlidar_dlog_export.py # 从单个原始 dlog 导出 LiDAR NPZ 与 RTK sidecar
prepare_station_dataset.py # 从逐站导出结果构建标定所需 prepared 数据集
results/
common/
open3d_gicp/
small_gicp/
consensus/
diagnostics/
final_extrinsic_deployment.json # 实际部署只读取这个文件
final_extrinsic_recommended.json # 算法原始结果及详细诊断
final_summary.json
```mermaid
flowchart LR
raw["原始站点 dlog / RTK、IMU rscap"] --> export["分别解析并统一到时间轴"]
export --> assoc["按每个 LiDAR 帧关联 RTK/IMU,导出 NPZ"]
assoc --> prep["每站选一帧,构建 RTK 车体位姿 A"]
prep --> b1["small_gicp 求 B"]
prep --> b2["Open3D GICP 求 B"]
b1 --> gate["与 X 无关的质量筛选及双后端一致性"]
b2 --> gate
gate --> solve["AX=XB + 地面约束求 X"]
solve --> check["残差、bootstrap、条件数、跨批复核和 3D 可视化"]
```
## 从原始数据到最终外参
流程有两个原始数据入口:
下面是从云盘中的**原始 dlog**到最终 LiDAR—车体外参的完整流程。第 2、3 步由本仓库的 tools/ 脚本完成;第 4 步及之后由 run/ 调度。不要把云盘中的原始数据直接传给 run_all.ps1。
- 旧式数据:LiDAR 和 `GPS-POST-Z` 位于每个站点 dlog 中,使用 `export_legacy_stations.ps1`
- 新式多传感器数据:LiDAR 位于逐站 dlogRTK 与 IMU 是独立 `.rscap`,使用 `export_multisensor_stations.ps1`。处理顺序是统一时间轴、分别解析、按 LiDAR 帧关联、导出 NPZ。
```text
原始静止站点 dlog(两批)
→ 逐站导出 LiDAR 原始点云 + RTK 旁路表(NPZ)
→ 逐站质量筛选、选取静止帧、重建 RTK 车体位姿(prepared
→ Open3D / small_gicp 分别求 B
→ X 无关的 B 精筛与跨后端共识
→ AX=XB + 地面平面约束求 X
→ 第一批辅助复核、3D 点云核查、确认部署 JSON
```
IMU 会在新式数据中原样解析并随 LiDAR 帧关联保存,但当前 LiDAR–RTK 外参求解不使用 IMU,也不做运动畸变校正,因为每一站采集点云时车辆静止。IMU 外参应使用单独的激励数据和专用标定流程求解。
### 1. 整理原始数据与标定元数据
## 三批数据的角色
每一个静止站点目录必须完整保留 LiDAR DObject 记录、对应 `.dorec` 文件和 RTK 日志。dlog 导出器要求站点目录至少具有:
| 数据 | 原始格式 | 站点 | RTK 情况 | 在本仓库中的角色 |
|---|---|---:|---|---|
| 第一批 | 逐站 dlog,内嵌 GPS-POST-Z | 38 | 约 10 秒一条,部分站仅 1–11 个有效样本 | 辅助复核,不承担主要求解 |
| 第二批 | 逐站 dlog,内嵌 GPS-POST-Z | 38 | 每站约 125–412 个有效样本,航向稳定 | 现部署外参的主要求解数据 |
| data4 | 逐站 LiDAR dlog + 独立 RTK/IMU rscap | 34 | 11,678 个 LiDAR 帧均成功关联 fixed RTK、heading 和 IMU | 独立重算与跨批比较 |
```text
<station_dlog>/
dobject/ # DObject 索引日志
dobject_recording/ # 与索引对应的 .dorec 二进制记录
GPS-POST-Z/ # RTK 文本记录;建议同时保留完整原始 N4 串口流
```
原始数据体积较大,不在 Git 仓库中。复现者应从云盘取得第一批、第二批或 data4 的原始目录,并在命令行传入路径。
两批数据要分开存放:第二批高频 RTK 用于主要求解,第一批仅用于辅助复核。与数据一同归档的标定元数据必须包括:LiDAR DObject 名称、时区、RTK 天线参考点、双天线基线方向、heading offset、天线到后轮轴中心的杆臂、车体系定义,以及 LiDAR/RTK/IMU 的时间基准。
## 环境
对于后续新录数据,RTK 原始记录应保留 `gnss_week``gnss_tow_ms`、GGA UTC、`pitch_deg`、基线长度、解算状态/类型、标准差、HDOP 和完整原文。PC 接收时间只能作为延迟诊断,不能替代 GNSS 测量时间。
- Windows PowerShell 5.1 或 PowerShell 7
- Python 3.10+
- `pip install -r requirements.txt`
- `small_gicp` 后端需要可导入 `small_gicp`Open3D 后端需要 `open3d`
### 2. 将每个原始站点 dlog 导出为 NPZ
所有脚本从自身位置推导仓库根目录。数据和输出路径均由参数传入,不依赖开发者电脑上的固定路径。
本仓库的 [tools/frontlidar_dlog_export.py](tools/frontlidar_dlog_export.py) 从每个静止站点的原始 dlog 导出 LiDAR 原始点云与 RTK sidecar。对每个站点分别执行;必须开启 RTK sidecar 与审计报告:
## 从原始数据开始复现
以下路径只表示格式,请替换为自己的目录。
### A. 第一批、第二批旧式 dlog
```powershell
$Repo = "D:\Outdoor Ackerman Cart Sensor Adaptation\LiDAR_RTK_Calibration_Rigorous_20260721"
$Exporter = "$Repo\tools\frontlidar_dlog_export.py"
$RawStation = "<某一个原始静止站点 dlog 目录>"
$StationOut = "<工作目录>\export\001"
$Repo = "D:\你的代码目录\calibration"
python $Exporter `
--dlog "$RawStation" `
--out "$StationOut" `
--object frontlidar `
--format npz `
--timezone +08:00 `
--rtk-sidecars `
--write-reports
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\export_legacy_stations.ps1" `
-DataRoot "D:\你的数据目录\batch2_raw" `
-OutputRoot "D:\你的输出目录\batch2_export"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\prepare_legacy_dataset.ps1" `
-ExportRoot "D:\你的输出目录\batch2_export" `
-Output "D:\你的输出目录\batch2_prepared" `
-HeadingOffsetDeg 21.226 `
-AntennaLever -0.320,-0.365,0.620 `
-ExpectedStations 38 `
-HeadingStdLimitDeg 0.5
```
导出后的站点目录应包含:
第一批采用同一导出方式,但应在导出命令显式添加 `-RtkMaxDtMs 15000`;因其 RTK 稀疏,准备阶段也不建议沿用 `0.5°` 的严格站内航向离散度阈值。上述 `21.226°` 和杆臂 `[-0.320,-0.365,0.620] m` 是本项目已有两批数据采用的配置,不是通用常数;换车或改变天线安装后必须重新确认。
```text
<StationOut>/
frames/ # 多帧 LiDAR NPZ;每帧保留 points_raw
rtk/ # gps_post_z.csv/jsonl/npz 等 RTK 旁路表
reports/manifest.csv # 每帧导出状态、时间及 RTK 匹配信息
reports/validation_report.json
reports/metadata.json
```
先检查每个站点的 `validation_report.json``manifest.csv``rtk/gps_post_z.csv`LiDAR payload 必须可读;候选帧必须存在;RTK 位置/航向有效;并记录 LiDAR—RTK 时间差。若新插件已开始输出 GNSS 周/周内时间与 pitch,导出器也必须同步扩展并写出这些字段;旧导出器只保留旧插件字段时,不能伪称已完成严格时间同步。
### 3. 从导出 NPZ 构建 prepared 静止站点数据
对每个批次,将所有站点导出目录汇总后执行以下预处理逻辑,生成供本仓库使用的 `prepared` 目录:
1. 每个站点只选择一帧**车辆已静止**且 RTK 质量合格的 LiDAR 帧;通常取有效候选帧中的中间帧,避免启动/停车边缘。
2. 仅保留 `position_valid=true``heading_valid=true`、Fix 为 4 或 5,且航向标准差满足项目门限的 RTK 样本;记录每站的样本数、时间跨度、航向圆标准差与被剔除原因。
3. 使用 GNSS 测量时间把位置、heading 和 pitch 配成同一时刻;将经纬高转换到同一 ENU 世界系;根据天线杆臂和双天线方向求后轮轴中心车体位姿 `T_WB`。双天线可提供 heading/pitch,但不能提供 rollroll 在后续 LiDAR—IMU 流程中由 IMU 补充。
4. 将选出的 LiDAR 帧原样复制到 `frames_all/`。标定程序从每帧的 `points_raw` 转为 LiDAR 传感器系 XYZ,不得使用已经变换到车体系的点。
5. 输出时间戳车体位姿 CSV。当前主求解器要求列为:
```text
time,x,y,z,qx,qy,qz,qw
```
最终目录必须类似:
```text
<BatchPrepared>/
frames_all/
station_01.npz
station_02.npz
...
body_poses_rear_gga_raw_rear_to_front.csv
station_summary.csv # 推荐保留:站点质量和选择原因
manifest.json # 推荐保留:参考点、杆臂、heading/pitch 定义与筛选配置
```
本仓库的 [tools/prepare_station_dataset.py](tools/prepare_station_dataset.py) 完成上述固定站点筛选、点云复制和车体位姿重建。它要求每个站点均已由上一步使用 --rtk-sidecars --write-reports 导出。下面的参数仅用于复现本仓库历史结果:lat/lon/h 被假定为后天线相位中心,raw_heading_deg 被假定为后天线指向前天线;车体系为后轮轴中心、X 前 Y 左 Z 上。
~~~powershell
$Repo = "D:\Outdoor Ackerman Cart Sensor Adaptation\LiDAR_RTK_Calibration_Rigorous_20260721"
$Prepare = "$Repo\tools\prepare_station_dataset.py"
# 第二批:主要求解数据。ExportBatch2 下为 38 个逐站导出目录。
python $Prepare --export-root "<ExportBatch2>" --output "<PreparedBatch2>" --expected-stations 38 --heading-std-limit-deg 0.5 --heading-offset-deg 21.226 --antenna-lever -0.320 -0.365 0.620 --pose-name rear_gga_raw_rear_to_front
# 第一批:辅助复核数据。若航向质量较低,不应设置过严的 heading 标准差阈值。
python $Prepare --export-root "<ExportBatch1>" --output "<PreparedBatch1>" --expected-stations 38 --heading-offset-deg 21.226 --antenna-lever -0.320 -0.365 0.620 --pose-name rear_gga_raw_rear_to_front
~~~
命令完成后检查 <PreparedBatch*>/station_summary.csv:每行均应有有效 RTK 样本,且没有超出设定的航向离散度。manifest.json 记录实际采用的杆臂、航向偏移、ENU 原点和输入站点;它应与原始数据一同归档。
不要把上述 21.226° 和 [-0.320,-0.365,0.620] m 当成通用常数:它们是本车、后天线、后天线到前天线航向定义、以及后轮轴中心车体原点的历史配置。换车、换参考天线、改变车体原点或改变 rawHeading 定义后,必须先复核并替换这些参数,再重建 A。
当前导出器可复现本仓库历史日志,但历史 RTK 插件只记录工控机接收时间,并未保存 GNSS 周/周内时间、双天线 pitch、质量标准差及完整原始串口流。因此它不能把旧数据宣称为严格 GNSS 时间同步。插件升级后,应先扩展导出器以保存新字段,再以 GNSS 测量时间重建位置/航向(及可用的 pitch)轨迹。
### 4. 安装标定依赖并运行第一阶段
### B. data4 式独立 RTK/IMU rscap
```powershell
$Repo = "D:\Outdoor Ackerman Cart Sensor Adaptation\LiDAR_RTK_Calibration_Rigorous_20260721"
python -m pip install -r "$Repo\requirements.txt"
python -c "import numpy, scipy, open3d, small_gicp; print('dependencies OK')"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\export_multisensor_stations.ps1" `
-DataRoot "D:\你的数据目录\data4_raw" `
-RtkCapture "D:\你的数据目录\captures\rtk.rscap" `
-ImuCapture "D:\你的数据目录\captures\imu.rscap" `
-OutputRoot "D:\你的输出目录\data4_export"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\prepare_multisensor_dataset.ps1" `
-CombinedRoot "D:\你的输出目录\data4_export\combined" `
-Output "D:\你的输出目录\data4_prepared" `
-HeadingOffsetDeg 21.226 `
-AntennaLever -0.320,-0.365,0.620 `
-ExpectedStations 34
```
### C. 运行标定
单批数据同时运行 small_gicp、Open3D GICP 和跨后端共识:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_single_dataset.ps1" `
-Prepared "D:\你的输出目录\data4_prepared" `
-OutputRoot "D:\你的输出目录\data4_calibration" `
-BodyHeight 0.2335
```
复现本仓库“第二批求解、第一批辅助复核”的历史流程:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_all.ps1" `
-Batch1Prepared "<第一批 prepared 目录>" `
-Batch2Prepared "<第二批 prepared 目录>"
-Batch1Prepared "D:\你的输出目录\batch1_prepared" `
-Batch2Prepared "D:\你的输出目录\batch2_prepared" `
-OutputRoot "D:\你的输出目录\two_batch_calibration"
```
`run_all.ps1` 完成:第二批地面拟合;Open3D 与 small_gicp 两套 B 生成和精筛;两套单后端 X 求解;第一批独立 B 与辅助复核;以及单后端对比摘要。第一阶段至少应生成:
`BodyHeight=0.2335 m` 是后轮轴中心离地高度,用于把雷达地面平面约束转换到车体原点;它不是雷达离地高度。换轮胎、胎压或车体载荷后应重新测量并评估不确定度。
## 如何判断结果
不能仅凭 `AX=XB` 残差或一张叠图宣称外参正确。至少同时检查:
1. B 的配准质量:收敛、有效对应点数/比例、RMSE、Hessian 信息矩阵特征值与条件数、small_gicp 与 Open3D 的相对运动一致性。
2. X 的可观性:加权雅可比条件数越接近 1 越均衡;极大值说明存在弱方向,但没有脱离尺度和数据分布的单一硬阈值。本次 data4 为 `7.44`,不是病态。
3. AX 残差分布:看 RMS、median、P90/P95、max 和逐对异常,而不是只看均值。
4. 重采样稳定性:bootstrap 的六自由度标准差及置信区间。
5. 跨批检查:同一 X 在独立批次的残差,以及两批独立求出的 X 之 SE(3) 差。
6. 3D 可视化:模式 3 的 B 与模式 4 的 `X^-1 A X` 是否都使相同墙面、杆件和地面重合。
当前没有把“增量小于某个值”当成绝对真值判据。工程筛查可先关注多数优质运动对是否约在厘米级到数厘米、亚度级;但阈值必须结合场景尺度、点云分辨率、RTK 质量和车辆运动幅度制定。模式 3 已错位时优先检查 B;模式 3 正常而模式 4 系统性错位时,再检查 A、坐标约定和 X。
## 结果摘要
| 结果 | 平移 xyz (m) | roll/pitch/yaw (deg) | AX 平移/旋转 RMS | 结论 |
|---|---|---|---|---|
| 部署值(第二批求解) | `[1.297760,-0.000067,0.720498]` | `[-0.785151,1.202661,-0.835510]` | `0.07985 m / 0.96118°`39 对 | 当前建议部署 |
| data4 独立候选 | `[1.300376,-0.001707,0.704877]` | `[-0.791892,1.393823,-0.970743]` | `0.11762 m / 1.24257°`,26 对 | 独立候选,不替换部署值 |
data4 候选相对部署值变化 `1.592 cm / 0.234°`,其中 z 低 `1.562 cm`。旧部署值作用于 data4 的残差约 `0.11953 m / 1.24836°`;data4 候选作用于历史第二批的残差约 `0.07931 m / 0.98926°`。两个 X 的跨批表现接近,当前数据不足以证明 data4 的较低 z 更接近真值。
## 仓库目录
```text
results/common/ground_planes_batch2.csv
results/open3d_gicp/B_batch2_refined.npz
results/small_gicp/B_batch2_refined.npz
results/open3d_gicp/extrinsic_batch2_refined.json
results/small_gicp/extrinsic_batch2_refined.json
results/comparison_summary.json
code/ 标定、配准筛选、共识、比较和可视化核心程序
tools/ 原始 dlog/rscap 解析、时间关联、NPZ 导出和数据准备
run/ 不含本机固定路径的 PowerShell 入口
results/ 历史两批、data4 与跨批比较三个结果目录
```
### 5. 构造跨后端共识并生成算法结果
完整复现流程和所有主要文件职责均在本 README;`run/README.md``tools/README.md` 和 [results/README.md](results/README.md) 只是目录内快速索引。
第一阶段成功后执行:
## 代码、工具和运行入口职责
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_consensus_finish.ps1"
```
根 README 是本仓库唯一的完整复现说明。`run/README.md``tools/README.md``results/README.md` 只作为进入对应目录时的快速索引,不承载另一套流程。
该步骤以 `0.05 m / 0.50°` 门限对同一运动对的 Open3D 与 small_gicp B 做一致性门控,用共识 B 重求第二批外参,并将第一批只用于辅助复核。主要输出为:
### code:标定核心
| 文件 | 职责 |
|---|---|
| `rigorous_calibration.py` | 核心 CLI。`ground` 拟合每站地面;`pairs` 用 small_gicp 或 Open3D GICP 求 B 和质量指标;`calibrate` 联合 AX=XB 与地面约束求 X`validate` 计算指定 X 的逐对残差。 |
| `refine_pairs.py` | 根据收敛、RMSE、对应关系、Hessian/信息矩阵和运动覆盖筛选 B;不读取 X,避免循环挑选。 |
| `cross_backend_filter.py` | 对齐 Open3D 与 small_gicp 的同一站点对,只保留两个后端相互一致的 B。 |
| `finalize_consensus.py` | 汇总历史两批的 consensus B、外参和第一批辅助复核。 |
| `summarize_results.py` | 汇总两个后端的外参、B 质量和跨批检查,生成推荐结果。 |
| `compare_extrinsics.py` | 在 SE(3) 上计算两套外参的严格相对平移和旋转差。 |
| `visualize_pair_3d.py` | 交互显示原始点云、RTK A、GICP B、`X^-1AX`,并打印 `B^-1(X^-1AX)` 数值增量。 |
| `scan_extrinsic_sensitivity.py` | 对 X 左乘小角度 roll/pitch/yaw 扰动,检查指定运动对的局部敏感方向。 |
### tools:原始数据到 prepared
| 文件 | 职责 |
|---|---|
| `frontlidar_dlog_export.py` | 从 Medulla dlog 导出传感器坐标系 `points_raw`;旧格式可同时匹配站内 GPS-POST-Z。 |
| `prepare_station_dataset.py` | 从旧式逐站导出中每站选择一帧,计算 yaw-only RTK 后轮轴位姿并生成 prepared。 |
| `build_multisensor_npz.py` | 将独立 LiDAR、RTK、IMU 统一到 LiDAR 帧索引并生成 combined NPZ。 |
| `prepare_multisensor_station_dataset.py` | 从 combined NPZ 选择每站静止帧,生成与旧流程相同的 prepared 接口。 |
| `rscap_v2/capture_format_v2.py` | 读取 rscap v2 文件头、原始记录块和文件尾。 |
| `rscap_v2/audit_capture_v2.py` | 审计 capture 完整性、时间范围和记录统计。 |
| `rscap_v2/parse_rtk_imu_v2.py` | 分别解析 RTK 与 IMU capture,输出 JSONL。 |
| `rscap_v2/pipeline_common*.py` | rscap 解析、时间处理和采集格式兼容的共用逻辑。 |
### run:推荐 PowerShell 入口
| 文件 | 职责 |
|---|---|
| `export_legacy_stations.ps1` | 批量导出 LiDAR 与 RTK 同在逐站 dlog 中的旧格式。 |
| `export_multisensor_stations.ps1` | 解析独立 RTK/IMU,导出逐站 LiDAR,并按 LiDAR 帧建立关联。 |
| `prepare_legacy_dataset.ps1` | 旧式导出结果转换为 prepared。 |
| `prepare_multisensor_dataset.ps1` | combined 多传感器结果转换为 prepared。 |
| `run_single_dataset.ps1` | 单批数据同时完成两个 GICP 后端、B 筛选、consensus 和 X 求解。 |
| `run_all.ps1` | 历史流程:第二批求解,第一批稀疏 RTK 数据作辅助复核。 |
| `run_consensus_finish.ps1` | 在已有两个后端 B 的基础上重新生成 consensus 和汇总结果。 |
| `run_sensitivity_scan.ps1` | 对历史运动对执行外参角度灵敏度扫描。 |
| `view_result.ps1` | 传入匹配的 frames、B 和 X,运行交互式 3D 可视化。 |
### 标定核心文件的数据关系
```text
results/consensus/B_batch2_consensus.npz
results/consensus/extrinsic_batch2_consensus.json
results/consensus/batch1_auxiliary_check.json
results/final_extrinsic_recommended.json
results/final_summary.json
原始 dlog/rscap
-> tools 导出和时间关联
-> prepared/{frames_all, body_poses_*.csv}
-> rigorous_calibration.py pairs 生成 A、B
-> refine_pairs.py 做与 X 无关的 B 筛选
-> cross_backend_filter.py 生成 consensus B
-> rigorous_calibration.py calibrate 联合 AX=XB 与地面约束求 X
-> validate / visualize_pair_3d.py 做数值和三维检查
```
### 6. 验证、可视化与部署确认
先检查 `results/final_summary.json`:当前历史数据的参考值是第二批共识 39 对、AX RMS 约 `0.07985 m / 0.96118°`。若明显偏离,应从第 2、3 步检查站点选择、RTK 时间/参考点/杆臂/heading 定义,而不是直接手调 X。
## 重要限制
再使用 3D 可视化检查:模式 3 为点云配准 B,模式 4 为外参预测 `X^-1AX`;终端打印 `B^-1(X^-1AX)` 的平移与旋转增量。只有 B 质量、全体运动对统计、地面约束和可视化均合理时,才可确认外参
- RTK 车体姿态当前是双天线 heading 构造的 yaw-only 轨迹;没有用 RTK pitch/roll 构造 A
- 新式解析器保存 IMU 与 RTK pitch 等原始字段,但当前手眼方程未融合 IMU。
- 静止站点法不估计 LiDAR–RTK 时间偏移;时间戳关联必须在导出阶段通过审计。
- 地面约束负责 roll、pitch 和 z 的补充可观性,不会独立求出另一套六自由度外参。
- 仓库归档的是结果和轻量 B 文件,不包含云盘中的原始点云数据。
`final_extrinsic_recommended.json` 是完整算法审计结果。`run_consensus_finish.ps1` 不会自动覆盖 `results/final_extrinsic_deployment.json`;部署 JSON 必须在人工确认坐标定义和高度约束后明确写入。
## prepared 阶段的快速重跑
## 专题说明
```powershell
$Repo = "D:\Outdoor Ackerman Cart Sensor Adaptation\LiDAR_RTK_Calibration_Rigorous_20260721"
python -m pip install -r "$Repo\requirements.txt"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_all.ps1"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_consensus_finish.ps1"
```
如果 prepared 数据移动了:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_all.ps1" -Batch1Prepared "你的第一批prepared目录" -Batch2Prepared "你的第二批prepared目录"
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\run_consensus_finish.ps1"
```
重跑 `run_consensus_finish.ps1` 会重新生成算法原始结果,但不会自动生成或覆盖已经人工确认的 `final_extrinsic_deployment.json`
## 3D 可视化
Open3D 单后端结果:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\view_open3d_result.ps1" -PairIndex 0
```
small_gicp 单后端结果:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$Repo\run\view_small_gicp_result.ps1" -PairIndex 0
```
蓝色是目标站 i,橙色是源站 j。按键:`1` 原始;`2` RTK 初始 A`3` GICP B`4` 外参预测的 (X^{-1}AX);指定左乘 RPY 试验量后,`5` 为试验结果。
程序打印的差值为:
\[
B^{-1}(X^{-1}AX)
\]
其 xyz/RPY 分量位于站点 j 的雷达局部坐标系,不是屏幕坐标。模式 3 好而模式 4 差,说明 A/X 一致性不足;模式 3 本身差,说明该 B 不能用于判断外参。
共识运动对可直接指定部署外参:
```powershell
python "$Repo\code\visualize_pair_3d.py" --frames "C:\Users\admin\Documents\Codex\2026-07-15\wo\outputs\calibration_data1_20260720\prepared\frames_all" --pairs "$Repo\results\consensus\B_batch2_consensus.npz" --extrinsic "$Repo\results\final_extrinsic_deployment.json" --pair-index 0
```
局部灵敏度扫描和判断规则见 [`PAIR_DIAGNOSTICS.md`](PAIR_DIAGNOSTICS.md)。
## 关键质量文件
- [`results/open3d_gicp/B_batch2_quality.json`](results/open3d_gicp/B_batch2_quality.json):海森矩阵、留出点、正反向和多初值信息;
- [`results/open3d_gicp/B_batch2_refined.refinement.json`](results/open3d_gicp/B_batch2_refined.refinement.json):二级筛选原因;
- [`results/consensus/B_batch2_consensus.consensus.json`](results/consensus/B_batch2_consensus.consensus.json):跨后端一致性筛选;
- [`results/consensus/batch1_auxiliary_check.json`](results/consensus/batch1_auxiliary_check.json):第一批辅助复核;
- [`results/final_summary.json`](results/final_summary.json):算法结果摘要;
- [`results/diagnostics/`](results/diagnostics/):局部修正对单对及全体运动对的影响。
## 必须外部确认的假设
- `lat/lon/h` 确为后天线相位中心;
- `rawHeading` 确为后天线指向前天线;
- heading offset 数值和正负号正确;
任何一项变化,都必须更新相应约束;RTK 定义变化时需要重新生成 A 并重跑。
- [运动对诊断](PAIR_DIAGNOSTICS.md)
- [双后端共识筛选](CONSENSUS_SELECTION.md)
- [结果文件索引](results/README.md)
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Compare two T_body_lidar JSON files in parameter space and on SE(3)."""
import argparse
import json
from pathlib import Path
import numpy as np
from scipy.spatial.transform import Rotation
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--reference", type=Path, required=True)
parser.add_argument("--candidate", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
reference = json.loads(args.reference.read_text(encoding="utf-8-sig"))
candidate = json.loads(args.candidate.read_text(encoding="utf-8-sig"))
a = np.asarray(reference["matrix_4x4"], dtype=float)
b = np.asarray(candidate["matrix_4x4"], dtype=float)
delta = np.linalg.inv(a) @ b
result = {
"convention": "delta = inverse(reference) @ candidate",
"reference": str(args.reference.resolve()),
"candidate": str(args.candidate.resolve()),
"candidate_minus_reference_translation_xyz_m": (b[:3, 3] - a[:3, 3]).tolist(),
"candidate_minus_reference_rpy_xyz_deg": (
np.asarray(candidate["rotation_rpy_deg_xyz"], float)
- np.asarray(reference["rotation_rpy_deg_xyz"], float)
).tolist(),
"relative_translation_norm_m": float(np.linalg.norm(delta[:3, 3])),
"relative_rotation_deg": float(np.degrees(Rotation.from_matrix(delta[:3, :3]).magnitude())),
"relative_matrix_4x4": delta.tolist(),
}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+10
View File
@@ -0,0 +1,10 @@
# 历史两批结果
第二批 38 站的密集 RTK 数据用于求解;第一批 38 站因 RTK 约 10 秒一条,仅作辅助检查。最终部署值:
```text
translation_m = [1.297759692, -0.000067331, 0.720497835]
RPY_deg_xyz = [-0.785151146, 1.202660822, -0.835510053]
```
共识估计使用 39 个运动对,AX RMS 约 `0.07985 m / 0.96118°`。第一批辅助检查 22 对约 `0.06067 m / 1.00298°``final_extrinsic_deployment.json` 是唯一建议直接交给下游的部署 JSON;其余文件用于审计和复现。
@@ -76,4 +76,4 @@
"batch1_auxiliary_rotation_rms_deg": 1.1492603033421918
},
"important_limit": "Backend agreement is strong, but AX rotation RMS remains about one degree. This is not a centimetre-grade absolute certification."
}
}
@@ -6,8 +6,8 @@
"correction_convention": "X_test = DeltaR_body * X; DeltaR uses fixed body xyz RPY axes",
"component_frame": "delta translation/RPY components are in station-j LiDAR coordinates, not screen axes",
"selection_rule": "Never accept a correction from selected_pair alone. Require improvement over all refined pairs, directional consistency across pairs, acceptable ground constraints, and independent visual review. This script never overwrites X.",
"pairs_file": "D:\\Outdoor Ackerman Cart Sensor Adaptation\\LiDAR_RTK_Calibration_Rigorous_20260721\\results\\consensus\\B_batch2_consensus.npz",
"extrinsic_file": "D:\\Outdoor Ackerman Cart Sensor Adaptation\\LiDAR_RTK_Calibration_Rigorous_20260721\\results\\final_extrinsic_recommended.json",
"pairs_file": "results\\01_previous_two_batches\\consensus\\B_batch2_consensus.npz",
"extrinsic_file": "results\\01_previous_two_batches\\final_extrinsic_recommended.json",
"stations": 38,
"pairs": 39,
"selected_pair_index": 0,
@@ -17436,4 +17436,4 @@
}
}
]
}
}
@@ -6,8 +6,8 @@
"correction_convention": "X_test = DeltaR_body * X; DeltaR uses fixed body xyz RPY axes",
"component_frame": "delta translation/RPY components are in station-j LiDAR coordinates, not screen axes",
"selection_rule": "Never accept a correction from selected_pair alone. Require improvement over all refined pairs, directional consistency across pairs, acceptable ground constraints, and independent visual review. This script never overwrites X.",
"pairs_file": "D:\\Outdoor Ackerman Cart Sensor Adaptation\\LiDAR_RTK_Calibration_Rigorous_20260721\\results\\open3d_gicp\\B_batch2_refined.npz",
"extrinsic_file": "D:\\Outdoor Ackerman Cart Sensor Adaptation\\LiDAR_RTK_Calibration_Rigorous_20260721\\results\\final_extrinsic_recommended.json",
"pairs_file": "results\\01_previous_two_batches\\open3d_gicp\\B_batch2_refined.npz",
"extrinsic_file": "results\\01_previous_two_batches\\final_extrinsic_recommended.json",
"stations": 38,
"pairs": 66,
"selected_pair_index": 0,
@@ -28128,4 +28128,4 @@
}
}
]
}
}
@@ -6,8 +6,8 @@
"correction_convention": "X_test = DeltaR_body * X; DeltaR uses fixed body xyz RPY axes",
"component_frame": "delta translation/RPY components are in station-j LiDAR coordinates, not screen axes",
"selection_rule": "Never accept a correction from selected_pair alone. Require improvement over all refined pairs, directional consistency across pairs, acceptable ground constraints, and independent visual review. This script never overwrites X.",
"pairs_file": "D:\\Outdoor Ackerman Cart Sensor Adaptation\\LiDAR_RTK_Calibration_Rigorous_20260721\\results\\small_gicp\\B_batch2_refined.npz",
"extrinsic_file": "D:\\Outdoor Ackerman Cart Sensor Adaptation\\LiDAR_RTK_Calibration_Rigorous_20260721\\results\\final_extrinsic_recommended.json",
"pairs_file": "results\\01_previous_two_batches\\small_gicp\\B_batch2_refined.npz",
"extrinsic_file": "results\\01_previous_two_batches\\final_extrinsic_recommended.json",
"stations": 38,
"pairs": 80,
"selected_pair_index": 0,
@@ -33672,4 +33672,4 @@
}
}
]
}
}
@@ -402,4 +402,4 @@
"consensus_pair_threshold": "Open3D-small_gicp B delta <= 0.05 m and <= 0.50 deg",
"warning": "AX rotation RMS remains about one degree; this is not centimetre-grade absolute certification."
}
}
}
@@ -437,4 +437,4 @@
}
},
"warning": "AX rotation RMS remains about one degree; this is not centimetre-grade absolute certification."
}
}
@@ -13472,4 +13472,4 @@
"rejection_reasons": []
}
]
}
}
@@ -13455,4 +13455,4 @@
"rejection_reasons": []
}
]
}
}
@@ -741,4 +741,4 @@
-0.7099716198219946
]
}
}
}
@@ -13506,4 +13506,4 @@
"rejection_reasons": []
}
]
}
}
@@ -22520,4 +22520,4 @@
"rejection_reasons": []
}
]
}
}
@@ -971,4 +971,4 @@
-0.7335620940370134
]
}
}
}
+12
View File
@@ -0,0 +1,12 @@
# data4 独立标定结果
data4 含 34 个静止站点,LiDAR 来自逐站 dlogRTK/IMU 来自独立 rscap。求解不使用手量外参初始化。
```text
translation_m = [1.300376020, -0.001706691, 0.704877045]
RPY_deg_xyz = [-0.791891617, 1.393823210, -0.970742631]
AX RMS = 0.11762 m / 1.24257 deg (26 pairs)
condition = 7.44293
```
该结果用于独立对照,不是当前部署值。`final_extrinsic_data4.json``consensus/extrinsic.json` 数值相同;前者是便于下游读取的顶层副本。
@@ -0,0 +1,35 @@
time,nx,ny,nz,d,inliers,rms_m,frame_counter
1784783825.357129,-0.011572516208853837,-0.01546146293660183,0.9998134926237273,0.9428804652027359,2148,0.011933750988078078,382
1784783905.353819,0.0037183584307534687,-0.006818427483323246,0.9999698409738341,0.9412842800288904,1992,0.011942807895967073,1182
1784783971.0503054,-0.021709843154394736,-0.005232422217807239,0.999750621139065,0.9451429710867566,1863,0.012080615710528817,1839
1784784059.7468228,-0.02414670904491499,-0.0009406084776101358,0.9997079832120939,0.9517260475425733,1921,0.013468410314875789,2726
1784784149.2434597,-0.034005828386544125,0.003026503321489513,0.9994170520425345,0.9257219945735469,1673,0.012714432789206961,3621
1784784224.2408776,-0.02761369891036776,0.0016288328836576937,0.9996173420544108,0.9001377900125058,1799,0.012615520511386497,4371
1784784301.6372502,-0.0071762210837946405,-0.011511959356927467,0.9999079840878965,0.9580809447760092,2113,0.012007730376482893,5145
1784784387.733771,-0.010489658730106238,-0.009076163262051764,0.9999037905319524,0.9306898458653136,2187,0.01214615852280608,6006
1784784474.9314597,-0.016114135387570262,-0.0509432368134707,0.9985715403834008,0.9072229543504865,1894,0.012012086783846727,6878
1784784549.4274275,-0.025174259196536854,-0.013435577824823405,0.9995927880504234,0.9410339772836553,1603,0.01154746446935748,7623
1784784614.7244046,-0.03173300872144653,-0.01870285516152855,0.9993213794202,0.932566466561638,1970,0.01233410897082844,8276
1784784682.921899,-0.022665842512528424,-0.02998919824936144,0.9992932040054899,0.9145231002636922,1663,0.012948552270457988,8958
1784784758.8187964,-0.023688193326187944,-0.030852054660206355,0.9992432237549499,0.9349709939783197,1756,0.013443733500218538,9717
1784784836.0155501,-0.018461711654128285,-0.02498001140817507,0.9995174656967467,0.94720244509644,2057,0.013445893726732725,10489
1784784921.1126208,-0.01993595874096254,-0.02538449671402936,0.9994789566947642,0.9120981909462803,1822,0.012774262320504949,11340
1784784992.709947,-0.017778693539407146,-0.026803117327322167,0.9994826216386005,0.9369057170493221,2114,0.012253841210002203,12056
1784785067.6067727,-0.03766905893322114,-0.00624993226014667,0.9992707242513558,0.9491859078478477,1753,0.01368084681318558,12805
1784785215.9006598,-0.023255779098430147,-0.030526061005580876,0.9992633928739754,0.9173759052467025,2145,0.012710466955150847,14288
1784785296.4990919,-0.014034225033964978,-0.025014032112439084,0.9995885847313253,0.9275558675790446,2445,0.012495377814995792,15094
1784785363.1952267,-0.03352240146955635,-0.008711847039715113,0.9993999961581301,0.9543448006240765,1968,0.01391585588879859,15761
1784785434.592462,-0.016701702947054253,0.016663838961403298,0.9997216460544089,0.9289121671743757,1857,0.011181698281215124,16475
1784785506.389296,-0.03393096202185679,-0.007325597743377637,0.9993973311120926,0.9387396500642948,1844,0.01340053385502904,17193
1784785587.5863533,-0.03382019410288933,-0.0007820019558384217,0.9994276276668481,0.9255866795455666,1691,0.012723688174337812,18005
1784785681.9825997,-0.03422781555599595,-0.019255467037831406,0.999228544243718,0.9454924962027047,1959,0.01264678059788212,18949
1784785815.4779446,-0.0011902245774294716,-0.023262098479360813,0.9997286922659526,0.9523139768255908,2026,0.012446728838862407,20284
1784785891.9768085,-0.02758114176328463,0.0022252358991911286,0.9996170911625244,0.9417694614877323,1551,0.012616490996777729,21049
1784785967.8726046,-0.031038730802887403,-0.010699305087773895,0.9994609157244643,0.9511405539250901,1512,0.013166899954854165,21808
1784786031.5701303,-0.023257481598180085,-0.0214545061132305,0.9994992714940555,0.9380611442094842,1555,0.013339981386836522,22445
1784786087.9670725,-0.02720411594210121,-0.012536181034248165,0.9995512894498637,0.942804167293392,1531,0.013246549575843425,23009
1784786160.8647907,-0.030839502708575457,0.06473269778477829,0.9974259886879789,1.0783938869067702,1436,0.010501132985089602,23738
1784786252.6621523,-0.040972893104105006,-0.02082299858725662,0.9989432540242288,0.9524735117369321,1736,0.012945834939507173,24656
1784786319.6581354,-0.029761355495275914,-0.026565272466750618,0.9992039571668294,0.9594371422885729,1571,0.013108184070957877,25326
1784786396.7558627,-0.030769194030483185,-0.022645428394942407,0.9992699541522921,0.9756606317668476,1326,0.013202989492489907,26097
1784786557.4492514,-0.019983269314458783,-0.016965365794746882,0.9996563636124948,0.9409853928234136,1228,0.013269243263785414,27704
1 time nx ny nz d inliers rms_m frame_counter
2 1784783825.357129 -0.011572516208853837 -0.01546146293660183 0.9998134926237273 0.9428804652027359 2148 0.011933750988078078 382
3 1784783905.353819 0.0037183584307534687 -0.006818427483323246 0.9999698409738341 0.9412842800288904 1992 0.011942807895967073 1182
4 1784783971.0503054 -0.021709843154394736 -0.005232422217807239 0.999750621139065 0.9451429710867566 1863 0.012080615710528817 1839
5 1784784059.7468228 -0.02414670904491499 -0.0009406084776101358 0.9997079832120939 0.9517260475425733 1921 0.013468410314875789 2726
6 1784784149.2434597 -0.034005828386544125 0.003026503321489513 0.9994170520425345 0.9257219945735469 1673 0.012714432789206961 3621
7 1784784224.2408776 -0.02761369891036776 0.0016288328836576937 0.9996173420544108 0.9001377900125058 1799 0.012615520511386497 4371
8 1784784301.6372502 -0.0071762210837946405 -0.011511959356927467 0.9999079840878965 0.9580809447760092 2113 0.012007730376482893 5145
9 1784784387.733771 -0.010489658730106238 -0.009076163262051764 0.9999037905319524 0.9306898458653136 2187 0.01214615852280608 6006
10 1784784474.9314597 -0.016114135387570262 -0.0509432368134707 0.9985715403834008 0.9072229543504865 1894 0.012012086783846727 6878
11 1784784549.4274275 -0.025174259196536854 -0.013435577824823405 0.9995927880504234 0.9410339772836553 1603 0.01154746446935748 7623
12 1784784614.7244046 -0.03173300872144653 -0.01870285516152855 0.9993213794202 0.932566466561638 1970 0.01233410897082844 8276
13 1784784682.921899 -0.022665842512528424 -0.02998919824936144 0.9992932040054899 0.9145231002636922 1663 0.012948552270457988 8958
14 1784784758.8187964 -0.023688193326187944 -0.030852054660206355 0.9992432237549499 0.9349709939783197 1756 0.013443733500218538 9717
15 1784784836.0155501 -0.018461711654128285 -0.02498001140817507 0.9995174656967467 0.94720244509644 2057 0.013445893726732725 10489
16 1784784921.1126208 -0.01993595874096254 -0.02538449671402936 0.9994789566947642 0.9120981909462803 1822 0.012774262320504949 11340
17 1784784992.709947 -0.017778693539407146 -0.026803117327322167 0.9994826216386005 0.9369057170493221 2114 0.012253841210002203 12056
18 1784785067.6067727 -0.03766905893322114 -0.00624993226014667 0.9992707242513558 0.9491859078478477 1753 0.01368084681318558 12805
19 1784785215.9006598 -0.023255779098430147 -0.030526061005580876 0.9992633928739754 0.9173759052467025 2145 0.012710466955150847 14288
20 1784785296.4990919 -0.014034225033964978 -0.025014032112439084 0.9995885847313253 0.9275558675790446 2445 0.012495377814995792 15094
21 1784785363.1952267 -0.03352240146955635 -0.008711847039715113 0.9993999961581301 0.9543448006240765 1968 0.01391585588879859 15761
22 1784785434.592462 -0.016701702947054253 0.016663838961403298 0.9997216460544089 0.9289121671743757 1857 0.011181698281215124 16475
23 1784785506.389296 -0.03393096202185679 -0.007325597743377637 0.9993973311120926 0.9387396500642948 1844 0.01340053385502904 17193
24 1784785587.5863533 -0.03382019410288933 -0.0007820019558384217 0.9994276276668481 0.9255866795455666 1691 0.012723688174337812 18005
25 1784785681.9825997 -0.03422781555599595 -0.019255467037831406 0.999228544243718 0.9454924962027047 1959 0.01264678059788212 18949
26 1784785815.4779446 -0.0011902245774294716 -0.023262098479360813 0.9997286922659526 0.9523139768255908 2026 0.012446728838862407 20284
27 1784785891.9768085 -0.02758114176328463 0.0022252358991911286 0.9996170911625244 0.9417694614877323 1551 0.012616490996777729 21049
28 1784785967.8726046 -0.031038730802887403 -0.010699305087773895 0.9994609157244643 0.9511405539250901 1512 0.013166899954854165 21808
29 1784786031.5701303 -0.023257481598180085 -0.0214545061132305 0.9994992714940555 0.9380611442094842 1555 0.013339981386836522 22445
30 1784786087.9670725 -0.02720411594210121 -0.012536181034248165 0.9995512894498637 0.942804167293392 1531 0.013246549575843425 23009
31 1784786160.8647907 -0.030839502708575457 0.06473269778477829 0.9974259886879789 1.0783938869067702 1436 0.010501132985089602 23738
32 1784786252.6621523 -0.040972893104105006 -0.02082299858725662 0.9989432540242288 0.9524735117369321 1736 0.012945834939507173 24656
33 1784786319.6581354 -0.029761355495275914 -0.026565272466750618 0.9992039571668294 0.9594371422885729 1571 0.013108184070957877 25326
34 1784786396.7558627 -0.030769194030483185 -0.022645428394942407 0.9992699541522921 0.9756606317668476 1326 0.013202989492489907 26097
35 1784786557.4492514 -0.019983269314458783 -0.016965365794746882 0.9996563636124948 0.9409853928234136 1228 0.013269243263785414 27704
@@ -0,0 +1,340 @@
{
"selection_is_X_independent": true,
"B_source": "Open3D; small_gicp is used only as an agreement gate",
"max_translation_m": 0.05,
"max_rotation_deg": 0.5,
"input_open3d_pairs": 42,
"accepted_pairs": 26,
"pairs": [
{
"i": 0,
"j": 1,
"open3d_small_translation_m": 0.014276441704401843,
"open3d_small_rotation_deg": 0.61360593819684,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 0,
"j": 2,
"open3d_small_translation_m": 0.019952450418350955,
"open3d_small_rotation_deg": 0.1460927002571647,
"accepted": true,
"reason": ""
},
{
"i": 1,
"j": 2,
"accepted": false,
"reason": "not_in_small_gicp_refined"
},
{
"i": 2,
"j": 3,
"open3d_small_translation_m": 0.02240729290024995,
"open3d_small_rotation_deg": 0.16748011698659707,
"accepted": true,
"reason": ""
},
{
"i": 2,
"j": 5,
"open3d_small_translation_m": 0.0061616498009369,
"open3d_small_rotation_deg": 0.13760629005733752,
"accepted": true,
"reason": ""
},
{
"i": 3,
"j": 5,
"open3d_small_translation_m": 0.03979893704050873,
"open3d_small_rotation_deg": 0.21317157512260582,
"accepted": true,
"reason": ""
},
{
"i": 3,
"j": 6,
"open3d_small_translation_m": 0.012411893826144786,
"open3d_small_rotation_deg": 0.6409039547122976,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 5,
"j": 8,
"open3d_small_translation_m": 0.02304199707639382,
"open3d_small_rotation_deg": 0.37075901631515606,
"accepted": true,
"reason": ""
},
{
"i": 6,
"j": 7,
"open3d_small_translation_m": 0.008481323658868146,
"open3d_small_rotation_deg": 0.2110246381252998,
"accepted": true,
"reason": ""
},
{
"i": 6,
"j": 8,
"open3d_small_translation_m": 0.03928926702287607,
"open3d_small_rotation_deg": 0.3638880951335251,
"accepted": true,
"reason": ""
},
{
"i": 7,
"j": 8,
"open3d_small_translation_m": 0.008121615288998074,
"open3d_small_rotation_deg": 0.6269514061788241,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 10,
"j": 11,
"open3d_small_translation_m": 0.021311366483594964,
"open3d_small_rotation_deg": 0.5895827931090624,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 12,
"j": 14,
"open3d_small_translation_m": 0.021593615635265958,
"open3d_small_rotation_deg": 0.28280557179019644,
"accepted": true,
"reason": ""
},
{
"i": 12,
"j": 15,
"open3d_small_translation_m": 0.03680193420722766,
"open3d_small_rotation_deg": 0.560849139510841,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 13,
"j": 15,
"open3d_small_translation_m": 0.02340982602704091,
"open3d_small_rotation_deg": 0.5560478634176494,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 13,
"j": 16,
"open3d_small_translation_m": 0.027602744462628358,
"open3d_small_rotation_deg": 0.2529072067309942,
"accepted": true,
"reason": ""
},
{
"i": 15,
"j": 16,
"open3d_small_translation_m": 0.0209082447077901,
"open3d_small_rotation_deg": 0.03400462126505844,
"accepted": true,
"reason": ""
},
{
"i": 15,
"j": 17,
"open3d_small_translation_m": 0.04186146133502697,
"open3d_small_rotation_deg": 0.13358624916951176,
"accepted": true,
"reason": ""
},
{
"i": 15,
"j": 18,
"open3d_small_translation_m": 0.014610177110996908,
"open3d_small_rotation_deg": 0.25378510251316655,
"accepted": true,
"reason": ""
},
{
"i": 16,
"j": 19,
"open3d_small_translation_m": 0.012703728170382652,
"open3d_small_rotation_deg": 0.36607447371237334,
"accepted": true,
"reason": ""
},
{
"i": 17,
"j": 18,
"open3d_small_translation_m": 0.03054772105829047,
"open3d_small_rotation_deg": 0.6293060553954827,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 21,
"j": 22,
"open3d_small_translation_m": 0.008438580476457845,
"open3d_small_rotation_deg": 0.15351253406054555,
"accepted": true,
"reason": ""
},
{
"i": 21,
"j": 23,
"open3d_small_translation_m": 0.018479690388276085,
"open3d_small_rotation_deg": 0.5781174905909121,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 21,
"j": 24,
"open3d_small_translation_m": 0.031284642427708814,
"open3d_small_rotation_deg": 0.39218826727895556,
"accepted": true,
"reason": ""
},
{
"i": 22,
"j": 23,
"open3d_small_translation_m": 0.011568269703658672,
"open3d_small_rotation_deg": 0.06135783639322868,
"accepted": true,
"reason": ""
},
{
"i": 22,
"j": 25,
"open3d_small_translation_m": 0.041018828349300214,
"open3d_small_rotation_deg": 0.42033964239927313,
"accepted": true,
"reason": ""
},
{
"i": 23,
"j": 24,
"open3d_small_translation_m": 0.009191025383393128,
"open3d_small_rotation_deg": 0.5691342385312416,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 25,
"j": 26,
"open3d_small_translation_m": 0.020915300595375722,
"open3d_small_rotation_deg": 0.48750667250286367,
"accepted": true,
"reason": ""
},
{
"i": 25,
"j": 27,
"open3d_small_translation_m": 0.09632206662156453,
"open3d_small_rotation_deg": 0.36999381209651033,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 25,
"j": 28,
"open3d_small_translation_m": 0.030138112812714665,
"open3d_small_rotation_deg": 0.46587172800425714,
"accepted": true,
"reason": ""
},
{
"i": 26,
"j": 27,
"open3d_small_translation_m": 0.010107572072066551,
"open3d_small_rotation_deg": 0.4201365669869145,
"accepted": true,
"reason": ""
},
{
"i": 26,
"j": 28,
"open3d_small_translation_m": 0.006243998078354416,
"open3d_small_rotation_deg": 1.3246136424341453,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 26,
"j": 29,
"open3d_small_translation_m": 0.036178252863401886,
"open3d_small_rotation_deg": 0.2782366609124745,
"accepted": true,
"reason": ""
},
{
"i": 27,
"j": 28,
"open3d_small_translation_m": 0.00854064689385523,
"open3d_small_rotation_deg": 0.36768009631129556,
"accepted": true,
"reason": ""
},
{
"i": 27,
"j": 29,
"open3d_small_translation_m": 0.013991202401755857,
"open3d_small_rotation_deg": 0.43729057730087656,
"accepted": true,
"reason": ""
},
{
"i": 28,
"j": 29,
"open3d_small_translation_m": 0.12711262123542336,
"open3d_small_rotation_deg": 0.923125258275691,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 28,
"j": 31,
"accepted": false,
"reason": "not_in_small_gicp_refined"
},
{
"i": 29,
"j": 30,
"open3d_small_translation_m": 0.024384029932636327,
"open3d_small_rotation_deg": 0.4169363425835767,
"accepted": true,
"reason": ""
},
{
"i": 29,
"j": 31,
"open3d_small_translation_m": 0.012379055939823663,
"open3d_small_rotation_deg": 0.2514662901219578,
"accepted": true,
"reason": ""
},
{
"i": 30,
"j": 31,
"open3d_small_translation_m": 0.056474627862095485,
"open3d_small_rotation_deg": 0.6666837905087916,
"accepted": false,
"reason": "backend_disagreement"
},
{
"i": 30,
"j": 32,
"open3d_small_translation_m": 0.015291330212417978,
"open3d_small_rotation_deg": 0.309282499387647,
"accepted": true,
"reason": ""
},
{
"i": 31,
"j": 32,
"accepted": false,
"reason": "not_in_small_gicp_refined"
}
]
}
@@ -0,0 +1,334 @@
{
"schema_version": 2,
"success": true,
"message": "`ftol` termination condition is satisfied.",
"convention": "T_body_lidar maps raw LiDAR points into rear-axle body frame",
"equation": "A_ij X = X B_ij",
"measured_extrinsic_used_as_initial": false,
"translation_m": [
1.3003760202954853,
-0.001706690964618711,
0.7048770452870675
],
"rotation_rpy_deg_xyz": [
-0.7918916173570606,
1.3938232102436765,
-0.9707426305626278
],
"quaternion_xyzw": [
-0.006806709954738454,
0.01222091143708808,
-0.008386347182892065,
0.9998669847555559
],
"matrix_4x4": [
[
0.9995606370091497,
0.016604094942673456,
0.024552738604839263,
1.3003760202954853
],
[
-0.016936831740812692,
0.9997666757610401,
0.01340663150469432,
-0.001706690964618711
],
[
-0.02432440487342451,
-0.013816586729505535,
0.9996086360464778,
0.7048770452870675
],
[
0.0,
0.0,
0.0,
1.0
]
],
"estimation": {
"stations": 34,
"pairs": 26,
"residuals": {
"pairs": 26,
"translation_m": {
"rms": 0.11761746401526753,
"median": 0.05973702338495346,
"p90": 0.12346154791373619,
"p95": 0.2642880752048252,
"max": 0.38818289084930874
},
"rotation_deg": {
"rms": 1.2425719632195558,
"median": 0.7690245702483323,
"p90": 1.8136122342812846,
"p95": 1.9548737964733964,
"max": 4.332147922413344
},
"per_pair": [
{
"pair_index": 0,
"translation_m": 0.04628122614893134,
"rotation_deg": 0.6982246240615032
},
{
"pair_index": 1,
"translation_m": 0.060020104078054665,
"rotation_deg": 0.3948778403656006
},
{
"pair_index": 2,
"translation_m": 0.10360591701372288,
"rotation_deg": 0.30363546700066535
},
{
"pair_index": 3,
"translation_m": 0.06188990107567784,
"rotation_deg": 0.5542261589736583
},
{
"pair_index": 4,
"translation_m": 0.02429424053491548,
"rotation_deg": 0.5086436052507228
},
{
"pair_index": 5,
"translation_m": 0.06697411809016678,
"rotation_deg": 0.7794510927257128
},
{
"pair_index": 6,
"translation_m": 0.012085958852730979,
"rotation_deg": 0.7997166092197476
},
{
"pair_index": 7,
"translation_m": 0.04528858471141819,
"rotation_deg": 0.868879957697597
},
{
"pair_index": 8,
"translation_m": 0.05426127100895971,
"rotation_deg": 1.8644817833901326
},
{
"pair_index": 9,
"translation_m": 0.12845158967421863,
"rotation_deg": 1.9850044675011511
},
{
"pair_index": 10,
"translation_m": 0.06743761428527982,
"rotation_deg": 0.23695300739340452
},
{
"pair_index": 11,
"translation_m": 0.060359774544132216,
"rotation_deg": 0.3380187067759931
},
{
"pair_index": 12,
"translation_m": 0.03420362550416907,
"rotation_deg": 0.49547670606543825
},
{
"pair_index": 13,
"translation_m": 0.059453942691852245,
"rotation_deg": 0.6142581264077737
},
{
"pair_index": 14,
"translation_m": 0.048234437308639966,
"rotation_deg": 1.7627426851724366
},
{
"pair_index": 15,
"translation_m": 0.0572558948305829,
"rotation_deg": 0.8826512445544471
},
{
"pair_index": 16,
"translation_m": 0.11847150615325375,
"rotation_deg": 4.332147922413344
},
{
"pair_index": 17,
"translation_m": 0.05084797298915324,
"rotation_deg": 0.9691629612561474
},
{
"pair_index": 18,
"translation_m": 0.03403536387594384,
"rotation_deg": 0.9311969881819248
},
{
"pair_index": 19,
"translation_m": 0.05397038183164141,
"rotation_deg": 0.5108403673418249
},
{
"pair_index": 20,
"translation_m": 0.10079710084559501,
"rotation_deg": 0.9132399116834199
},
{
"pair_index": 21,
"translation_m": 0.0245653967740859,
"rotation_deg": 0.29258746424545673
},
{
"pair_index": 22,
"translation_m": 0.06599237622687693,
"rotation_deg": 1.130391129168243
},
{
"pair_index": 23,
"translation_m": 0.30956690371502743,
"rotation_deg": 0.7585980477709516
},
{
"pair_index": 24,
"translation_m": 0.38818289084930874,
"rotation_deg": 0.6792102747820554
},
{
"pair_index": 25,
"translation_m": 0.10874622325422871,
"rotation_deg": 0.8215202229484776
}
]
}
},
"ground": {
"planes": 34,
"body_origin_height_above_ground_m": 0.2335,
"formula": "d_lidar - (R_X n_lidar)^T t_X - body_height"
},
"linearized_one_sigma": {
"translation_m": [
0.00880269284499636,
0.00901239388348182,
0.0047231303576790815
],
"rotation_deg": [
0.08002846787949729,
0.07607548557648909,
0.16438009658312316
],
"warning": "conditional local estimate; bootstrap is the primary stability check"
},
"weighted_jacobian_condition_number": 7.44293047593025,
"solver_multistart": {
"runs": 12,
"candidates_relative_to_best": [
{
"cost": 125.63045335315196,
"success": true,
"translation_m": 2.6261422580681395e-09,
"rotation_deg": 5.414246532613018e-08
},
{
"cost": 125.63045335315196,
"success": true,
"translation_m": 1.4824807208067879e-09,
"rotation_deg": 1.72860210982948e-08
},
{
"cost": 125.63045335315223,
"success": true,
"translation_m": 2.630839730785566e-09,
"rotation_deg": 6.406686644059802e-08
},
{
"cost": 125.63045335315202,
"success": true,
"translation_m": 3.4171046507681323e-09,
"rotation_deg": 6.031782751294924e-08
},
{
"cost": 125.63045335315198,
"success": true,
"translation_m": 1.024465800095368e-09,
"rotation_deg": 5.593612335539163e-09
},
{
"cost": 125.63045335315205,
"success": true,
"translation_m": 3.3380353081804456e-09,
"rotation_deg": 5.989779005616406e-08
},
{
"cost": 125.63045335315195,
"success": true,
"translation_m": 1.3390324575379226e-09,
"rotation_deg": 1.669449907197552e-08
},
{
"cost": 125.63045335315194,
"success": true,
"translation_m": 9.664926809065912e-10,
"rotation_deg": 1.725605645088929e-08
},
{
"cost": 125.63045335315195,
"success": true,
"translation_m": 1.1236440604027455e-09,
"rotation_deg": 8.844155186013785e-09
},
{
"cost": 125.63045335315957,
"success": true,
"translation_m": 4.0330699471902714e-08,
"rotation_deg": 2.7706147186959257e-07
},
{
"cost": 125.63045335315196,
"success": true,
"translation_m": 1.1516332665838399e-10,
"rotation_deg": 2.767718681568013e-09
},
{
"cost": 125.63045335315192,
"success": true,
"translation_m": 2.220446049250313e-16,
"rotation_deg": 0.0
}
]
},
"bootstrap": {
"runs": 100,
"order": [
"x_m",
"y_m",
"z_m",
"roll_deg",
"pitch_deg",
"yaw_deg"
],
"std": [
0.004332764631777773,
0.0052124106362270465,
0.002294080818689292,
0.09999053340933307,
0.10013087210161215,
0.1488688877903013
],
"p025": [
1.2918362278137754,
-0.009531330977586527,
0.7011199682224338,
-0.9599898042168808,
1.2066688822355272,
-1.2617485460367455
],
"p975": [
1.3082553293462966,
0.009443875945124016,
0.7092373306937458,
-0.5988363907444886,
1.5590218787244785,
-0.7051603064062103
]
}
}
@@ -0,0 +1,334 @@
{
"schema_version": 2,
"success": true,
"message": "`ftol` termination condition is satisfied.",
"convention": "T_body_lidar maps raw LiDAR points into rear-axle body frame",
"equation": "A_ij X = X B_ij",
"measured_extrinsic_used_as_initial": false,
"translation_m": [
1.3003760202954853,
-0.001706690964618711,
0.7048770452870675
],
"rotation_rpy_deg_xyz": [
-0.7918916173570606,
1.3938232102436765,
-0.9707426305626278
],
"quaternion_xyzw": [
-0.006806709954738454,
0.01222091143708808,
-0.008386347182892065,
0.9998669847555559
],
"matrix_4x4": [
[
0.9995606370091497,
0.016604094942673456,
0.024552738604839263,
1.3003760202954853
],
[
-0.016936831740812692,
0.9997666757610401,
0.01340663150469432,
-0.001706690964618711
],
[
-0.02432440487342451,
-0.013816586729505535,
0.9996086360464778,
0.7048770452870675
],
[
0.0,
0.0,
0.0,
1.0
]
],
"estimation": {
"stations": 34,
"pairs": 26,
"residuals": {
"pairs": 26,
"translation_m": {
"rms": 0.11761746401526753,
"median": 0.05973702338495346,
"p90": 0.12346154791373619,
"p95": 0.2642880752048252,
"max": 0.38818289084930874
},
"rotation_deg": {
"rms": 1.2425719632195558,
"median": 0.7690245702483323,
"p90": 1.8136122342812846,
"p95": 1.9548737964733964,
"max": 4.332147922413344
},
"per_pair": [
{
"pair_index": 0,
"translation_m": 0.04628122614893134,
"rotation_deg": 0.6982246240615032
},
{
"pair_index": 1,
"translation_m": 0.060020104078054665,
"rotation_deg": 0.3948778403656006
},
{
"pair_index": 2,
"translation_m": 0.10360591701372288,
"rotation_deg": 0.30363546700066535
},
{
"pair_index": 3,
"translation_m": 0.06188990107567784,
"rotation_deg": 0.5542261589736583
},
{
"pair_index": 4,
"translation_m": 0.02429424053491548,
"rotation_deg": 0.5086436052507228
},
{
"pair_index": 5,
"translation_m": 0.06697411809016678,
"rotation_deg": 0.7794510927257128
},
{
"pair_index": 6,
"translation_m": 0.012085958852730979,
"rotation_deg": 0.7997166092197476
},
{
"pair_index": 7,
"translation_m": 0.04528858471141819,
"rotation_deg": 0.868879957697597
},
{
"pair_index": 8,
"translation_m": 0.05426127100895971,
"rotation_deg": 1.8644817833901326
},
{
"pair_index": 9,
"translation_m": 0.12845158967421863,
"rotation_deg": 1.9850044675011511
},
{
"pair_index": 10,
"translation_m": 0.06743761428527982,
"rotation_deg": 0.23695300739340452
},
{
"pair_index": 11,
"translation_m": 0.060359774544132216,
"rotation_deg": 0.3380187067759931
},
{
"pair_index": 12,
"translation_m": 0.03420362550416907,
"rotation_deg": 0.49547670606543825
},
{
"pair_index": 13,
"translation_m": 0.059453942691852245,
"rotation_deg": 0.6142581264077737
},
{
"pair_index": 14,
"translation_m": 0.048234437308639966,
"rotation_deg": 1.7627426851724366
},
{
"pair_index": 15,
"translation_m": 0.0572558948305829,
"rotation_deg": 0.8826512445544471
},
{
"pair_index": 16,
"translation_m": 0.11847150615325375,
"rotation_deg": 4.332147922413344
},
{
"pair_index": 17,
"translation_m": 0.05084797298915324,
"rotation_deg": 0.9691629612561474
},
{
"pair_index": 18,
"translation_m": 0.03403536387594384,
"rotation_deg": 0.9311969881819248
},
{
"pair_index": 19,
"translation_m": 0.05397038183164141,
"rotation_deg": 0.5108403673418249
},
{
"pair_index": 20,
"translation_m": 0.10079710084559501,
"rotation_deg": 0.9132399116834199
},
{
"pair_index": 21,
"translation_m": 0.0245653967740859,
"rotation_deg": 0.29258746424545673
},
{
"pair_index": 22,
"translation_m": 0.06599237622687693,
"rotation_deg": 1.130391129168243
},
{
"pair_index": 23,
"translation_m": 0.30956690371502743,
"rotation_deg": 0.7585980477709516
},
{
"pair_index": 24,
"translation_m": 0.38818289084930874,
"rotation_deg": 0.6792102747820554
},
{
"pair_index": 25,
"translation_m": 0.10874622325422871,
"rotation_deg": 0.8215202229484776
}
]
}
},
"ground": {
"planes": 34,
"body_origin_height_above_ground_m": 0.2335,
"formula": "d_lidar - (R_X n_lidar)^T t_X - body_height"
},
"linearized_one_sigma": {
"translation_m": [
0.00880269284499636,
0.00901239388348182,
0.0047231303576790815
],
"rotation_deg": [
0.08002846787949729,
0.07607548557648909,
0.16438009658312316
],
"warning": "conditional local estimate; bootstrap is the primary stability check"
},
"weighted_jacobian_condition_number": 7.44293047593025,
"solver_multistart": {
"runs": 12,
"candidates_relative_to_best": [
{
"cost": 125.63045335315196,
"success": true,
"translation_m": 2.6261422580681395e-09,
"rotation_deg": 5.414246532613018e-08
},
{
"cost": 125.63045335315196,
"success": true,
"translation_m": 1.4824807208067879e-09,
"rotation_deg": 1.72860210982948e-08
},
{
"cost": 125.63045335315223,
"success": true,
"translation_m": 2.630839730785566e-09,
"rotation_deg": 6.406686644059802e-08
},
{
"cost": 125.63045335315202,
"success": true,
"translation_m": 3.4171046507681323e-09,
"rotation_deg": 6.031782751294924e-08
},
{
"cost": 125.63045335315198,
"success": true,
"translation_m": 1.024465800095368e-09,
"rotation_deg": 5.593612335539163e-09
},
{
"cost": 125.63045335315205,
"success": true,
"translation_m": 3.3380353081804456e-09,
"rotation_deg": 5.989779005616406e-08
},
{
"cost": 125.63045335315195,
"success": true,
"translation_m": 1.3390324575379226e-09,
"rotation_deg": 1.669449907197552e-08
},
{
"cost": 125.63045335315194,
"success": true,
"translation_m": 9.664926809065912e-10,
"rotation_deg": 1.725605645088929e-08
},
{
"cost": 125.63045335315195,
"success": true,
"translation_m": 1.1236440604027455e-09,
"rotation_deg": 8.844155186013785e-09
},
{
"cost": 125.63045335315957,
"success": true,
"translation_m": 4.0330699471902714e-08,
"rotation_deg": 2.7706147186959257e-07
},
{
"cost": 125.63045335315196,
"success": true,
"translation_m": 1.1516332665838399e-10,
"rotation_deg": 2.767718681568013e-09
},
{
"cost": 125.63045335315192,
"success": true,
"translation_m": 2.220446049250313e-16,
"rotation_deg": 0.0
}
]
},
"bootstrap": {
"runs": 100,
"order": [
"x_m",
"y_m",
"z_m",
"roll_deg",
"pitch_deg",
"yaw_deg"
],
"std": [
0.004332764631777773,
0.0052124106362270465,
0.002294080818689292,
0.09999053340933307,
0.10013087210161215,
0.1488688877903013
],
"p025": [
1.2918362278137754,
-0.009531330977586527,
0.7011199682224338,
-0.9599898042168808,
1.2066688822355272,
-1.2617485460367455
],
"p975": [
1.3082553293462966,
0.009443875945124016,
0.7092373306937458,
-0.5988363907444886,
1.5590218787244785,
-0.7051603064062103
]
}
}
@@ -0,0 +1,97 @@
i,j,rtk_translation_m,rtk_rotation_deg,heldout_inlier_ratio,heldout_inlier_rmse_m,hessian_rank,hessian_condition,reverse_translation_m,reverse_rotation_deg,multistart_success_rate,accepted,rejection_reasons
0,1,1.8477087194158957,24.171297449440786,0.8061657032755298,0.10961296014103376,6,2.7038608113687213,0.004225163540003135,0.15304490677206398,1.0,True,
0,2,2.633731568575307,80.09074797031298,0.7489394523717702,0.116305716193008,6,3.0720058333957385,0.02073003109723772,0.12418344306820694,1.0,True,
0,3,6.923255970100826,79.91583883299243,0.6310283235519265,0.12470979645173065,6,5.235632990817995,0.02154775870989581,0.2539904497958252,1.0,True,
1,2,1.7155362084417105,55.91945052087219,0.7867383512544803,0.11079021393934936,6,3.282529873989152,0.0076823267394430066,0.05080433004685343,1.0,True,
1,3,5.885389341942907,55.74454138355163,0.6794562317367552,0.1190716913809658,6,4.183386002322129,0.024832426027852336,0.29364033194785566,1.0,True,
1,4,2.3031736807956613,106.08652205569953,0.6786112833230006,0.11255015823152649,6,3.2941312581877575,0.007769137931169012,0.07270245476793177,1.0,True,
2,3,4.339151683168145,0.17490913732054883,0.7586776859504132,0.11541610277862563,6,3.730803369806123,0.007879904085793275,0.11659483159946175,1.0,True,
2,4,0.6150408096900305,50.167071534827386,0.7854572527608884,0.1098649389241601,6,2.641287751481569,0.017042953274278242,0.09383247792992147,1.0,True,
2,5,5.894923525501676,8.735318060700383,0.7074574574574575,0.11912871456224478,6,3.8527093190832473,0.016641241123247028,0.24777402858141082,1.0,True,
3,4,3.9735035126146885,50.34198067214791,0.6974624291697462,0.1172763088894914,6,3.5010457716923225,0.012148721219467648,0.2079308684382429,1.0,True,
3,5,2.3619393178889707,8.910227198020932,0.7962985964476462,0.10646082199215777,6,3.0377458538379902,0.011008298723511818,0.08217077610411756,1.0,True,
3,6,2.2300116834828536,47.63555775102664,0.8376509054325956,0.10956583416752592,6,3.1930663579156233,0.001222821038316072,0.09207521511761255,1.0,True,
4,5,5.370771070097231,41.431753474126985,0.6652516676773802,0.12322094568314959,6,4.985227704290952,0.005399786589867114,0.1389350777589359,1.0,True,
4,6,6.142548765456278,97.97753842317456,0.6529585072428186,0.11878586101480827,6,5.324803753668097,0.011377811219747914,0.20418431358287809,1.0,True,
4,7,5.314106070657687,123.28910472998356,0.6964418087472202,0.11691192595448072,6,4.414726450762486,0.017641796253111963,0.10886712169052283,1.0,True,
5,6,1.9357867378988893,56.54578494904757,0.7526921648718901,0.10924852668609378,6,3.289802074469556,0.012374747586501101,0.14461856068326442,1.0,True,
5,7,0.19501559913675365,81.85735125585654,0.7833561729164071,0.11683079277948674,6,2.8807765869032655,0.0136898748124479,0.2000656277647698,1.0,True,
5,8,1.8038807059856978,172.47951556359365,0.7410703250525275,0.11436945722491815,6,3.9768956884456648,0.020336999081352437,0.07548452187379719,1.0,True,
6,7,2.130456053070114,25.311566306808988,0.8497729566094854,0.10415909908074772,6,3.375936129784756,0.008986805974948919,0.1309947950641294,1.0,True,
6,8,0.24494500622098103,115.93373061454483,0.7678928928928929,0.11166725079781287,6,3.2609205733873607,0.010085391730565987,0.11752836996157842,1.0,True,
6,9,8.025985916230132,103.90638727582186,0.6071384156199477,0.12594282886521954,6,6.882498483502561,0.01655440444551025,0.4345945520890901,1.0,True,
7,8,1.9962664218365056,90.62216430773583,0.8299748110831234,0.10748525688830209,6,3.0071179226782414,0.007106461978852689,0.10698728473689886,1.0,True,
7,9,7.8811064994361235,78.59482096901284,0.6188509200150206,0.1271320507839345,6,6.716979637303383,0.026230622734929154,0.28112746782878123,1.0,True,
7,10,7.620696678413973,134.60031955305035,0.5757088027733069,0.1313446077529101,6,5.755535843660075,0.012710332356906473,0.2297418063823675,1.0,True,
8,9,7.803713858547152,12.027343338722998,0.6326834719980131,0.12084265385763356,6,5.539689880493177,0.02518064403521875,0.3859647522318924,1.0,True,
8,10,8.062504137991457,43.97815524531457,0.6163861933423412,0.12936028463965984,6,4.789005902863088,0.011327540721525892,0.17059480981566058,1.0,True,
8,11,11.084531710563947,22.606708130954026,0.5371195721380364,0.13463285672049757,6,5.833003178860405,0.017887959781482814,0.14919899299178907,1.0,True,
9,10,2.04815062353057,56.00549858403758,0.6543345543345543,0.10729272360686735,6,3.3788563349731584,0.008725655639009402,0.02870462611907292,1.0,True,
9,11,4.738677237611319,34.634051469677026,0.5818780055682106,0.11717687815100752,6,3.555986532172074,0.009813025835682346,0.0603546019895227,1.0,True,
9,12,7.170741483679294,16.35622721712263,0.5379123584441162,0.12645695585785732,6,4.882558096197038,0.008862930161052695,0.11566409877698863,1.0,True,
10,11,3.2047552083250137,21.371447114360556,0.7118898623279099,0.1192865608074921,6,3.2202034715576238,0.0029529340190147615,0.0041769080934441144,1.0,True,
10,12,6.291813977735496,39.64927136691496,0.6120311738918656,0.1252565212812615,6,4.691686416856199,0.005686910809265337,0.10204044727301474,1.0,True,
10,13,10.199392557022867,72.41150002956134,0.516551290119572,0.1354903305714049,6,7.346543684414892,0.01356504272158301,0.3255230434099281,1.0,True,
11,12,3.467398797536633,18.277824252554396,0.650555275113579,0.12104882943540958,6,4.612247786063486,0.003583199374499245,0.03300207707174736,1.0,True,
11,13,7.516502113110916,51.04005291520078,0.5698054068172914,0.12858662743221627,6,7.539783468898054,0.016001435752891854,0.11059625579949509,1.0,True,
11,14,3.767517331528496,20.548768889074672,0.6420881321982974,0.12414062948335584,6,4.9522650472668115,0.012369101516230236,0.018705060406060074,1.0,True,
12,13,4.049286119591895,32.762228662646386,0.6972966112450819,0.11680896213116294,6,4.282247993741361,0.011410238767832601,0.04779713993430858,1.0,True,
12,14,0.97948616772873,2.2709446365202806,0.8749086479902558,0.09521299965540617,6,3.309695139564419,0.006159472215773642,0.014929948455572307,1.0,True,
12,15,4.286747470271891,25.863710300929224,0.7022030893897189,0.11797995277580095,6,4.2772327867721325,0.008495008365045943,0.102782991447036,1.0,True,
13,14,4.006260191078547,30.491284026126113,0.6955810147299509,0.1145595612270532,6,3.350289886810732,0.010228664633443074,0.03515966054944097,1.0,True,
13,15,0.9562774815922267,6.898518361717157,0.868300353819945,0.10454213568084784,6,3.243735713395398,0.0023750253827712867,0.010725047644197173,1.0,True,
13,16,3.565173336606111,18.944899794614482,0.7265456392027422,0.10962529664062398,6,3.522751424445623,0.008958927594995584,0.0304143851242741,1.0,True,
14,15,4.019575892829469,23.592765664408944,0.7120070334086913,0.11868441290330693,6,4.620592469502459,0.002571958018982041,0.05506919751152759,1.0,True,
14,16,7.5676649485439835,49.43618382074059,0.5918615984405458,0.12229328437386527,6,7.149509813179243,0.014273957859022303,0.25325650727956367,1.0,True,
14,17,5.910977627463022,0.8461207481731591,0.6694009445687298,0.12443900216431929,6,5.157741429696001,0.017895201000461415,0.10920228290609475,1.0,True,
15,16,3.7301261399251735,25.84341815633164,0.702887537993921,0.11495230769293868,6,3.540289976352534,0.013545291843393993,0.033466251783377816,1.0,True,
15,17,2.2049738368271745,24.438886412582093,0.7429531936901991,0.11679524427533879,6,3.526664394280145,0.00989411002791081,0.07786907370564648,1.0,True,
15,18,4.7000039832559155,3.452521908779401,0.7209645010046886,0.11716134583909153,6,4.125231895423432,0.011654729311847106,0.13683586564190353,1.0,True,
16,17,3.368526196086246,50.282304568913744,0.618922305764411,0.11254196340939995,6,4.068632188828396,0.03104786021350874,0.10375098145235381,1.0,True,
16,18,3.5240348999326185,22.39089624755224,0.6890156918687589,0.11084024896736888,6,4.42116710884217,0.01556225520371614,0.02495881796886513,1.0,True,
16,19,2.146829294717985,30.035090485266103,0.8685060899826,0.10135543575024519,6,2.92007223300188,0.002952251383831446,0.02753369989560042,1.0,True,
17,18,2.640403049812329,27.891408321361506,0.7697708305735859,0.10648049893472207,6,3.7928223564531667,0.010582276181446382,0.039599051949106026,1.0,True,
17,19,3.933985934417215,80.31739505417984,0.6293759512937596,0.1095449771750205,6,3.6483069293931876,0.012240937390583118,0.060306184678878015,1.0,True,
17,20,4.2254212089887,152.98392843416656,0.6014520938674964,0.12300562605352797,6,4.719447385686107,1.3272837904090529,0.4474442011368189,1.0,False,forward_reverse_translation
18,19,2.4460967931915643,52.425986732818345,0.6827314510833881,0.10834806615100012,6,3.5073857685652805,0.012418496053917759,0.11905018881098527,1.0,True,
18,20,6.593931011285688,125.09252011280485,0.5756313809779688,0.1265759478434112,6,5.389095141151797,0.012917057356045326,0.24042373011343365,1.0,True,
18,21,11.793089868757727,175.70238585456048,0.4044519656339495,0.14396876383759685,6,10.041635341774429,0.06610149517267662,0.6118050873537593,1.0,False,forward_reverse_rotation
19,20,6.650720121741557,72.6665333799865,0.6234734541714874,0.12775305804057488,6,5.958603794025073,0.009237066767190358,0.22540488888099894,1.0,True,
19,21,12.053580264031138,123.27639912174077,0.3788200074840963,0.14687803396129923,6,10.170069582593054,0.1645149893124099,1.1204113986936788,1.0,False,forward_reverse_translation;forward_reverse_rotation
19,22,14.836246959975925,128.85294276465592,0.3215252152521525,0.1505833656059155,6,15.25410245430046,0.08605091089946505,0.48945936193054473,1.0,False,heldout_inlier_ratio;forward_reverse_translation
20,21,5.40286046809103,50.60986574175429,0.6596992097884272,0.12147955429086157,6,4.0820327359553845,0.014725537493637062,0.1988422472286775,1.0,True,
20,22,8.200956565000565,56.18640938466938,0.5739414499308958,0.13255415786946786,6,5.328486341352201,0.006040617548520927,0.19706632354687328,1.0,True,
20,23,5.175118275082073,70.79178325235415,0.6456945156330087,0.12075756038041646,6,3.9642702950866386,0.022949011013485506,0.1911210947924365,1.0,True,
21,22,2.8644847627909416,5.57654364291509,0.7716237647919971,0.11632271358541554,6,2.79620153032919,0.00996357879827298,0.15332892195322273,1.0,True,
21,23,1.2936223973175418,20.18191751059984,0.8576224819696593,0.09865676260631216,6,3.0367950693845156,0.0039225733250952055,0.00898487649368532,1.0,True,
21,24,2.3128500583741403,54.59467208208593,0.7715940569126165,0.1136150455355286,6,2.76815824933335,0.007475673605589086,0.042277690012993266,1.0,True,
22,23,3.7194009250537223,14.605373867684753,0.7396689147762109,0.11702962848624102,6,3.4148047250889095,0.01814015943430581,0.11847321816106485,1.0,True,
22,24,4.786117710081478,49.01812843917083,0.6983240223463687,0.11898873157361621,6,3.6334655936865663,0.013438931050857202,0.08244161259412694,1.0,True,
22,25,2.3747421598149763,12.391903814042255,0.736861094407697,0.11586394710110075,6,2.383007054117407,0.018559552524775386,0.0645260279790244,1.0,True,
23,24,1.0880644725336985,34.41275457148609,0.7853164556962026,0.1128725442321241,6,2.409921828847164,0.002195759849115173,0.032112959181416705,1.0,True,
23,25,5.033935954106079,2.213470053642494,0.6881127450980392,0.12303206700403986,6,2.9363727218037994,0.004939188019100004,0.12964064637100806,1.0,True,
23,26,5.765534437288107,40.730927532824346,0.6852618757612667,0.12040544972155913,6,3.0590065093977192,0.006032445250250899,0.14039335223024835,1.0,True,
24,25,6.097212810998921,36.62622462512857,0.677667493796526,0.12346315805814134,6,3.6286524357748307,0.02347922689116974,0.16273859629352566,1.0,True,
24,26,6.852918693116795,6.318172961338249,0.6530209617755857,0.12710147612984257,6,3.5331859775372005,0.013311199903818141,0.18193579850152686,1.0,True,
24,27,7.47982907414204,31.60254826458195,0.6649014778325123,0.1248115961442785,6,4.10006355979974,0.017872821205486625,0.1680556511699705,1.0,True,
25,26,1.2882746074869595,42.944397586466835,0.9127837514934289,0.0958142916589384,6,2.9017658682436953,0.003534148840172682,0.018440169300173608,1.0,True,
25,27,1.7666235781584831,68.22877288971053,0.8510739856801909,0.10418150333394147,6,2.368253417089997,0.006103232696002816,0.13366555345652814,1.0,True,
25,28,2.1807182972588706,88.09106909546726,0.8853518429870751,0.10761251229652005,6,2.6372860976794645,0.003483231665912094,0.0369774420199415,1.0,True,
26,27,0.6269948654714946,25.284375303243706,0.9183867141162515,0.09074909570650921,6,2.8142315359282506,0.00040199505815422204,0.011554314408129918,1.0,True,
26,28,0.9431372257108486,45.14667150900044,0.8853200095170116,0.10604184939655073,6,2.656402027789519,0.007320240476189879,0.12930335868605478,1.0,True,
26,29,1.129925637779203,87.41573662125148,0.7880466815984911,0.10871274240898265,6,3.111957466886604,0.0063952430610542755,0.039517035902874385,1.0,True,
27,28,0.44809323479571145,19.862296205756735,0.9289448669201521,0.0850509551532673,6,2.96325286846982,0.005157655135591474,0.022666807877311387,1.0,True,
27,29,1.160064136065596,62.13136131800778,0.7872365477452019,0.10455513464835667,6,2.9621627623005296,0.011650639607012138,0.1624719134077532,1.0,True,
27,30,4.25412615059095,152.09392556214777,0.04792444029850746,0.15706827084964883,6,3.4115087753552786,2.5641513880069633,2.874944882076104,0.0,False,heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
28,29,1.5995723610319212,42.26906511225104,0.8000944621560987,0.1086547547358129,6,3.1466224100058553,0.0011394164967304414,0.02119627748719656,1.0,True,
28,30,4.295353381127667,132.23162935639104,0.042095416276894296,0.16025490727880456,6,4.631028587407634,2.5607552487652696,2.17230408479254,0.0,False,heldout_inlier_ratio;heldout_inlier_rmse;forward_reverse_translation;forward_reverse_rotation;multistart_instability
28,31,5.754426930327516,158.8421298011293,0.7465330381074466,0.1066966653439246,6,2.402598366822474,0.00635764905956918,0.040016615214377715,1.0,True,
29,30,4.099530130205247,89.96256424413995,0.7248812145092132,0.10870831469083347,6,2.9471223804737057,0.00465638537716095,0.12465763189227934,1.0,True,
29,31,4.890018768530818,116.57306468887772,0.7243012243012243,0.11138751941762699,6,2.7645021322697017,0.004263906321896464,0.046637396086304225,1.0,True,
29,32,4.464737187763289,159.39493219688632,0.6956070563818748,0.1103354081949904,6,3.227012588611917,0.011088061861347823,0.34561739459504637,1.0,True,
30,31,2.3862917603439455,26.61050044473775,0.8185562292643862,0.0961579573295129,6,2.899558208444643,0.004999163455591531,0.0213847958734735,1.0,True,
30,32,1.1507634071714652,69.43236795274659,0.8041343079031521,0.099533431536842,6,2.6146168742247573,0.003171376495143,0.02376737774843464,1.0,True,
30,33,2.4361832484951105,160.7145717128102,0.0824534345711235,0.13003518012314966,6,7.2913036106936,1.3743823383381788,1.9837113244583153,0.0,False,heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
31,32,1.2514382602180638,42.82186750800885,0.8157085941946499,0.09925944770258267,6,3.0485411400768245,0.004719596234887584,0.06146741991681224,1.0,True,
31,33,0.641507519046697,134.10407126807203,0.0911563017261764,0.12702324561674966,6,7.541483837366941,2.816294381538612,3.333105317420589,0.0,False,heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
32,33,1.4370687242949811,91.2822037600631,0.7569928006609229,0.10008740880870744,6,3.6611044282667207,2.988193350509204,2.696443494598379,1.0,False,forward_reverse_translation;forward_reverse_rotation
1 i j rtk_translation_m rtk_rotation_deg heldout_inlier_ratio heldout_inlier_rmse_m hessian_rank hessian_condition reverse_translation_m reverse_rotation_deg multistart_success_rate accepted rejection_reasons
2 0 1 1.8477087194158957 24.171297449440786 0.8061657032755298 0.10961296014103376 6 2.7038608113687213 0.004225163540003135 0.15304490677206398 1.0 True
3 0 2 2.633731568575307 80.09074797031298 0.7489394523717702 0.116305716193008 6 3.0720058333957385 0.02073003109723772 0.12418344306820694 1.0 True
4 0 3 6.923255970100826 79.91583883299243 0.6310283235519265 0.12470979645173065 6 5.235632990817995 0.02154775870989581 0.2539904497958252 1.0 True
5 1 2 1.7155362084417105 55.91945052087219 0.7867383512544803 0.11079021393934936 6 3.282529873989152 0.0076823267394430066 0.05080433004685343 1.0 True
6 1 3 5.885389341942907 55.74454138355163 0.6794562317367552 0.1190716913809658 6 4.183386002322129 0.024832426027852336 0.29364033194785566 1.0 True
7 1 4 2.3031736807956613 106.08652205569953 0.6786112833230006 0.11255015823152649 6 3.2941312581877575 0.007769137931169012 0.07270245476793177 1.0 True
8 2 3 4.339151683168145 0.17490913732054883 0.7586776859504132 0.11541610277862563 6 3.730803369806123 0.007879904085793275 0.11659483159946175 1.0 True
9 2 4 0.6150408096900305 50.167071534827386 0.7854572527608884 0.1098649389241601 6 2.641287751481569 0.017042953274278242 0.09383247792992147 1.0 True
10 2 5 5.894923525501676 8.735318060700383 0.7074574574574575 0.11912871456224478 6 3.8527093190832473 0.016641241123247028 0.24777402858141082 1.0 True
11 3 4 3.9735035126146885 50.34198067214791 0.6974624291697462 0.1172763088894914 6 3.5010457716923225 0.012148721219467648 0.2079308684382429 1.0 True
12 3 5 2.3619393178889707 8.910227198020932 0.7962985964476462 0.10646082199215777 6 3.0377458538379902 0.011008298723511818 0.08217077610411756 1.0 True
13 3 6 2.2300116834828536 47.63555775102664 0.8376509054325956 0.10956583416752592 6 3.1930663579156233 0.001222821038316072 0.09207521511761255 1.0 True
14 4 5 5.370771070097231 41.431753474126985 0.6652516676773802 0.12322094568314959 6 4.985227704290952 0.005399786589867114 0.1389350777589359 1.0 True
15 4 6 6.142548765456278 97.97753842317456 0.6529585072428186 0.11878586101480827 6 5.324803753668097 0.011377811219747914 0.20418431358287809 1.0 True
16 4 7 5.314106070657687 123.28910472998356 0.6964418087472202 0.11691192595448072 6 4.414726450762486 0.017641796253111963 0.10886712169052283 1.0 True
17 5 6 1.9357867378988893 56.54578494904757 0.7526921648718901 0.10924852668609378 6 3.289802074469556 0.012374747586501101 0.14461856068326442 1.0 True
18 5 7 0.19501559913675365 81.85735125585654 0.7833561729164071 0.11683079277948674 6 2.8807765869032655 0.0136898748124479 0.2000656277647698 1.0 True
19 5 8 1.8038807059856978 172.47951556359365 0.7410703250525275 0.11436945722491815 6 3.9768956884456648 0.020336999081352437 0.07548452187379719 1.0 True
20 6 7 2.130456053070114 25.311566306808988 0.8497729566094854 0.10415909908074772 6 3.375936129784756 0.008986805974948919 0.1309947950641294 1.0 True
21 6 8 0.24494500622098103 115.93373061454483 0.7678928928928929 0.11166725079781287 6 3.2609205733873607 0.010085391730565987 0.11752836996157842 1.0 True
22 6 9 8.025985916230132 103.90638727582186 0.6071384156199477 0.12594282886521954 6 6.882498483502561 0.01655440444551025 0.4345945520890901 1.0 True
23 7 8 1.9962664218365056 90.62216430773583 0.8299748110831234 0.10748525688830209 6 3.0071179226782414 0.007106461978852689 0.10698728473689886 1.0 True
24 7 9 7.8811064994361235 78.59482096901284 0.6188509200150206 0.1271320507839345 6 6.716979637303383 0.026230622734929154 0.28112746782878123 1.0 True
25 7 10 7.620696678413973 134.60031955305035 0.5757088027733069 0.1313446077529101 6 5.755535843660075 0.012710332356906473 0.2297418063823675 1.0 True
26 8 9 7.803713858547152 12.027343338722998 0.6326834719980131 0.12084265385763356 6 5.539689880493177 0.02518064403521875 0.3859647522318924 1.0 True
27 8 10 8.062504137991457 43.97815524531457 0.6163861933423412 0.12936028463965984 6 4.789005902863088 0.011327540721525892 0.17059480981566058 1.0 True
28 8 11 11.084531710563947 22.606708130954026 0.5371195721380364 0.13463285672049757 6 5.833003178860405 0.017887959781482814 0.14919899299178907 1.0 True
29 9 10 2.04815062353057 56.00549858403758 0.6543345543345543 0.10729272360686735 6 3.3788563349731584 0.008725655639009402 0.02870462611907292 1.0 True
30 9 11 4.738677237611319 34.634051469677026 0.5818780055682106 0.11717687815100752 6 3.555986532172074 0.009813025835682346 0.0603546019895227 1.0 True
31 9 12 7.170741483679294 16.35622721712263 0.5379123584441162 0.12645695585785732 6 4.882558096197038 0.008862930161052695 0.11566409877698863 1.0 True
32 10 11 3.2047552083250137 21.371447114360556 0.7118898623279099 0.1192865608074921 6 3.2202034715576238 0.0029529340190147615 0.0041769080934441144 1.0 True
33 10 12 6.291813977735496 39.64927136691496 0.6120311738918656 0.1252565212812615 6 4.691686416856199 0.005686910809265337 0.10204044727301474 1.0 True
34 10 13 10.199392557022867 72.41150002956134 0.516551290119572 0.1354903305714049 6 7.346543684414892 0.01356504272158301 0.3255230434099281 1.0 True
35 11 12 3.467398797536633 18.277824252554396 0.650555275113579 0.12104882943540958 6 4.612247786063486 0.003583199374499245 0.03300207707174736 1.0 True
36 11 13 7.516502113110916 51.04005291520078 0.5698054068172914 0.12858662743221627 6 7.539783468898054 0.016001435752891854 0.11059625579949509 1.0 True
37 11 14 3.767517331528496 20.548768889074672 0.6420881321982974 0.12414062948335584 6 4.9522650472668115 0.012369101516230236 0.018705060406060074 1.0 True
38 12 13 4.049286119591895 32.762228662646386 0.6972966112450819 0.11680896213116294 6 4.282247993741361 0.011410238767832601 0.04779713993430858 1.0 True
39 12 14 0.97948616772873 2.2709446365202806 0.8749086479902558 0.09521299965540617 6 3.309695139564419 0.006159472215773642 0.014929948455572307 1.0 True
40 12 15 4.286747470271891 25.863710300929224 0.7022030893897189 0.11797995277580095 6 4.2772327867721325 0.008495008365045943 0.102782991447036 1.0 True
41 13 14 4.006260191078547 30.491284026126113 0.6955810147299509 0.1145595612270532 6 3.350289886810732 0.010228664633443074 0.03515966054944097 1.0 True
42 13 15 0.9562774815922267 6.898518361717157 0.868300353819945 0.10454213568084784 6 3.243735713395398 0.0023750253827712867 0.010725047644197173 1.0 True
43 13 16 3.565173336606111 18.944899794614482 0.7265456392027422 0.10962529664062398 6 3.522751424445623 0.008958927594995584 0.0304143851242741 1.0 True
44 14 15 4.019575892829469 23.592765664408944 0.7120070334086913 0.11868441290330693 6 4.620592469502459 0.002571958018982041 0.05506919751152759 1.0 True
45 14 16 7.5676649485439835 49.43618382074059 0.5918615984405458 0.12229328437386527 6 7.149509813179243 0.014273957859022303 0.25325650727956367 1.0 True
46 14 17 5.910977627463022 0.8461207481731591 0.6694009445687298 0.12443900216431929 6 5.157741429696001 0.017895201000461415 0.10920228290609475 1.0 True
47 15 16 3.7301261399251735 25.84341815633164 0.702887537993921 0.11495230769293868 6 3.540289976352534 0.013545291843393993 0.033466251783377816 1.0 True
48 15 17 2.2049738368271745 24.438886412582093 0.7429531936901991 0.11679524427533879 6 3.526664394280145 0.00989411002791081 0.07786907370564648 1.0 True
49 15 18 4.7000039832559155 3.452521908779401 0.7209645010046886 0.11716134583909153 6 4.125231895423432 0.011654729311847106 0.13683586564190353 1.0 True
50 16 17 3.368526196086246 50.282304568913744 0.618922305764411 0.11254196340939995 6 4.068632188828396 0.03104786021350874 0.10375098145235381 1.0 True
51 16 18 3.5240348999326185 22.39089624755224 0.6890156918687589 0.11084024896736888 6 4.42116710884217 0.01556225520371614 0.02495881796886513 1.0 True
52 16 19 2.146829294717985 30.035090485266103 0.8685060899826 0.10135543575024519 6 2.92007223300188 0.002952251383831446 0.02753369989560042 1.0 True
53 17 18 2.640403049812329 27.891408321361506 0.7697708305735859 0.10648049893472207 6 3.7928223564531667 0.010582276181446382 0.039599051949106026 1.0 True
54 17 19 3.933985934417215 80.31739505417984 0.6293759512937596 0.1095449771750205 6 3.6483069293931876 0.012240937390583118 0.060306184678878015 1.0 True
55 17 20 4.2254212089887 152.98392843416656 0.6014520938674964 0.12300562605352797 6 4.719447385686107 1.3272837904090529 0.4474442011368189 1.0 False forward_reverse_translation
56 18 19 2.4460967931915643 52.425986732818345 0.6827314510833881 0.10834806615100012 6 3.5073857685652805 0.012418496053917759 0.11905018881098527 1.0 True
57 18 20 6.593931011285688 125.09252011280485 0.5756313809779688 0.1265759478434112 6 5.389095141151797 0.012917057356045326 0.24042373011343365 1.0 True
58 18 21 11.793089868757727 175.70238585456048 0.4044519656339495 0.14396876383759685 6 10.041635341774429 0.06610149517267662 0.6118050873537593 1.0 False forward_reverse_rotation
59 19 20 6.650720121741557 72.6665333799865 0.6234734541714874 0.12775305804057488 6 5.958603794025073 0.009237066767190358 0.22540488888099894 1.0 True
60 19 21 12.053580264031138 123.27639912174077 0.3788200074840963 0.14687803396129923 6 10.170069582593054 0.1645149893124099 1.1204113986936788 1.0 False forward_reverse_translation;forward_reverse_rotation
61 19 22 14.836246959975925 128.85294276465592 0.3215252152521525 0.1505833656059155 6 15.25410245430046 0.08605091089946505 0.48945936193054473 1.0 False heldout_inlier_ratio;forward_reverse_translation
62 20 21 5.40286046809103 50.60986574175429 0.6596992097884272 0.12147955429086157 6 4.0820327359553845 0.014725537493637062 0.1988422472286775 1.0 True
63 20 22 8.200956565000565 56.18640938466938 0.5739414499308958 0.13255415786946786 6 5.328486341352201 0.006040617548520927 0.19706632354687328 1.0 True
64 20 23 5.175118275082073 70.79178325235415 0.6456945156330087 0.12075756038041646 6 3.9642702950866386 0.022949011013485506 0.1911210947924365 1.0 True
65 21 22 2.8644847627909416 5.57654364291509 0.7716237647919971 0.11632271358541554 6 2.79620153032919 0.00996357879827298 0.15332892195322273 1.0 True
66 21 23 1.2936223973175418 20.18191751059984 0.8576224819696593 0.09865676260631216 6 3.0367950693845156 0.0039225733250952055 0.00898487649368532 1.0 True
67 21 24 2.3128500583741403 54.59467208208593 0.7715940569126165 0.1136150455355286 6 2.76815824933335 0.007475673605589086 0.042277690012993266 1.0 True
68 22 23 3.7194009250537223 14.605373867684753 0.7396689147762109 0.11702962848624102 6 3.4148047250889095 0.01814015943430581 0.11847321816106485 1.0 True
69 22 24 4.786117710081478 49.01812843917083 0.6983240223463687 0.11898873157361621 6 3.6334655936865663 0.013438931050857202 0.08244161259412694 1.0 True
70 22 25 2.3747421598149763 12.391903814042255 0.736861094407697 0.11586394710110075 6 2.383007054117407 0.018559552524775386 0.0645260279790244 1.0 True
71 23 24 1.0880644725336985 34.41275457148609 0.7853164556962026 0.1128725442321241 6 2.409921828847164 0.002195759849115173 0.032112959181416705 1.0 True
72 23 25 5.033935954106079 2.213470053642494 0.6881127450980392 0.12303206700403986 6 2.9363727218037994 0.004939188019100004 0.12964064637100806 1.0 True
73 23 26 5.765534437288107 40.730927532824346 0.6852618757612667 0.12040544972155913 6 3.0590065093977192 0.006032445250250899 0.14039335223024835 1.0 True
74 24 25 6.097212810998921 36.62622462512857 0.677667493796526 0.12346315805814134 6 3.6286524357748307 0.02347922689116974 0.16273859629352566 1.0 True
75 24 26 6.852918693116795 6.318172961338249 0.6530209617755857 0.12710147612984257 6 3.5331859775372005 0.013311199903818141 0.18193579850152686 1.0 True
76 24 27 7.47982907414204 31.60254826458195 0.6649014778325123 0.1248115961442785 6 4.10006355979974 0.017872821205486625 0.1680556511699705 1.0 True
77 25 26 1.2882746074869595 42.944397586466835 0.9127837514934289 0.0958142916589384 6 2.9017658682436953 0.003534148840172682 0.018440169300173608 1.0 True
78 25 27 1.7666235781584831 68.22877288971053 0.8510739856801909 0.10418150333394147 6 2.368253417089997 0.006103232696002816 0.13366555345652814 1.0 True
79 25 28 2.1807182972588706 88.09106909546726 0.8853518429870751 0.10761251229652005 6 2.6372860976794645 0.003483231665912094 0.0369774420199415 1.0 True
80 26 27 0.6269948654714946 25.284375303243706 0.9183867141162515 0.09074909570650921 6 2.8142315359282506 0.00040199505815422204 0.011554314408129918 1.0 True
81 26 28 0.9431372257108486 45.14667150900044 0.8853200095170116 0.10604184939655073 6 2.656402027789519 0.007320240476189879 0.12930335868605478 1.0 True
82 26 29 1.129925637779203 87.41573662125148 0.7880466815984911 0.10871274240898265 6 3.111957466886604 0.0063952430610542755 0.039517035902874385 1.0 True
83 27 28 0.44809323479571145 19.862296205756735 0.9289448669201521 0.0850509551532673 6 2.96325286846982 0.005157655135591474 0.022666807877311387 1.0 True
84 27 29 1.160064136065596 62.13136131800778 0.7872365477452019 0.10455513464835667 6 2.9621627623005296 0.011650639607012138 0.1624719134077532 1.0 True
85 27 30 4.25412615059095 152.09392556214777 0.04792444029850746 0.15706827084964883 6 3.4115087753552786 2.5641513880069633 2.874944882076104 0.0 False heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
86 28 29 1.5995723610319212 42.26906511225104 0.8000944621560987 0.1086547547358129 6 3.1466224100058553 0.0011394164967304414 0.02119627748719656 1.0 True
87 28 30 4.295353381127667 132.23162935639104 0.042095416276894296 0.16025490727880456 6 4.631028587407634 2.5607552487652696 2.17230408479254 0.0 False heldout_inlier_ratio;heldout_inlier_rmse;forward_reverse_translation;forward_reverse_rotation;multistart_instability
88 28 31 5.754426930327516 158.8421298011293 0.7465330381074466 0.1066966653439246 6 2.402598366822474 0.00635764905956918 0.040016615214377715 1.0 True
89 29 30 4.099530130205247 89.96256424413995 0.7248812145092132 0.10870831469083347 6 2.9471223804737057 0.00465638537716095 0.12465763189227934 1.0 True
90 29 31 4.890018768530818 116.57306468887772 0.7243012243012243 0.11138751941762699 6 2.7645021322697017 0.004263906321896464 0.046637396086304225 1.0 True
91 29 32 4.464737187763289 159.39493219688632 0.6956070563818748 0.1103354081949904 6 3.227012588611917 0.011088061861347823 0.34561739459504637 1.0 True
92 30 31 2.3862917603439455 26.61050044473775 0.8185562292643862 0.0961579573295129 6 2.899558208444643 0.004999163455591531 0.0213847958734735 1.0 True
93 30 32 1.1507634071714652 69.43236795274659 0.8041343079031521 0.099533431536842 6 2.6146168742247573 0.003171376495143 0.02376737774843464 1.0 True
94 30 33 2.4361832484951105 160.7145717128102 0.0824534345711235 0.13003518012314966 6 7.2913036106936 1.3743823383381788 1.9837113244583153 0.0 False heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
95 31 32 1.2514382602180638 42.82186750800885 0.8157085941946499 0.09925944770258267 6 3.0485411400768245 0.004719596234887584 0.06146741991681224 1.0 True
96 31 33 0.641507519046697 134.10407126807203 0.0911563017261764 0.12702324561674966 6 7.541483837366941 2.816294381538612 3.333105317420589 0.0 False heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
97 32 33 1.4370687242949811 91.2822037600631 0.7569928006609229 0.10008740880870744 6 3.6611044282667207 2.988193350509204 2.696443494598379 1.0 False forward_reverse_translation;forward_reverse_rotation
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,414 @@
{
"schema_version": 2,
"success": true,
"message": "`ftol` termination condition is satisfied.",
"convention": "T_body_lidar maps raw LiDAR points into rear-axle body frame",
"equation": "A_ij X = X B_ij",
"measured_extrinsic_used_as_initial": false,
"translation_m": [
1.2975484155117711,
-0.0014693441955134902,
0.7052088406121522
],
"rotation_rpy_deg_xyz": [
-0.8301548470744382,
1.3787117674135543,
-0.9873301733452331
],
"quaternion_xyzw": [
-0.007139952974435751,
0.012092890421885221,
-0.008527968948116832,
0.9998650192992987
],
"matrix_4x4": [
[
0.9995620714937284,
0.01688095033591077,
0.02430429482463916,
1.2975484155117711
],
[
-0.017226321011659856,
0.9997525896342856,
0.014071722849138928,
-0.0014693441955134902
],
[
-0.024060737635611132,
-0.014484234025182223,
0.9996055661455342,
0.7052088406121522
],
[
0.0,
0.0,
0.0,
1.0
]
],
"estimation": {
"stations": 34,
"pairs": 42,
"residuals": {
"pairs": 42,
"translation_m": {
"rms": 0.12183423858445137,
"median": 0.060649486998667235,
"p90": 0.11912435755512873,
"p95": 0.3030566180355447,
"max": 0.45978165540815585
},
"rotation_deg": {
"rms": 1.0805282705084651,
"median": 0.7524132662595089,
"p90": 1.482445248533153,
"p95": 1.8550286425106393,
"max": 4.3358524167513774
},
"per_pair": [
{
"pair_index": 0,
"translation_m": 0.038624527608401314,
"rotation_deg": 0.8023305035396044
},
{
"pair_index": 1,
"translation_m": 0.04180287320767011,
"rotation_deg": 0.6893264946463747
},
{
"pair_index": 2,
"translation_m": 0.019127950093528003,
"rotation_deg": 1.0042991942789465
},
{
"pair_index": 3,
"translation_m": 0.06137084310587195,
"rotation_deg": 0.3949193126839051
},
{
"pair_index": 4,
"translation_m": 0.1071111410675142,
"rotation_deg": 0.29878675978634545
},
{
"pair_index": 5,
"translation_m": 0.06364016847596804,
"rotation_deg": 0.5544336747047969
},
{
"pair_index": 6,
"translation_m": 0.06293697736756677,
"rotation_deg": 0.5686016770604462
},
{
"pair_index": 7,
"translation_m": 0.024970034635953178,
"rotation_deg": 0.5027938164737027
},
{
"pair_index": 8,
"translation_m": 0.06609908977135717,
"rotation_deg": 0.769573579451918
},
{
"pair_index": 9,
"translation_m": 0.015198604597359418,
"rotation_deg": 0.7395239518119227
},
{
"pair_index": 10,
"translation_m": 0.08603113369700931,
"rotation_deg": 0.38160978353264396
},
{
"pair_index": 11,
"translation_m": 0.023734833308603348,
"rotation_deg": 0.5279761588421565
},
{
"pair_index": 12,
"translation_m": 0.044728554479905425,
"rotation_deg": 0.8691250339782107
},
{
"pair_index": 13,
"translation_m": 0.07088160554682986,
"rotation_deg": 0.450151294217732
},
{
"pair_index": 14,
"translation_m": 0.08888554602585935,
"rotation_deg": 0.6592285262192215
},
{
"pair_index": 15,
"translation_m": 0.05233056995457292,
"rotation_deg": 1.8589142482602727
},
{
"pair_index": 16,
"translation_m": 0.12686760514047216,
"rotation_deg": 1.9773175636228766
},
{
"pair_index": 17,
"translation_m": 0.06853778836627006,
"rotation_deg": 0.2507622154612463
},
{
"pair_index": 18,
"translation_m": 0.0613281598931951,
"rotation_deg": 0.3396141740929532
},
{
"pair_index": 19,
"translation_m": 0.03461219125828727,
"rotation_deg": 0.5120113019840405
},
{
"pair_index": 20,
"translation_m": 0.003516478473153868,
"rotation_deg": 0.3641614482709119
},
{
"pair_index": 21,
"translation_m": 0.059970814104139375,
"rotation_deg": 0.61595189426212
},
{
"pair_index": 22,
"translation_m": 0.011156452422652521,
"rotation_deg": 0.39742394993610364
},
{
"pair_index": 23,
"translation_m": 0.04780738274324129,
"rotation_deg": 1.7812021332676105
},
{
"pair_index": 24,
"translation_m": 0.0580893770008635,
"rotation_deg": 0.879332260016813
},
{
"pair_index": 25,
"translation_m": 0.12024661823169551,
"rotation_deg": 4.3358524167513774
},
{
"pair_index": 26,
"translation_m": 0.06196910794842297,
"rotation_deg": 1.5238281010443262
},
{
"pair_index": 27,
"translation_m": 0.0503874055448458,
"rotation_deg": 0.972956387575872
},
{
"pair_index": 28,
"translation_m": 0.01757080571201053,
"rotation_deg": 0.8111658366205472
},
{
"pair_index": 29,
"translation_m": 0.032044188496992296,
"rotation_deg": 0.943436351113904
},
{
"pair_index": 30,
"translation_m": 0.05418626153418599,
"rotation_deg": 0.5111200883804832
},
{
"pair_index": 31,
"translation_m": 0.02622969795541729,
"rotation_deg": 0.26503765103607685
},
{
"pair_index": 32,
"translation_m": 0.10034938173040396,
"rotation_deg": 0.8910089412227198
},
{
"pair_index": 33,
"translation_m": 0.024232025179701657,
"rotation_deg": 0.2983628682948933
},
{
"pair_index": 34,
"translation_m": 0.06450981390919311,
"rotation_deg": 1.1099995759326005
},
{
"pair_index": 35,
"translation_m": 0.06841378002467255,
"rotation_deg": 0.765302580707095
},
{
"pair_index": 36,
"translation_m": 0.45978165540815585,
"rotation_deg": 0.8318777085643659
},
{
"pair_index": 37,
"translation_m": 0.31232972397739145,
"rotation_deg": 0.7893009066081454
},
{
"pair_index": 38,
"translation_m": 0.3910017597095041,
"rotation_deg": 0.6583887677988087
},
{
"pair_index": 39,
"translation_m": 0.1015358221759577,
"rotation_deg": 0.8428062435042994
},
{
"pair_index": 40,
"translation_m": 0.1090240114660279,
"rotation_deg": 0.8418950507797021
},
{
"pair_index": 41,
"translation_m": 0.03539906786781987,
"rotation_deg": 0.7658972874383352
}
]
}
},
"ground": {
"planes": 34,
"body_origin_height_above_ground_m": 0.2335,
"formula": "d_lidar - (R_X n_lidar)^T t_X - body_height"
},
"linearized_one_sigma": {
"translation_m": [
0.007068161359903731,
0.007140938677742994,
0.00460528013456401
],
"rotation_deg": [
0.07009913972450056,
0.0632417549466229,
0.13781143232539678
],
"warning": "conditional local estimate; bootstrap is the primary stability check"
},
"weighted_jacobian_condition_number": 6.8653146384480275,
"solver_multistart": {
"runs": 12,
"candidates_relative_to_best": [
{
"cost": 165.05915287927994,
"success": true,
"translation_m": 3.0576433724414325e-08,
"rotation_deg": 1.5435010182701515e-07
},
{
"cost": 165.05915287928036,
"success": true,
"translation_m": 3.132972747129673e-08,
"rotation_deg": 1.5835217811052665e-07
},
{
"cost": 165.0591528792959,
"success": true,
"translation_m": 5.032571008409966e-08,
"rotation_deg": 6.019987077228086e-07
},
{
"cost": 165.0591528792966,
"success": true,
"translation_m": 5.0134365128719625e-08,
"rotation_deg": 6.036593761377996e-07
},
{
"cost": 165.059152879271,
"success": true,
"translation_m": 4.0000992845476256e-09,
"rotation_deg": 5.9504483976085874e-08
},
{
"cost": 165.0591528792741,
"success": true,
"translation_m": 1.71650324878148e-08,
"rotation_deg": 2.1433774624387286e-07
},
{
"cost": 165.059152879271,
"success": true,
"translation_m": 3.8510946546090955e-09,
"rotation_deg": 5.7484467409642816e-08
},
{
"cost": 165.05915287927274,
"success": true,
"translation_m": 1.2609667860271796e-08,
"rotation_deg": 1.3972470982253386e-07
},
{
"cost": 165.05915287929693,
"success": true,
"translation_m": 5.0359957454032455e-08,
"rotation_deg": 6.053505895170472e-07
},
{
"cost": 165.05915287927098,
"success": true,
"translation_m": 0.0,
"rotation_deg": 0.0
},
{
"cost": 165.0591528792721,
"success": true,
"translation_m": 1.123025431013551e-08,
"rotation_deg": 3.014422244971439e-08
},
{
"cost": 165.0591528792711,
"success": true,
"translation_m": 2.4200230224271075e-09,
"rotation_deg": 6.741856490734345e-08
}
]
},
"bootstrap": {
"runs": 100,
"order": [
"x_m",
"y_m",
"z_m",
"roll_deg",
"pitch_deg",
"yaw_deg"
],
"std": [
0.0030749822448559927,
0.0034380377435498433,
0.0016315909218529902,
0.07586073216888294,
0.07187134364073751,
0.11146702425116647
],
"p025": [
1.292007464312339,
-0.006797525226093335,
0.701848741787294,
-0.9789682101604196,
1.2351532937224006,
-1.211682139162798
],
"p975": [
1.3029347232892419,
0.005407572756192536,
0.7084594772174496,
-0.705679642362121,
1.5258122853862286,
-0.8045746213351143
]
}
}
@@ -0,0 +1,156 @@
i,j,rtk_translation_m,rtk_rotation_deg,heldout_inlier_ratio,heldout_inlier_rmse_m,hessian_rank,hessian_condition,reverse_translation_m,reverse_rotation_deg,multistart_success_rate,accepted,rejection_reasons
0,1,1.8477087194158957,24.171297449440786,0.8193962748876044,0.11049677170760565,6,14.01384280930539,0.026680306344951596,0.12400141971813113,1.0,True,
0,2,2.633731568575307,80.09074797031298,0.7525388867463684,0.11492533799497681,6,14.962108606915407,0.0018465318763874656,0.03093450642761156,1.0,True,
0,3,6.923255970100826,79.91583883299243,0.628093901505486,0.12365050970311489,6,23.49058941554809,0.010524333328229862,0.23898233567763014,1.0,True,
0,4,2.952974950373882,130.25781950514033,0.693351593625498,0.11485738628517483,6,16.30512452170669,2.3214755241040135,1.4768350244766943,1.0,False,forward_reverse_translation;forward_reverse_rotation
0,5,8.205074921973447,88.82606603101337,0.6015065913370998,0.12959862169013578,6,26.276485388438196,0.013711901937629547,0.26024107991311357,1.0,True,
1,2,1.7155362084417105,55.91945052087219,0.7981310803891449,0.11167434332282761,6,13.484382276710292,0.050742368484802715,0.5170998105709517,1.0,False,forward_reverse_rotation
1,3,5.885389341942907,55.74454138355163,0.678820988438572,0.12109867185657652,6,27.478714016209608,0.008324315958289934,0.25689852176843175,1.0,True,
1,4,2.3031736807956613,106.08652205569953,0.6772473651580905,0.11157492180388107,6,13.25032679930311,0.00802452890723869,0.011334867255723058,1.0,True,
1,5,7.590375029412286,64.65476858157257,0.618779694923731,0.12786963556791484,6,21.376356908960595,0.012044684321695526,0.5185219918090508,1.0,False,forward_reverse_rotation
1,6,8.114848491620153,8.108983632525,0.6933789087226231,0.12162612003144421,6,23.560279629760274,0.008258211706006158,0.08942722541817279,1.0,True,
2,3,4.339151683168145,0.17490913732054883,0.7609663064208518,0.11583878636897658,6,13.299472485774093,0.00784220284106163,0.01711532312869618,1.0,True,
2,4,0.6150408096900305,50.167071534827386,0.796748976299789,0.11484342359047915,6,12.041666425070249,0.011210942709507262,0.11462542679280045,1.0,True,
2,5,5.894923525501676,8.735318060700383,0.7112112112112112,0.12001317983504071,6,15.619771158055366,0.011298647069299764,0.02158504824207679,1.0,True,
2,6,6.5470565092896145,47.810466888347186,0.7254529329785886,0.11794151013960824,6,22.73936753420594,0.004729020804343234,0.026463705119922912,1.0,True,
2,7,5.848768648120739,73.12203319515616,0.7569187603621987,0.11528530285715083,6,13.439936375510058,0.011994525026310043,0.05882394078371607,1.0,True,
3,4,3.9735035126146885,50.34198067214791,0.6957378664695738,0.11619002437046372,6,17.769677378118345,0.006018969660459514,0.057234124307389854,1.0,True,
3,5,2.3619393178889707,8.910227198020932,0.7989069680784996,0.1077590577814381,6,12.151555874812605,0.01353229173052898,0.06486032517688407,1.0,True,
3,6,2.2300116834828536,47.63555775102664,0.8435613682092555,0.11222309863990207,6,13.914901775514604,0.009512765198853305,0.09743636874085052,1.0,True,
3,7,2.450274232383144,72.94712405783562,0.8651898734177215,0.11184568072686094,6,12.975777248765134,0.010827690226297962,0.12520280777371934,1.0,True,
3,8,1.9936391786054533,163.56928836557165,0.825590155700653,0.10787987267070262,6,12.959410142765178,0.0056088079192386986,0.021536601402145895,1.0,True,
4,5,5.370771070097231,41.431753474126985,0.6652516676773802,0.12444356767629798,6,17.0278455355532,0.003155412913416372,0.05824374716659687,1.0,True,
4,6,6.142548765456278,97.97753842317456,0.6551681807021851,0.12030857820705748,6,24.380739297719824,0.011212969141455763,0.08373658947114908,0.5,True,
4,7,5.314106070657687,123.28910472998356,0.696936001976773,0.11799538994568655,6,16.89426071448657,0.00409529177569414,0.0519770010059835,0.5,True,
4,8,5.8981084980454765,146.08873096228052,0.7129198332924737,0.11349036082807595,6,18.616925126379197,0.01249802665331653,0.04549823831834878,1.0,True,
4,9,3.1650365894055956,158.11607430100375,0.6097234068478128,0.11677937926620108,6,25.94675052408701,0.00616984405743089,0.11548555225177864,0.5,True,
5,6,1.9357867378988893,56.54578494904757,0.7604901596732269,0.11101741624951145,6,16.791344333152654,0.01028641250952863,0.031127073736479716,1.0,True,
5,7,0.19501559913675365,81.85735125585654,0.8092687180764918,0.10777871969807898,6,15.20338641054919,0.010579714886860828,0.03333492483252218,1.0,True,
5,8,1.8038807059856978,172.47951556359365,0.7295760721789643,0.11311887218463837,6,17.053462425193878,0.009759205787181369,0.05935114015788255,1.0,True,
5,9,7.881613621164215,160.45217222486949,0.514987714987715,0.13057312144553648,6,36.76355827214787,0.013423709236357283,0.8456480282562385,1.0,False,forward_reverse_rotation
5,10,7.671857805815354,143.54232919109316,0.5442391832766165,0.13173165083201857,6,26.337222871823027,0.00523999417676037,0.17134499262937464,1.0,True,
6,7,2.130456053070114,25.311566306808988,0.8539354187689203,0.10462253085153163,6,12.502910766598342,0.002843314342407394,0.028424505305675103,1.0,True,
6,8,0.24494500622098103,115.93373061454483,0.7757757757757757,0.1131640002846509,6,15.521747346102597,0.007224674181694773,0.10395594559619498,1.0,True,
6,9,8.025985916230132,103.90638727582186,0.6009202835468226,0.1259448851291812,6,28.795189977892573,0.007975110549369148,0.07338758946554978,1.0,True,
6,10,8.303463402700086,159.91188585985975,0.5363513347275187,0.13083118553166714,6,31.167661263990606,1.7146158775247784,14.688249303878628,1.0,False,forward_reverse_translation;forward_reverse_rotation
6,11,11.328882063792355,138.54043874549896,0.4633337584491774,0.13769942152040132,6,58.377014583638996,0.023339879627157865,0.18888808553225306,1.0,True,
7,8,1.9962664218365056,90.62216430773583,0.8340050377833753,0.11132245152738919,6,16.170350777028464,0.004806949653405585,0.02034039865601464,1.0,True,
7,9,7.8811064994361235,78.59482096901284,0.6160971335586432,0.12637042711002067,6,24.17866477904224,0.012112046262530643,0.5927664140170301,1.0,False,forward_reverse_rotation
7,10,7.620696678413973,134.60031955305035,0.5721183607775164,0.13040415535689726,6,20.571872802604858,0.010507785715791607,0.10324111221368969,1.0,True,
7,11,10.356268683459426,113.22887243868989,0.5179098728976762,0.13593896500168,6,36.18276452817704,0.01570096854689765,0.062213006599939585,1.0,True,
7,12,13.76202076313695,94.9510481861355,0.419173636250156,0.14753718360891266,6,89.28960611274712,0.018245717875293024,0.41212759156893974,1.0,True,
8,9,7.803713858547152,12.027343338722998,0.6407549981373402,0.12189583104066963,6,28.06713348388497,0.025170709673794655,0.14112050756711447,1.0,True,
8,10,8.062504137991457,43.97815524531457,0.6075420709986488,0.12933255373443525,6,20.429632894689156,0.01256038832173067,0.12386976869589704,1.0,True,
8,11,11.084531710563947,22.606708130954026,0.5447599643448364,0.13547919137380177,6,27.72840331379869,0.006462771421800638,0.08629803406158434,1.0,True,
8,12,14.350449135419003,4.328883878399632,0.46489164086687307,0.14530908872417966,6,37.958964738084276,0.00955628888567663,0.1112688142308669,1.0,True,
8,13,18.245302761704593,28.43334478424675,0.37832991803278687,0.15442802982944018,6,101.4897081447731,0.027875630486669568,0.37387837894914167,1.0,True,
9,10,2.04815062353057,56.00549858403758,0.6576312576312576,0.10693844153477818,6,15.957672354569297,0.0038184479242649575,0.016135929192430513,0.5,True,
9,11,4.738677237611319,34.634051469677026,0.5883320678309288,0.11815838246331245,6,21.526672921842792,0.008866435423074387,0.05039949658393549,1.0,True,
9,12,7.170741483679294,16.35622721712263,0.5413589364844904,0.12469816852157387,6,38.0354745717217,0.03403608365914784,0.249719392637206,1.0,True,
9,13,10.733928030180744,16.40600144552375,0.5075728649611811,0.13356609480985904,6,50.3483756147633,0.0053243668825205025,0.1401976908512776,1.0,True,
9,14,7.8912187855633125,14.085282580602351,0.5416463116756228,0.12859551377809317,6,26.721469573230642,0.011377143482081005,0.045883349808202654,1.0,True,
10,11,3.2047552083250137,21.371447114360556,0.7131414267834794,0.12095106901516857,6,13.404202373771971,0.006897671932212844,0.18540056788520093,1.0,True,
10,12,6.291813977735496,39.64927136691496,0.6117876278616659,0.125002241212049,6,19.806559061723195,0.006509808900811586,0.06742381610992963,1.0,True,
10,13,10.199392557022867,72.41150002956134,0.5244808055380743,0.13689263771908436,6,36.18235272080672,0.0046518429660042555,0.17931102925270673,1.0,True,
10,14,6.819808471778838,41.920216003435236,0.5996858385693572,0.12955691635611957,6,17.916357473627098,0.009593578345611205,0.05753783232412823,1.0,True,
10,15,10.561163834622734,65.51298166784417,0.5048970366649924,0.13966622273060364,6,30.173625507677322,0.006074999657630774,0.04867559411570644,1.0,True,
11,12,3.467398797536633,18.277824252554396,0.6508076728924785,0.12017753597340274,6,15.211760062254479,0.016302173123952383,0.05141023051579345,1.0,True,
11,13,7.516502113110916,51.04005291520078,0.5666710199817161,0.12966061077144844,6,26.14207974844453,0.010250004424021303,0.0630753341579125,1.0,True,
11,14,3.767517331528496,20.548768889074672,0.64271407110666,0.12109534664165014,6,14.04589685067402,0.007610676428698485,0.05350102444543152,1.0,True,
11,15,7.712397127778454,44.141534553483616,0.570479416362689,0.12836105648377344,6,18.857231656557765,0.006898635416764969,0.028532809525243653,1.0,True,
11,16,11.0395297023812,69.98495270981527,0.4761423882857864,0.13515636052103228,6,45.4042112112498,0.037021121414326036,0.5129154434742894,1.0,False,forward_reverse_rotation
12,13,4.049286119591895,32.762228662646386,0.7056733087955325,0.11689859246507509,6,14.509757355016381,0.0020816591744367207,0.08889045467662131,1.0,True,
12,14,0.97948616772873,2.2709446365202806,0.8745432399512789,0.09766069029332589,6,11.857488007889668,0.002473515580842477,0.018069288085025077,1.0,True,
12,15,4.286747470271891,25.863710300929224,0.7033426183844012,0.12103901189560114,6,11.859397045728326,0.022780233796594867,0.04927694758545139,1.0,True,
12,16,7.5836501880550475,51.70712845726087,0.5820235756385069,0.12458133956199553,6,32.351864267271,0.00989839849833309,0.03808519306435704,1.0,True,
12,17,6.351478009829798,1.4248238883471207,0.6542219994988725,0.12658997093251892,6,11.229951110937455,0.019742680150836883,0.07899236042083516,1.0,True,
13,14,4.006260191078547,30.491284026126113,0.6984766461034874,0.11406275231178287,6,13.924718120717541,0.012533581961738958,0.10861604448395465,1.0,True,
13,15,0.9562774815922267,6.898518361717157,0.8794391298650243,0.09903209123829654,6,10.514819201987352,0.006638809913641406,0.04266354358349458,1.0,True,
13,16,3.565173336606111,18.944899794614482,0.7273073505141552,0.10958532316684438,6,19.40505650408657,0.005441055528592516,0.12519365495730794,1.0,True,
13,17,2.967150651379281,31.337404774299262,0.7130265716137395,0.11779895987026284,6,12.816390530620689,0.013263327701592687,0.16221305155705806,1.0,True,
13,18,5.249882044431148,3.4459964529377567,0.7019876443728176,0.11940768524727291,6,25.985486690964983,0.008995185113784413,0.05198334813202341,1.0,True,
14,15,4.019575892829469,23.592765664408944,0.7035920622959055,0.12189498753760801,6,10.295943232842452,0.010817230757509965,0.008099442158890762,0.5,True,
14,16,7.5676649485439835,49.43618382074059,0.5923489278752436,0.12241126089476961,6,33.65365690477826,0.003104373432332903,0.13386003286196627,1.0,True,
14,17,5.910977627463022,0.8461207481731591,0.6687795177728063,0.12452036369759786,6,8.95305147071513,0.01159286549234958,0.10132041390926738,1.0,True,
14,18,8.548241724186095,27.045287573188347,0.6196476790536196,0.12845393182350578,6,17.805505225184664,0.010070495463159433,0.13041926369959572,1.0,True,
14,19,9.249254057266956,79.47127430600668,0.5845660749506904,0.12584132662356784,6,30.335272352492872,0.016830803029542436,0.2574761671757917,1.0,True,
15,16,3.7301261399251735,25.84341815633164,0.7032674772036475,0.11602216892855616,6,22.696890954829907,0.0009988867448423883,0.09036168134112703,1.0,True,
15,17,2.2049738368271745,24.438886412582093,0.7489009568140678,0.11923375870790391,6,6.944925131742929,0.02582458487951314,0.095039680889365,1.0,True,
15,18,4.7000039832559155,3.452521908779401,0.7165438713998661,0.11783922962071142,6,19.334165264487982,0.009354386824208725,0.17478401338848865,1.0,True,
15,19,5.238340351181781,55.87850864159773,0.6924358974358974,0.11506895717743916,6,20.41712608461488,0.012497900854287812,0.3255615001296644,1.0,True,
15,20,2.0380809160783224,128.5450420215843,0.6751867872591427,0.11901811643083532,6,27.381712286843808,0.007126007329084919,0.09473387178666172,1.0,True,
16,17,3.368526196086246,50.282304568913744,0.6284461152882206,0.1113618900510703,6,25.184005110635376,0.015370940622322818,0.07520125332617648,1.0,True,
16,18,3.5240348999326185,22.39089624755224,0.6921281286473868,0.10919235768364483,6,39.42922894015919,0.005355462743716769,0.03654303027439279,1.0,True,
16,19,2.146829294717985,30.035090485266103,0.8880188913745961,0.0968346861370537,6,11.70909430755386,0.006745004702225108,0.04851926363283949,1.0,True,
16,20,4.728364693236263,102.70162386525263,0.6307301587301587,0.12362874516637085,6,35.70048091760535,0.007128919591300971,0.10475436005961275,1.0,True,
16,21,10.094336026449797,153.311489607007,0.40465918895599656,0.14174574821871383,6,57.2667389243621,0.01442382622115287,0.2621394134233604,1.0,True,
17,18,2.640403049812329,27.891408321361506,0.7665916015366274,0.11462276561713726,6,9.759321661407776,0.003676908482849856,0.03455537183665902,1.0,True,
17,19,3.933985934417215,80.31739505417984,0.645738203957382,0.11467441399973681,6,29.93502749301381,0.002658154417044598,0.03997293849541112,1.0,True,
17,20,4.2254212089887,152.98392843416656,0.6031375599636977,0.12542519992547976,6,27.708613247812377,4.543486491493239,1.5708713272202308,0.5,False,forward_reverse_translation;forward_reverse_rotation
17,21,9.21529677253323,156.40620582407942,0.4672368255565338,0.13751533766984328,6,32.19730123852794,0.13403735422224766,1.1805230479043929,0.5,False,forward_reverse_translation;forward_reverse_rotation
17,22,11.781237287606983,150.82966218116434,0.4255952380952381,0.1417851723285674,6,38.71155142811069,0.01638337185583796,0.14112774930143776,1.0,True,
18,19,2.4460967931915643,52.425986732818345,0.6999343401181878,0.10876407223187468,6,36.919102980185954,0.0029722527816906422,0.028553700929610463,1.0,True,
18,20,6.593931011285688,125.09252011280485,0.5796614723267061,0.12778176089815,6,32.46060078153023,0.0849644734357879,0.24110896992060843,1.0,False,forward_reverse_translation
18,21,11.793089868757727,175.70238585456048,0.42345743296016664,0.14463219993853524,6,41.677643204951686,0.008594432490503082,0.044791316017877565,1.0,True,
18,22,14.40479158879449,178.72107050256645,0.35586914688903143,0.14801012931814386,6,118.81863294454718,0.05364920712348454,0.15560820359743163,1.0,True,
18,23,11.744734461596845,164.11569663484164,0.3967277486910995,0.14073241205403209,6,67.8157115569044,0.0379980141073483,0.09667096210106078,1.0,True,
19,20,6.650720121741557,72.6665333799865,0.6260444787247719,0.12668491637849374,6,25.97982149253148,0.022781715838261912,0.19834674514582162,1.0,True,
19,21,12.053580264031138,123.27639912174077,0.3952850193339154,0.1421446220617431,6,61.553005632745744,0.01796074748252817,0.16263425635632095,1.0,True,
19,22,14.836246959975925,128.85294276465592,0.3237392373923739,0.15069330869045588,6,75.70505048512227,0.2579934626807855,1.1847082611411373,1.0,False,heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation
19,23,11.740810561661805,143.45831663234065,0.37768025078369905,0.1432975121556299,6,38.93505210462392,0.07411982241929344,0.4458370864472602,1.0,True,
19,24,11.093516369446519,177.87107120383473,0.43504761904761907,0.13924446561169374,6,41.93660606132572,0.04258350776081339,0.7879276702809055,0.5,False,forward_reverse_rotation
20,21,5.40286046809103,50.60986574175429,0.6607188376242672,0.12214671257866079,6,14.082798146569438,0.01471691635641488,0.0747935794181239,1.0,True,
20,22,8.200956565000565,56.18640938466938,0.576328684508104,0.13245799460807672,6,16.034011252149995,0.02165175771302933,0.4612076789897289,1.0,True,
20,23,5.175118275082073,70.79178325235415,0.6451819579702717,0.12102263178677423,6,12.511468996120387,0.018249896901851893,0.12115759970382113,1.0,True,
20,24,4.710308396362637,105.20453782384023,0.7383177570093458,0.11441422046802802,6,12.152737785424229,0.009011279667019194,0.06430292411421322,1.0,True,
20,25,8.226491803107123,68.57831319871164,0.6182822702159718,0.12791257682460647,6,29.104161441720954,0.014703530260578728,0.32055535743282454,1.0,True,
21,22,2.8644847627909416,5.57654364291509,0.7740636818348177,0.11564788773425824,6,12.209574213097333,0.0065184199045192235,0.06991965908247275,1.0,True,
21,23,1.2936223973175418,20.18191751059984,0.862223327530465,0.10307116830776755,6,11.124499228103288,0.0026785916987678697,0.01879221122979615,1.0,True,
21,24,2.3128500583741403,54.59467208208593,0.7795265676152102,0.11182158240581822,6,15.934013426145448,0.003491995501356654,0.0374256510953578,1.0,True,
21,25,3.793272903723766,17.968447456957346,0.7340892465252378,0.12002239056891174,6,16.57216298005221,0.037735058311297705,0.2878144229712423,1.0,True,
21,26,4.631707245574304,60.912845043424184,0.7147358216190014,0.11997311573294966,6,17.372315653280065,0.012351932089198563,0.10379217190868555,1.0,True,
22,23,3.7194009250537223,14.605373867684753,0.7408951563458002,0.11726725109752717,6,12.610213323576254,0.009206244303295173,0.09601986001481369,1.0,True,
22,24,4.786117710081478,49.01812843917083,0.6936064556176288,0.11914624513148218,6,13.20338761495324,0.006090737153726605,0.027557138417491776,1.0,True,
22,25,2.3747421598149763,12.391903814042255,0.7356584485868911,0.11474070552638106,6,16.673633938013044,0.0019617091597581428,0.011643770804738128,1.0,True,
22,26,2.381693697571969,55.336301400509086,0.7422594142259414,0.11765221626900083,6,18.880420396501957,0.007814937371704293,0.039342553564881436,1.0,True,
22,27,2.9603952927690247,80.62067670375279,0.7254925373134329,0.11870181965154768,6,21.309389349405485,0.018241823604788005,0.08279236858582419,1.0,True,
23,24,1.0880644725336985,34.41275457148609,0.7884810126582279,0.10662565692629543,6,10.611199681165669,0.001882338148224756,0.020239211377623818,1.0,True,
23,25,5.033935954106079,2.213470053642494,0.6843137254901961,0.12171091934898426,6,15.150809064137142,0.014639828785635214,0.2046006298281892,1.0,True,
23,26,5.765534437288107,40.730927532824346,0.6772228989037758,0.12237954766026346,6,16.382334072569904,0.04518647006837157,0.20367965681897066,1.0,True,
23,27,6.392376191887118,66.01530283606805,0.6637469586374696,0.12340951265232122,6,20.64383986204732,0.010626193556947957,1.1817079481882757,1.0,False,forward_reverse_rotation
23,28,6.631292861991925,85.8775990418248,0.6720351390922401,0.12122778564083748,6,19.20657067904157,0.043540468687755365,0.26051478047218213,1.0,True,
24,25,6.097212810998921,36.62622462512857,0.6764267990074442,0.12367636388755719,6,21.50176872604179,0.035816405766439664,0.2046504337319023,1.0,True,
24,26,6.852918693116795,6.318172961338249,0.6524044389642417,0.12740232296321938,6,21.753053297058692,0.062038641373543625,0.12012273822228971,1.0,True,
24,27,7.47982907414204,31.60254826458195,0.6546798029556651,0.12425657449629145,6,26.295019203914197,0.05403266260962086,0.2126374478532415,1.0,True,
24,28,7.71913193400284,51.4648444703387,0.6614377470355731,0.12010155595807545,6,20.98500095492876,0.049490676797893776,0.25138526875505346,1.0,True,
24,29,7.213852273638132,93.73390958258973,0.6620579958399608,0.12331341986261203,6,28.045882177977884,0.08010108996241684,0.7934458070763889,1.0,False,forward_reverse_translation;forward_reverse_rotation
25,26,1.2882746074869595,42.944397586466835,0.9137395459976105,0.08148336624555251,6,16.56293455758718,0.0032405549728505064,0.006669174585592186,1.0,True,
25,27,1.7666235781584831,68.22877288971053,0.8596658711217183,0.1096145882030974,6,22.188153380460985,0.010624789877375215,0.04475651255674721,1.0,True,
25,28,2.1807182972588706,88.09106909546726,0.8839157491622786,0.10490699814192792,6,16.17217948348072,0.00283419137499405,0.01585308444597649,1.0,True,
25,29,1.118287177357716,130.36013420771832,0.7857227558401518,0.10954358768244267,6,20.132087187715307,0.003247718742817276,0.01654235380830592,1.0,True,
25,30,5.200639419572971,139.6773015481418,0.05061061531235322,0.1582745227634446,6,186.52424080569466,2.4401001990983833,2.63047633237589,0.0,False,heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
26,27,0.6269948654714946,25.284375303243706,0.9188612099644128,0.07520973241900301,6,20.799051531446562,0.0009920873307779914,0.005485007138529723,1.0,True,
26,28,0.9431372257108486,45.14667150900044,0.9182726623840114,0.07611768518866212,6,21.99541958409829,0.004984978187527545,0.01173507098896779,1.0,True,
26,29,1.129925637779203,87.41573662125148,0.7856890251090416,0.10851393061782179,6,24.280602674778656,0.002586732426991948,0.12205883610743135,1.0,True,
26,30,4.786676837961511,177.3783008654109,0.7634835395750642,0.10495724850674527,6,34.80647378548578,0.008489213184554775,0.021733210949123293,1.0,True,
26,31,5.900105669197046,156.01119868987135,0.7374054682955207,0.1073752166304431,6,38.310555401782544,3.545322953478412,6.459518815762061,1.0,False,forward_reverse_translation;forward_reverse_rotation
27,28,0.44809323479571145,19.862296205756735,0.9281131178707225,0.07220943509634768,6,20.093208902964978,0.0023003913248606234,0.001591751329336308,1.0,True,
27,29,1.160064136065596,62.13136131800778,0.7871188037207112,0.10966659884725236,6,24.009650087098436,0.005134403698947268,0.02434844099203523,1.0,True,
27,30,4.25412615059095,152.09392556214777,0.7922108208955224,0.10147035321534553,6,32.52026042532504,2.795897032209197,3.237329131939042,1.0,False,forward_reverse_translation;forward_reverse_rotation
27,31,5.542871649427442,178.70442600700352,0.7592097617664149,0.10539218018667602,6,48.70669958381899,0.0020939062619997405,0.027400662783563425,1.0,True,
27,32,4.8829350360277,138.47370648510565,0.7480278422273782,0.10408734715846867,6,42.21510199826003,0.004407463145306061,0.03490543724836902,1.0,True,
28,29,1.5995723610319212,42.26906511225104,0.787814381863266,0.10901449493832843,6,25.913625419675043,0.01570175790237609,0.03569660498137675,1.0,True,
28,30,4.295353381127667,132.23162935639104,0.7791159962581852,0.10438560755009844,6,36.501677925790155,0.01170149615735483,0.047047156693309715,1.0,True,
28,31,5.754426930327516,158.8421298011293,0.7449015266285981,0.10668967013687296,6,48.04636971250818,0.024421374377245158,0.6810283060803514,0.5,False,forward_reverse_rotation
28,32,5.013331477001236,158.33600269086247,0.7309213587715216,0.1081318944415262,6,52.23779663520043,0.10324080390042788,0.9479198721286788,1.0,False,forward_reverse_translation;forward_reverse_rotation
28,33,5.317102997899902,67.05379893079937,0.6692465836255895,0.10871806386783629,6,54.67376255043738,0.13259351588266985,0.659391929056215,1.0,False,forward_reverse_translation;forward_reverse_rotation
29,30,4.099530130205247,89.96256424413995,0.7320662880982732,0.10929719051930432,6,30.745355096911158,0.005463604154015333,0.018583483487870766,1.0,True,
29,31,4.890018768530818,116.57306468887772,0.7254562254562255,0.11323336570178015,6,49.09678922599757,0.0050777995851252426,0.07265894091769333,1.0,True,
29,32,4.464737187763289,159.39493219688632,0.695837657096737,0.10999820041072961,6,72.25486343140591,0.011220553177198233,0.14599088546526143,1.0,True,
29,33,4.34670323578768,109.32286404305042,0.6382698298586149,0.11215583949685817,6,72.66655529463955,0.018977478007060265,0.05615608666616428,1.0,True,
30,31,2.3862917603439455,26.61050044473775,0.8286237272623269,0.0964659694513182,6,41.03513305025223,0.018905314487385798,0.08632503464137033,1.0,True,
30,32,1.1507634071714652,69.43236795274659,0.8065326633165829,0.09815063400019142,6,35.25635856167173,0.007341509941520892,0.023973741904832375,1.0,True,
30,33,2.4361832484951105,160.7145717128102,0.6965239055641239,0.10032655671840386,6,47.08947384000088,2.6257249086936962,3.1197630566421646,1.0,False,forward_reverse_translation;forward_reverse_rotation
31,32,1.2514382602180638,42.82186750800885,0.8146841206602162,0.09914628416022417,6,34.33173181492779,0.06936877058296162,0.45703051311645637,1.0,True,
31,33,0.641507519046697,134.10407126807203,0.7551430598250177,0.1003246191461096,6,28.537349904719164,0.005559091155811048,0.0330053182501105,1.0,True,
32,33,1.4370687242949811,91.2822037600631,0.043432078366576185,0.1661924920133195,6,88.4956235178996,0.946171384319174,3.5092968853566924,0.0,False,heldout_inlier_ratio;heldout_inlier_rmse;forward_reverse_translation;forward_reverse_rotation;multistart_instability
1 i j rtk_translation_m rtk_rotation_deg heldout_inlier_ratio heldout_inlier_rmse_m hessian_rank hessian_condition reverse_translation_m reverse_rotation_deg multistart_success_rate accepted rejection_reasons
2 0 1 1.8477087194158957 24.171297449440786 0.8193962748876044 0.11049677170760565 6 14.01384280930539 0.026680306344951596 0.12400141971813113 1.0 True
3 0 2 2.633731568575307 80.09074797031298 0.7525388867463684 0.11492533799497681 6 14.962108606915407 0.0018465318763874656 0.03093450642761156 1.0 True
4 0 3 6.923255970100826 79.91583883299243 0.628093901505486 0.12365050970311489 6 23.49058941554809 0.010524333328229862 0.23898233567763014 1.0 True
5 0 4 2.952974950373882 130.25781950514033 0.693351593625498 0.11485738628517483 6 16.30512452170669 2.3214755241040135 1.4768350244766943 1.0 False forward_reverse_translation;forward_reverse_rotation
6 0 5 8.205074921973447 88.82606603101337 0.6015065913370998 0.12959862169013578 6 26.276485388438196 0.013711901937629547 0.26024107991311357 1.0 True
7 1 2 1.7155362084417105 55.91945052087219 0.7981310803891449 0.11167434332282761 6 13.484382276710292 0.050742368484802715 0.5170998105709517 1.0 False forward_reverse_rotation
8 1 3 5.885389341942907 55.74454138355163 0.678820988438572 0.12109867185657652 6 27.478714016209608 0.008324315958289934 0.25689852176843175 1.0 True
9 1 4 2.3031736807956613 106.08652205569953 0.6772473651580905 0.11157492180388107 6 13.25032679930311 0.00802452890723869 0.011334867255723058 1.0 True
10 1 5 7.590375029412286 64.65476858157257 0.618779694923731 0.12786963556791484 6 21.376356908960595 0.012044684321695526 0.5185219918090508 1.0 False forward_reverse_rotation
11 1 6 8.114848491620153 8.108983632525 0.6933789087226231 0.12162612003144421 6 23.560279629760274 0.008258211706006158 0.08942722541817279 1.0 True
12 2 3 4.339151683168145 0.17490913732054883 0.7609663064208518 0.11583878636897658 6 13.299472485774093 0.00784220284106163 0.01711532312869618 1.0 True
13 2 4 0.6150408096900305 50.167071534827386 0.796748976299789 0.11484342359047915 6 12.041666425070249 0.011210942709507262 0.11462542679280045 1.0 True
14 2 5 5.894923525501676 8.735318060700383 0.7112112112112112 0.12001317983504071 6 15.619771158055366 0.011298647069299764 0.02158504824207679 1.0 True
15 2 6 6.5470565092896145 47.810466888347186 0.7254529329785886 0.11794151013960824 6 22.73936753420594 0.004729020804343234 0.026463705119922912 1.0 True
16 2 7 5.848768648120739 73.12203319515616 0.7569187603621987 0.11528530285715083 6 13.439936375510058 0.011994525026310043 0.05882394078371607 1.0 True
17 3 4 3.9735035126146885 50.34198067214791 0.6957378664695738 0.11619002437046372 6 17.769677378118345 0.006018969660459514 0.057234124307389854 1.0 True
18 3 5 2.3619393178889707 8.910227198020932 0.7989069680784996 0.1077590577814381 6 12.151555874812605 0.01353229173052898 0.06486032517688407 1.0 True
19 3 6 2.2300116834828536 47.63555775102664 0.8435613682092555 0.11222309863990207 6 13.914901775514604 0.009512765198853305 0.09743636874085052 1.0 True
20 3 7 2.450274232383144 72.94712405783562 0.8651898734177215 0.11184568072686094 6 12.975777248765134 0.010827690226297962 0.12520280777371934 1.0 True
21 3 8 1.9936391786054533 163.56928836557165 0.825590155700653 0.10787987267070262 6 12.959410142765178 0.0056088079192386986 0.021536601402145895 1.0 True
22 4 5 5.370771070097231 41.431753474126985 0.6652516676773802 0.12444356767629798 6 17.0278455355532 0.003155412913416372 0.05824374716659687 1.0 True
23 4 6 6.142548765456278 97.97753842317456 0.6551681807021851 0.12030857820705748 6 24.380739297719824 0.011212969141455763 0.08373658947114908 0.5 True
24 4 7 5.314106070657687 123.28910472998356 0.696936001976773 0.11799538994568655 6 16.89426071448657 0.00409529177569414 0.0519770010059835 0.5 True
25 4 8 5.8981084980454765 146.08873096228052 0.7129198332924737 0.11349036082807595 6 18.616925126379197 0.01249802665331653 0.04549823831834878 1.0 True
26 4 9 3.1650365894055956 158.11607430100375 0.6097234068478128 0.11677937926620108 6 25.94675052408701 0.00616984405743089 0.11548555225177864 0.5 True
27 5 6 1.9357867378988893 56.54578494904757 0.7604901596732269 0.11101741624951145 6 16.791344333152654 0.01028641250952863 0.031127073736479716 1.0 True
28 5 7 0.19501559913675365 81.85735125585654 0.8092687180764918 0.10777871969807898 6 15.20338641054919 0.010579714886860828 0.03333492483252218 1.0 True
29 5 8 1.8038807059856978 172.47951556359365 0.7295760721789643 0.11311887218463837 6 17.053462425193878 0.009759205787181369 0.05935114015788255 1.0 True
30 5 9 7.881613621164215 160.45217222486949 0.514987714987715 0.13057312144553648 6 36.76355827214787 0.013423709236357283 0.8456480282562385 1.0 False forward_reverse_rotation
31 5 10 7.671857805815354 143.54232919109316 0.5442391832766165 0.13173165083201857 6 26.337222871823027 0.00523999417676037 0.17134499262937464 1.0 True
32 6 7 2.130456053070114 25.311566306808988 0.8539354187689203 0.10462253085153163 6 12.502910766598342 0.002843314342407394 0.028424505305675103 1.0 True
33 6 8 0.24494500622098103 115.93373061454483 0.7757757757757757 0.1131640002846509 6 15.521747346102597 0.007224674181694773 0.10395594559619498 1.0 True
34 6 9 8.025985916230132 103.90638727582186 0.6009202835468226 0.1259448851291812 6 28.795189977892573 0.007975110549369148 0.07338758946554978 1.0 True
35 6 10 8.303463402700086 159.91188585985975 0.5363513347275187 0.13083118553166714 6 31.167661263990606 1.7146158775247784 14.688249303878628 1.0 False forward_reverse_translation;forward_reverse_rotation
36 6 11 11.328882063792355 138.54043874549896 0.4633337584491774 0.13769942152040132 6 58.377014583638996 0.023339879627157865 0.18888808553225306 1.0 True
37 7 8 1.9962664218365056 90.62216430773583 0.8340050377833753 0.11132245152738919 6 16.170350777028464 0.004806949653405585 0.02034039865601464 1.0 True
38 7 9 7.8811064994361235 78.59482096901284 0.6160971335586432 0.12637042711002067 6 24.17866477904224 0.012112046262530643 0.5927664140170301 1.0 False forward_reverse_rotation
39 7 10 7.620696678413973 134.60031955305035 0.5721183607775164 0.13040415535689726 6 20.571872802604858 0.010507785715791607 0.10324111221368969 1.0 True
40 7 11 10.356268683459426 113.22887243868989 0.5179098728976762 0.13593896500168 6 36.18276452817704 0.01570096854689765 0.062213006599939585 1.0 True
41 7 12 13.76202076313695 94.9510481861355 0.419173636250156 0.14753718360891266 6 89.28960611274712 0.018245717875293024 0.41212759156893974 1.0 True
42 8 9 7.803713858547152 12.027343338722998 0.6407549981373402 0.12189583104066963 6 28.06713348388497 0.025170709673794655 0.14112050756711447 1.0 True
43 8 10 8.062504137991457 43.97815524531457 0.6075420709986488 0.12933255373443525 6 20.429632894689156 0.01256038832173067 0.12386976869589704 1.0 True
44 8 11 11.084531710563947 22.606708130954026 0.5447599643448364 0.13547919137380177 6 27.72840331379869 0.006462771421800638 0.08629803406158434 1.0 True
45 8 12 14.350449135419003 4.328883878399632 0.46489164086687307 0.14530908872417966 6 37.958964738084276 0.00955628888567663 0.1112688142308669 1.0 True
46 8 13 18.245302761704593 28.43334478424675 0.37832991803278687 0.15442802982944018 6 101.4897081447731 0.027875630486669568 0.37387837894914167 1.0 True
47 9 10 2.04815062353057 56.00549858403758 0.6576312576312576 0.10693844153477818 6 15.957672354569297 0.0038184479242649575 0.016135929192430513 0.5 True
48 9 11 4.738677237611319 34.634051469677026 0.5883320678309288 0.11815838246331245 6 21.526672921842792 0.008866435423074387 0.05039949658393549 1.0 True
49 9 12 7.170741483679294 16.35622721712263 0.5413589364844904 0.12469816852157387 6 38.0354745717217 0.03403608365914784 0.249719392637206 1.0 True
50 9 13 10.733928030180744 16.40600144552375 0.5075728649611811 0.13356609480985904 6 50.3483756147633 0.0053243668825205025 0.1401976908512776 1.0 True
51 9 14 7.8912187855633125 14.085282580602351 0.5416463116756228 0.12859551377809317 6 26.721469573230642 0.011377143482081005 0.045883349808202654 1.0 True
52 10 11 3.2047552083250137 21.371447114360556 0.7131414267834794 0.12095106901516857 6 13.404202373771971 0.006897671932212844 0.18540056788520093 1.0 True
53 10 12 6.291813977735496 39.64927136691496 0.6117876278616659 0.125002241212049 6 19.806559061723195 0.006509808900811586 0.06742381610992963 1.0 True
54 10 13 10.199392557022867 72.41150002956134 0.5244808055380743 0.13689263771908436 6 36.18235272080672 0.0046518429660042555 0.17931102925270673 1.0 True
55 10 14 6.819808471778838 41.920216003435236 0.5996858385693572 0.12955691635611957 6 17.916357473627098 0.009593578345611205 0.05753783232412823 1.0 True
56 10 15 10.561163834622734 65.51298166784417 0.5048970366649924 0.13966622273060364 6 30.173625507677322 0.006074999657630774 0.04867559411570644 1.0 True
57 11 12 3.467398797536633 18.277824252554396 0.6508076728924785 0.12017753597340274 6 15.211760062254479 0.016302173123952383 0.05141023051579345 1.0 True
58 11 13 7.516502113110916 51.04005291520078 0.5666710199817161 0.12966061077144844 6 26.14207974844453 0.010250004424021303 0.0630753341579125 1.0 True
59 11 14 3.767517331528496 20.548768889074672 0.64271407110666 0.12109534664165014 6 14.04589685067402 0.007610676428698485 0.05350102444543152 1.0 True
60 11 15 7.712397127778454 44.141534553483616 0.570479416362689 0.12836105648377344 6 18.857231656557765 0.006898635416764969 0.028532809525243653 1.0 True
61 11 16 11.0395297023812 69.98495270981527 0.4761423882857864 0.13515636052103228 6 45.4042112112498 0.037021121414326036 0.5129154434742894 1.0 False forward_reverse_rotation
62 12 13 4.049286119591895 32.762228662646386 0.7056733087955325 0.11689859246507509 6 14.509757355016381 0.0020816591744367207 0.08889045467662131 1.0 True
63 12 14 0.97948616772873 2.2709446365202806 0.8745432399512789 0.09766069029332589 6 11.857488007889668 0.002473515580842477 0.018069288085025077 1.0 True
64 12 15 4.286747470271891 25.863710300929224 0.7033426183844012 0.12103901189560114 6 11.859397045728326 0.022780233796594867 0.04927694758545139 1.0 True
65 12 16 7.5836501880550475 51.70712845726087 0.5820235756385069 0.12458133956199553 6 32.351864267271 0.00989839849833309 0.03808519306435704 1.0 True
66 12 17 6.351478009829798 1.4248238883471207 0.6542219994988725 0.12658997093251892 6 11.229951110937455 0.019742680150836883 0.07899236042083516 1.0 True
67 13 14 4.006260191078547 30.491284026126113 0.6984766461034874 0.11406275231178287 6 13.924718120717541 0.012533581961738958 0.10861604448395465 1.0 True
68 13 15 0.9562774815922267 6.898518361717157 0.8794391298650243 0.09903209123829654 6 10.514819201987352 0.006638809913641406 0.04266354358349458 1.0 True
69 13 16 3.565173336606111 18.944899794614482 0.7273073505141552 0.10958532316684438 6 19.40505650408657 0.005441055528592516 0.12519365495730794 1.0 True
70 13 17 2.967150651379281 31.337404774299262 0.7130265716137395 0.11779895987026284 6 12.816390530620689 0.013263327701592687 0.16221305155705806 1.0 True
71 13 18 5.249882044431148 3.4459964529377567 0.7019876443728176 0.11940768524727291 6 25.985486690964983 0.008995185113784413 0.05198334813202341 1.0 True
72 14 15 4.019575892829469 23.592765664408944 0.7035920622959055 0.12189498753760801 6 10.295943232842452 0.010817230757509965 0.008099442158890762 0.5 True
73 14 16 7.5676649485439835 49.43618382074059 0.5923489278752436 0.12241126089476961 6 33.65365690477826 0.003104373432332903 0.13386003286196627 1.0 True
74 14 17 5.910977627463022 0.8461207481731591 0.6687795177728063 0.12452036369759786 6 8.95305147071513 0.01159286549234958 0.10132041390926738 1.0 True
75 14 18 8.548241724186095 27.045287573188347 0.6196476790536196 0.12845393182350578 6 17.805505225184664 0.010070495463159433 0.13041926369959572 1.0 True
76 14 19 9.249254057266956 79.47127430600668 0.5845660749506904 0.12584132662356784 6 30.335272352492872 0.016830803029542436 0.2574761671757917 1.0 True
77 15 16 3.7301261399251735 25.84341815633164 0.7032674772036475 0.11602216892855616 6 22.696890954829907 0.0009988867448423883 0.09036168134112703 1.0 True
78 15 17 2.2049738368271745 24.438886412582093 0.7489009568140678 0.11923375870790391 6 6.944925131742929 0.02582458487951314 0.095039680889365 1.0 True
79 15 18 4.7000039832559155 3.452521908779401 0.7165438713998661 0.11783922962071142 6 19.334165264487982 0.009354386824208725 0.17478401338848865 1.0 True
80 15 19 5.238340351181781 55.87850864159773 0.6924358974358974 0.11506895717743916 6 20.41712608461488 0.012497900854287812 0.3255615001296644 1.0 True
81 15 20 2.0380809160783224 128.5450420215843 0.6751867872591427 0.11901811643083532 6 27.381712286843808 0.007126007329084919 0.09473387178666172 1.0 True
82 16 17 3.368526196086246 50.282304568913744 0.6284461152882206 0.1113618900510703 6 25.184005110635376 0.015370940622322818 0.07520125332617648 1.0 True
83 16 18 3.5240348999326185 22.39089624755224 0.6921281286473868 0.10919235768364483 6 39.42922894015919 0.005355462743716769 0.03654303027439279 1.0 True
84 16 19 2.146829294717985 30.035090485266103 0.8880188913745961 0.0968346861370537 6 11.70909430755386 0.006745004702225108 0.04851926363283949 1.0 True
85 16 20 4.728364693236263 102.70162386525263 0.6307301587301587 0.12362874516637085 6 35.70048091760535 0.007128919591300971 0.10475436005961275 1.0 True
86 16 21 10.094336026449797 153.311489607007 0.40465918895599656 0.14174574821871383 6 57.2667389243621 0.01442382622115287 0.2621394134233604 1.0 True
87 17 18 2.640403049812329 27.891408321361506 0.7665916015366274 0.11462276561713726 6 9.759321661407776 0.003676908482849856 0.03455537183665902 1.0 True
88 17 19 3.933985934417215 80.31739505417984 0.645738203957382 0.11467441399973681 6 29.93502749301381 0.002658154417044598 0.03997293849541112 1.0 True
89 17 20 4.2254212089887 152.98392843416656 0.6031375599636977 0.12542519992547976 6 27.708613247812377 4.543486491493239 1.5708713272202308 0.5 False forward_reverse_translation;forward_reverse_rotation
90 17 21 9.21529677253323 156.40620582407942 0.4672368255565338 0.13751533766984328 6 32.19730123852794 0.13403735422224766 1.1805230479043929 0.5 False forward_reverse_translation;forward_reverse_rotation
91 17 22 11.781237287606983 150.82966218116434 0.4255952380952381 0.1417851723285674 6 38.71155142811069 0.01638337185583796 0.14112774930143776 1.0 True
92 18 19 2.4460967931915643 52.425986732818345 0.6999343401181878 0.10876407223187468 6 36.919102980185954 0.0029722527816906422 0.028553700929610463 1.0 True
93 18 20 6.593931011285688 125.09252011280485 0.5796614723267061 0.12778176089815 6 32.46060078153023 0.0849644734357879 0.24110896992060843 1.0 False forward_reverse_translation
94 18 21 11.793089868757727 175.70238585456048 0.42345743296016664 0.14463219993853524 6 41.677643204951686 0.008594432490503082 0.044791316017877565 1.0 True
95 18 22 14.40479158879449 178.72107050256645 0.35586914688903143 0.14801012931814386 6 118.81863294454718 0.05364920712348454 0.15560820359743163 1.0 True
96 18 23 11.744734461596845 164.11569663484164 0.3967277486910995 0.14073241205403209 6 67.8157115569044 0.0379980141073483 0.09667096210106078 1.0 True
97 19 20 6.650720121741557 72.6665333799865 0.6260444787247719 0.12668491637849374 6 25.97982149253148 0.022781715838261912 0.19834674514582162 1.0 True
98 19 21 12.053580264031138 123.27639912174077 0.3952850193339154 0.1421446220617431 6 61.553005632745744 0.01796074748252817 0.16263425635632095 1.0 True
99 19 22 14.836246959975925 128.85294276465592 0.3237392373923739 0.15069330869045588 6 75.70505048512227 0.2579934626807855 1.1847082611411373 1.0 False heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation
100 19 23 11.740810561661805 143.45831663234065 0.37768025078369905 0.1432975121556299 6 38.93505210462392 0.07411982241929344 0.4458370864472602 1.0 True
101 19 24 11.093516369446519 177.87107120383473 0.43504761904761907 0.13924446561169374 6 41.93660606132572 0.04258350776081339 0.7879276702809055 0.5 False forward_reverse_rotation
102 20 21 5.40286046809103 50.60986574175429 0.6607188376242672 0.12214671257866079 6 14.082798146569438 0.01471691635641488 0.0747935794181239 1.0 True
103 20 22 8.200956565000565 56.18640938466938 0.576328684508104 0.13245799460807672 6 16.034011252149995 0.02165175771302933 0.4612076789897289 1.0 True
104 20 23 5.175118275082073 70.79178325235415 0.6451819579702717 0.12102263178677423 6 12.511468996120387 0.018249896901851893 0.12115759970382113 1.0 True
105 20 24 4.710308396362637 105.20453782384023 0.7383177570093458 0.11441422046802802 6 12.152737785424229 0.009011279667019194 0.06430292411421322 1.0 True
106 20 25 8.226491803107123 68.57831319871164 0.6182822702159718 0.12791257682460647 6 29.104161441720954 0.014703530260578728 0.32055535743282454 1.0 True
107 21 22 2.8644847627909416 5.57654364291509 0.7740636818348177 0.11564788773425824 6 12.209574213097333 0.0065184199045192235 0.06991965908247275 1.0 True
108 21 23 1.2936223973175418 20.18191751059984 0.862223327530465 0.10307116830776755 6 11.124499228103288 0.0026785916987678697 0.01879221122979615 1.0 True
109 21 24 2.3128500583741403 54.59467208208593 0.7795265676152102 0.11182158240581822 6 15.934013426145448 0.003491995501356654 0.0374256510953578 1.0 True
110 21 25 3.793272903723766 17.968447456957346 0.7340892465252378 0.12002239056891174 6 16.57216298005221 0.037735058311297705 0.2878144229712423 1.0 True
111 21 26 4.631707245574304 60.912845043424184 0.7147358216190014 0.11997311573294966 6 17.372315653280065 0.012351932089198563 0.10379217190868555 1.0 True
112 22 23 3.7194009250537223 14.605373867684753 0.7408951563458002 0.11726725109752717 6 12.610213323576254 0.009206244303295173 0.09601986001481369 1.0 True
113 22 24 4.786117710081478 49.01812843917083 0.6936064556176288 0.11914624513148218 6 13.20338761495324 0.006090737153726605 0.027557138417491776 1.0 True
114 22 25 2.3747421598149763 12.391903814042255 0.7356584485868911 0.11474070552638106 6 16.673633938013044 0.0019617091597581428 0.011643770804738128 1.0 True
115 22 26 2.381693697571969 55.336301400509086 0.7422594142259414 0.11765221626900083 6 18.880420396501957 0.007814937371704293 0.039342553564881436 1.0 True
116 22 27 2.9603952927690247 80.62067670375279 0.7254925373134329 0.11870181965154768 6 21.309389349405485 0.018241823604788005 0.08279236858582419 1.0 True
117 23 24 1.0880644725336985 34.41275457148609 0.7884810126582279 0.10662565692629543 6 10.611199681165669 0.001882338148224756 0.020239211377623818 1.0 True
118 23 25 5.033935954106079 2.213470053642494 0.6843137254901961 0.12171091934898426 6 15.150809064137142 0.014639828785635214 0.2046006298281892 1.0 True
119 23 26 5.765534437288107 40.730927532824346 0.6772228989037758 0.12237954766026346 6 16.382334072569904 0.04518647006837157 0.20367965681897066 1.0 True
120 23 27 6.392376191887118 66.01530283606805 0.6637469586374696 0.12340951265232122 6 20.64383986204732 0.010626193556947957 1.1817079481882757 1.0 False forward_reverse_rotation
121 23 28 6.631292861991925 85.8775990418248 0.6720351390922401 0.12122778564083748 6 19.20657067904157 0.043540468687755365 0.26051478047218213 1.0 True
122 24 25 6.097212810998921 36.62622462512857 0.6764267990074442 0.12367636388755719 6 21.50176872604179 0.035816405766439664 0.2046504337319023 1.0 True
123 24 26 6.852918693116795 6.318172961338249 0.6524044389642417 0.12740232296321938 6 21.753053297058692 0.062038641373543625 0.12012273822228971 1.0 True
124 24 27 7.47982907414204 31.60254826458195 0.6546798029556651 0.12425657449629145 6 26.295019203914197 0.05403266260962086 0.2126374478532415 1.0 True
125 24 28 7.71913193400284 51.4648444703387 0.6614377470355731 0.12010155595807545 6 20.98500095492876 0.049490676797893776 0.25138526875505346 1.0 True
126 24 29 7.213852273638132 93.73390958258973 0.6620579958399608 0.12331341986261203 6 28.045882177977884 0.08010108996241684 0.7934458070763889 1.0 False forward_reverse_translation;forward_reverse_rotation
127 25 26 1.2882746074869595 42.944397586466835 0.9137395459976105 0.08148336624555251 6 16.56293455758718 0.0032405549728505064 0.006669174585592186 1.0 True
128 25 27 1.7666235781584831 68.22877288971053 0.8596658711217183 0.1096145882030974 6 22.188153380460985 0.010624789877375215 0.04475651255674721 1.0 True
129 25 28 2.1807182972588706 88.09106909546726 0.8839157491622786 0.10490699814192792 6 16.17217948348072 0.00283419137499405 0.01585308444597649 1.0 True
130 25 29 1.118287177357716 130.36013420771832 0.7857227558401518 0.10954358768244267 6 20.132087187715307 0.003247718742817276 0.01654235380830592 1.0 True
131 25 30 5.200639419572971 139.6773015481418 0.05061061531235322 0.1582745227634446 6 186.52424080569466 2.4401001990983833 2.63047633237589 0.0 False heldout_inlier_ratio;forward_reverse_translation;forward_reverse_rotation;multistart_instability
132 26 27 0.6269948654714946 25.284375303243706 0.9188612099644128 0.07520973241900301 6 20.799051531446562 0.0009920873307779914 0.005485007138529723 1.0 True
133 26 28 0.9431372257108486 45.14667150900044 0.9182726623840114 0.07611768518866212 6 21.99541958409829 0.004984978187527545 0.01173507098896779 1.0 True
134 26 29 1.129925637779203 87.41573662125148 0.7856890251090416 0.10851393061782179 6 24.280602674778656 0.002586732426991948 0.12205883610743135 1.0 True
135 26 30 4.786676837961511 177.3783008654109 0.7634835395750642 0.10495724850674527 6 34.80647378548578 0.008489213184554775 0.021733210949123293 1.0 True
136 26 31 5.900105669197046 156.01119868987135 0.7374054682955207 0.1073752166304431 6 38.310555401782544 3.545322953478412 6.459518815762061 1.0 False forward_reverse_translation;forward_reverse_rotation
137 27 28 0.44809323479571145 19.862296205756735 0.9281131178707225 0.07220943509634768 6 20.093208902964978 0.0023003913248606234 0.001591751329336308 1.0 True
138 27 29 1.160064136065596 62.13136131800778 0.7871188037207112 0.10966659884725236 6 24.009650087098436 0.005134403698947268 0.02434844099203523 1.0 True
139 27 30 4.25412615059095 152.09392556214777 0.7922108208955224 0.10147035321534553 6 32.52026042532504 2.795897032209197 3.237329131939042 1.0 False forward_reverse_translation;forward_reverse_rotation
140 27 31 5.542871649427442 178.70442600700352 0.7592097617664149 0.10539218018667602 6 48.70669958381899 0.0020939062619997405 0.027400662783563425 1.0 True
141 27 32 4.8829350360277 138.47370648510565 0.7480278422273782 0.10408734715846867 6 42.21510199826003 0.004407463145306061 0.03490543724836902 1.0 True
142 28 29 1.5995723610319212 42.26906511225104 0.787814381863266 0.10901449493832843 6 25.913625419675043 0.01570175790237609 0.03569660498137675 1.0 True
143 28 30 4.295353381127667 132.23162935639104 0.7791159962581852 0.10438560755009844 6 36.501677925790155 0.01170149615735483 0.047047156693309715 1.0 True
144 28 31 5.754426930327516 158.8421298011293 0.7449015266285981 0.10668967013687296 6 48.04636971250818 0.024421374377245158 0.6810283060803514 0.5 False forward_reverse_rotation
145 28 32 5.013331477001236 158.33600269086247 0.7309213587715216 0.1081318944415262 6 52.23779663520043 0.10324080390042788 0.9479198721286788 1.0 False forward_reverse_translation;forward_reverse_rotation
146 28 33 5.317102997899902 67.05379893079937 0.6692465836255895 0.10871806386783629 6 54.67376255043738 0.13259351588266985 0.659391929056215 1.0 False forward_reverse_translation;forward_reverse_rotation
147 29 30 4.099530130205247 89.96256424413995 0.7320662880982732 0.10929719051930432 6 30.745355096911158 0.005463604154015333 0.018583483487870766 1.0 True
148 29 31 4.890018768530818 116.57306468887772 0.7254562254562255 0.11323336570178015 6 49.09678922599757 0.0050777995851252426 0.07265894091769333 1.0 True
149 29 32 4.464737187763289 159.39493219688632 0.695837657096737 0.10999820041072961 6 72.25486343140591 0.011220553177198233 0.14599088546526143 1.0 True
150 29 33 4.34670323578768 109.32286404305042 0.6382698298586149 0.11215583949685817 6 72.66655529463955 0.018977478007060265 0.05615608666616428 1.0 True
151 30 31 2.3862917603439455 26.61050044473775 0.8286237272623269 0.0964659694513182 6 41.03513305025223 0.018905314487385798 0.08632503464137033 1.0 True
152 30 32 1.1507634071714652 69.43236795274659 0.8065326633165829 0.09815063400019142 6 35.25635856167173 0.007341509941520892 0.023973741904832375 1.0 True
153 30 33 2.4361832484951105 160.7145717128102 0.6965239055641239 0.10032655671840386 6 47.08947384000088 2.6257249086936962 3.1197630566421646 1.0 False forward_reverse_translation;forward_reverse_rotation
154 31 32 1.2514382602180638 42.82186750800885 0.8146841206602162 0.09914628416022417 6 34.33173181492779 0.06936877058296162 0.45703051311645637 1.0 True
155 31 33 0.641507519046697 134.10407126807203 0.7551430598250177 0.1003246191461096 6 28.537349904719164 0.005559091155811048 0.0330053182501105 1.0 True
156 32 33 1.4370687242949811 91.2822037600631 0.043432078366576185 0.1661924920133195 6 88.4956235178996 0.946171384319174 3.5092968853566924 0.0 False heldout_inlier_ratio;heldout_inlier_rmse;forward_reverse_translation;forward_reverse_rotation;multistart_instability
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,489 @@
{
"schema_version": 2,
"success": true,
"message": "`ftol` termination condition is satisfied.",
"convention": "T_body_lidar maps raw LiDAR points into rear-axle body frame",
"equation": "A_ij X = X B_ij",
"measured_extrinsic_used_as_initial": false,
"translation_m": [
1.2944883843818025,
-0.0008838118010001346,
0.7058442435591689
],
"rotation_rpy_deg_xyz": [
-0.7159364624845366,
1.3526878670674298,
-0.975972089807914
],
"quaternion_xyzw": [
-0.006146489369094902,
0.011856702812467925,
-0.008442354678834595,
0.999875175166545
],
"matrix_4x4": [
[
0.9995762904917872,
0.016736847531056993,
0.02381422728959523,
1.2944883843818025
],
[
-0.01702835592221438,
0.9997818946318248,
0.01209124728823738,
-0.0008838118010001346
],
[
-0.023606663916460907,
-0.01249164125009491,
0.9996432785337048,
0.7058442435591689
],
[
0.0,
0.0,
0.0,
1.0
]
],
"estimation": {
"stations": 34,
"pairs": 57,
"residuals": {
"pairs": 57,
"translation_m": {
"rms": 0.1544348214275169,
"median": 0.0705120546692258,
"p90": 0.2796247931030114,
"p95": 0.38830732511177307,
"max": 0.42173620405868173
},
"rotation_deg": {
"rms": 1.6127366820434261,
"median": 0.7802394472449041,
"p90": 2.0427026963913084,
"p95": 4.139878905679253,
"max": 4.927876933555878
},
"per_pair": [
{
"pair_index": 0,
"translation_m": 0.04921586129986855,
"rotation_deg": 0.7802394472449041
},
{
"pair_index": 1,
"translation_m": 0.06022467069161827,
"rotation_deg": 0.8076387678918286
},
{
"pair_index": 2,
"translation_m": 0.05733334031955927,
"rotation_deg": 0.3801287351167008
},
{
"pair_index": 3,
"translation_m": 0.10178704595686662,
"rotation_deg": 0.23294290596786388
},
{
"pair_index": 4,
"translation_m": 0.0950710667744258,
"rotation_deg": 0.8441747973849835
},
{
"pair_index": 5,
"translation_m": 0.1482159791451249,
"rotation_deg": 0.681326797444399
},
{
"pair_index": 6,
"translation_m": 0.02325894326521952,
"rotation_deg": 0.6033104606942519
},
{
"pair_index": 7,
"translation_m": 0.07474087499522525,
"rotation_deg": 0.6507288891948437
},
{
"pair_index": 8,
"translation_m": 0.18825515016508942,
"rotation_deg": 0.26486292076967255
},
{
"pair_index": 9,
"translation_m": 0.04309283328904936,
"rotation_deg": 0.13788972833567684
},
{
"pair_index": 10,
"translation_m": 0.04634466299044426,
"rotation_deg": 0.727856852897771
},
{
"pair_index": 11,
"translation_m": 0.043890799917503956,
"rotation_deg": 0.7033085139548623
},
{
"pair_index": 12,
"translation_m": 0.06569153273548976,
"rotation_deg": 0.8215251942968772
},
{
"pair_index": 13,
"translation_m": 0.05085505830053342,
"rotation_deg": 0.5703942163369722
},
{
"pair_index": 14,
"translation_m": 0.08080041374696341,
"rotation_deg": 0.7042489825699358
},
{
"pair_index": 15,
"translation_m": 0.028283768727744116,
"rotation_deg": 0.5815552521395532
},
{
"pair_index": 16,
"translation_m": 0.034152028863445455,
"rotation_deg": 1.0076363281960339
},
{
"pair_index": 17,
"translation_m": 0.10661935094245772,
"rotation_deg": 0.585736616100307
},
{
"pair_index": 18,
"translation_m": 0.0705120546692258,
"rotation_deg": 0.8052707169746115
},
{
"pair_index": 19,
"translation_m": 0.06442322486081886,
"rotation_deg": 2.1113003980243557
},
{
"pair_index": 20,
"translation_m": 0.058531034305082885,
"rotation_deg": 0.5873890947329841
},
{
"pair_index": 21,
"translation_m": 0.06915436202628812,
"rotation_deg": 0.4455496836075116
},
{
"pair_index": 22,
"translation_m": 0.1484005911609239,
"rotation_deg": 1.9969708953026104
},
{
"pair_index": 23,
"translation_m": 0.10429385187976488,
"rotation_deg": 0.12781289285231975
},
{
"pair_index": 24,
"translation_m": 0.06859842051942554,
"rotation_deg": 0.3198403871694416
},
{
"pair_index": 25,
"translation_m": 0.037990236699121674,
"rotation_deg": 0.631070403868711
},
{
"pair_index": 26,
"translation_m": 0.03189885614531801,
"rotation_deg": 0.7541329389301039
},
{
"pair_index": 27,
"translation_m": 0.07672961622319935,
"rotation_deg": 1.5806114049925588
},
{
"pair_index": 28,
"translation_m": 0.069983101896218,
"rotation_deg": 0.4729689795147634
},
{
"pair_index": 29,
"translation_m": 0.016135065100391668,
"rotation_deg": 0.3789407761818106
},
{
"pair_index": 30,
"translation_m": 0.07432095926140471,
"rotation_deg": 1.8120862981058055
},
{
"pair_index": 31,
"translation_m": 0.17890921343127023,
"rotation_deg": 3.97184190714068
},
{
"pair_index": 32,
"translation_m": 0.2459042707502872,
"rotation_deg": 4.617881335971262
},
{
"pair_index": 33,
"translation_m": 0.06334407979243162,
"rotation_deg": 0.8238155509065317
},
{
"pair_index": 34,
"translation_m": 0.07743513463604834,
"rotation_deg": 4.0203782981062535
},
{
"pair_index": 35,
"translation_m": 0.15971289980951447,
"rotation_deg": 4.84466491195667
},
{
"pair_index": 36,
"translation_m": 0.13395086756218255,
"rotation_deg": 4.927876933555878
},
{
"pair_index": 37,
"translation_m": 0.06599998782040693,
"rotation_deg": 1.9960824610834838
},
{
"pair_index": 38,
"translation_m": 0.04503412561992633,
"rotation_deg": 1.279332716204477
},
{
"pair_index": 39,
"translation_m": 0.11215573119400024,
"rotation_deg": 0.5578231171630792
},
{
"pair_index": 40,
"translation_m": 0.05509905448014895,
"rotation_deg": 0.6608982729619334
},
{
"pair_index": 41,
"translation_m": 0.007272665876828039,
"rotation_deg": 1.2124341117031159
},
{
"pair_index": 42,
"translation_m": 0.055811383114362484,
"rotation_deg": 0.8153248357421402
},
{
"pair_index": 43,
"translation_m": 0.03138324591270751,
"rotation_deg": 1.2136591021205085
},
{
"pair_index": 44,
"translation_m": 0.13982233632280833,
"rotation_deg": 1.2089791291543672
},
{
"pair_index": 45,
"translation_m": 0.42173620405868173,
"rotation_deg": 1.126390973143559
},
{
"pair_index": 46,
"translation_m": 0.027936086071527602,
"rotation_deg": 0.5561027320483051
},
{
"pair_index": 47,
"translation_m": 0.08146548874814821,
"rotation_deg": 1.5984131745791201
},
{
"pair_index": 48,
"translation_m": 0.40597723810744263,
"rotation_deg": 1.3479854285502366
},
{
"pair_index": 49,
"translation_m": 0.3864154920341234,
"rotation_deg": 0.6215042157245596
},
{
"pair_index": 50,
"translation_m": 0.18945577362130134,
"rotation_deg": 0.596177211598774
},
{
"pair_index": 51,
"translation_m": 0.37813335218979344,
"rotation_deg": 1.0668602650100487
},
{
"pair_index": 52,
"translation_m": 0.330205576632098,
"rotation_deg": 0.40979383494055255
},
{
"pair_index": 53,
"translation_m": 0.39587465742237204,
"rotation_deg": 0.7934150366951904
},
{
"pair_index": 54,
"translation_m": 0.060998991546499626,
"rotation_deg": 0.9224390837063629
},
{
"pair_index": 55,
"translation_m": 0.0952510183208545,
"rotation_deg": 0.8616718556937568
},
{
"pair_index": 56,
"translation_m": 0.10976329201195999,
"rotation_deg": 0.5415573303416338
}
]
}
},
"ground": {
"planes": 34,
"body_origin_height_above_ground_m": 0.2335,
"formula": "d_lidar - (R_X n_lidar)^T t_X - body_height"
},
"linearized_one_sigma": {
"translation_m": [
0.006072210871275005,
0.0060852661675311025,
0.005465720025368553
],
"rotation_deg": [
0.06255688904352227,
0.06213349734715941,
0.13055508827073753
],
"warning": "conditional local estimate; bootstrap is the primary stability check"
},
"weighted_jacobian_condition_number": 6.040138286165465,
"solver_multistart": {
"runs": 12,
"candidates_relative_to_best": [
{
"cost": 324.2098227991519,
"success": true,
"translation_m": 5.570266299009551e-09,
"rotation_deg": 1.4455049406405054e-07
},
{
"cost": 324.20982279914983,
"success": true,
"translation_m": 0.0,
"rotation_deg": 0.0
},
{
"cost": 324.20982279915165,
"success": true,
"translation_m": 5.216823166795417e-09,
"rotation_deg": 1.973338877475283e-07
},
{
"cost": 324.20982279915034,
"success": true,
"translation_m": 1.3901066622882166e-09,
"rotation_deg": 6.335210062786435e-08
},
{
"cost": 324.20982279914995,
"success": true,
"translation_m": 3.578733410578741e-10,
"rotation_deg": 6.852733510041159e-09
},
{
"cost": 324.20982279914983,
"success": true,
"translation_m": 6.594966941115627e-10,
"rotation_deg": 2.2216701960527812e-08
},
{
"cost": 324.20982279914983,
"success": true,
"translation_m": 2.1650538139486925e-09,
"rotation_deg": 3.6255288379996055e-08
},
{
"cost": 324.2098227991501,
"success": true,
"translation_m": 1.5027981325208515e-09,
"rotation_deg": 6.326578543393082e-08
},
{
"cost": 324.20982279914983,
"success": true,
"translation_m": 6.094320168262204e-10,
"rotation_deg": 1.025130160040861e-08
},
{
"cost": 324.2098227991552,
"success": true,
"translation_m": 1.057266183614633e-08,
"rotation_deg": 3.643863231629731e-07
},
{
"cost": 324.20982279915063,
"success": true,
"translation_m": 3.9489601150102006e-09,
"rotation_deg": 1.359274031832874e-07
},
{
"cost": 324.20982279914983,
"success": true,
"translation_m": 6.276644321676319e-10,
"rotation_deg": 2.14493230752589e-08
}
]
},
"bootstrap": {
"runs": 100,
"order": [
"x_m",
"y_m",
"z_m",
"roll_deg",
"pitch_deg",
"yaw_deg"
],
"std": [
0.003048001100427371,
0.0025869752895426976,
0.0014870952541001317,
0.09174691854497609,
0.06502786700039134,
0.11777557058586356
],
"p025": [
1.2892012505330883,
-0.0051467048024525786,
0.7035478241042927,
-0.8634755073850175,
1.2139289883714506,
-1.173046710195043
],
"p975": [
1.2997598841406142,
0.005003259375615847,
0.7090711194920217,
-0.48035430043405547,
1.4534667429791082,
-0.7593758945747364
]
}
}
+7
View File
@@ -0,0 +1,7 @@
# 跨批比较
- `extrinsic_difference.json`:data4 X 相对历史部署 X 的严格 SE(3) 差,约 `1.592 cm / 0.234°`
- `old_extrinsic_on_data4.json`:历史部署 X 在 data4 B 上的残差,约 `0.11953 m / 1.24836°`
- `data4_extrinsic_on_previous_batch2.json`:data4 X 在历史第二批 B 上的残差,约 `0.07931 m / 0.98926°`
两套 X 的跨批表现接近,而 data4 自身估计残差更高;因此维持历史部署值,data4 只作为候选。
@@ -0,0 +1,298 @@
{
"role": "auxiliary check only; first-batch RTK is sparse",
"blind_with_respect_to_X": true,
"note": "No AX residual was used to select these pairs",
"stations": 38,
"metrics": {
"pairs": 39,
"translation_m": {
"rms": 0.07931219315845621,
"median": 0.0521831021438143,
"p90": 0.1205091988381832,
"p95": 0.14405284012014574,
"max": 0.23166749945892554
},
"rotation_deg": {
"rms": 0.9892579681085032,
"median": 0.8987168885592437,
"p90": 1.5241504377222574,
"p95": 1.5453098245907155,
"max": 1.8288069823538258
},
"per_pair": [
{
"pair_index": 0,
"translation_m": 0.13184857122951013,
"rotation_deg": 0.4578882478759752,
"i": 0,
"j": 2
},
{
"pair_index": 1,
"translation_m": 0.11088249231082419,
"rotation_deg": 0.49123829458862667,
"i": 0,
"j": 3
},
{
"pair_index": 2,
"translation_m": 0.06122066543511726,
"rotation_deg": 0.46678985990983063,
"i": 1,
"j": 2
},
{
"pair_index": 3,
"translation_m": 0.05361370234686629,
"rotation_deg": 0.6513155675962176,
"i": 1,
"j": 3
},
{
"pair_index": 4,
"translation_m": 0.023371189257265605,
"rotation_deg": 0.3726639533978929,
"i": 2,
"j": 3
},
{
"pair_index": 5,
"translation_m": 0.07839704351948763,
"rotation_deg": 0.9876540671880906,
"i": 2,
"j": 4
},
{
"pair_index": 6,
"translation_m": 0.06371505126502003,
"rotation_deg": 0.8674541404693262,
"i": 3,
"j": 4
},
{
"pair_index": 7,
"translation_m": 0.09440077842634854,
"rotation_deg": 1.176934949298133,
"i": 3,
"j": 5
},
{
"pair_index": 8,
"translation_m": 0.034738581722701674,
"rotation_deg": 0.46221894567574257,
"i": 4,
"j": 5
},
{
"pair_index": 9,
"translation_m": 0.1368264467472551,
"rotation_deg": 1.0614354353201445,
"i": 6,
"j": 7
},
{
"pair_index": 10,
"translation_m": 0.003100407410401193,
"rotation_deg": 0.6140746474880827,
"i": 7,
"j": 9
},
{
"pair_index": 11,
"translation_m": 0.06611151014323934,
"rotation_deg": 0.09617682103039936,
"i": 8,
"j": 9
},
{
"pair_index": 12,
"translation_m": 0.11767435574035143,
"rotation_deg": 0.8987168885592437,
"i": 8,
"j": 10
},
{
"pair_index": 13,
"translation_m": 0.05534374617362978,
"rotation_deg": 0.9177398533680782,
"i": 9,
"j": 10
},
{
"pair_index": 14,
"translation_m": 0.016375449524654653,
"rotation_deg": 1.8288069823538258,
"i": 12,
"j": 14
},
{
"pair_index": 15,
"translation_m": 0.23166749945892554,
"rotation_deg": 0.4806436956517463,
"i": 13,
"j": 15
},
{
"pair_index": 16,
"translation_m": 0.20909038047616038,
"rotation_deg": 1.1888833539076575,
"i": 14,
"j": 16
},
{
"pair_index": 17,
"translation_m": 0.04954727606060992,
"rotation_deg": 0.7479656125581431,
"i": 17,
"j": 18
},
{
"pair_index": 18,
"translation_m": 0.0401572454810912,
"rotation_deg": 1.042619605722841,
"i": 18,
"j": 19
},
{
"pair_index": 19,
"translation_m": 0.0432861701880587,
"rotation_deg": 0.6965786478374109,
"i": 20,
"j": 21
},
{
"pair_index": 20,
"translation_m": 0.010709015812516234,
"rotation_deg": 1.6253345198252291,
"i": 22,
"j": 23
},
{
"pair_index": 21,
"translation_m": 0.058199199584633175,
"rotation_deg": 0.36595813742919014,
"i": 22,
"j": 24
},
{
"pair_index": 22,
"translation_m": 0.030262860466569532,
"rotation_deg": 1.1081362850905554,
"i": 22,
"j": 25
},
{
"pair_index": 23,
"translation_m": 0.04000865510964372,
"rotation_deg": 1.189922759500818,
"i": 23,
"j": 24
},
{
"pair_index": 24,
"translation_m": 0.06464346915846221,
"rotation_deg": 0.7699901460922239,
"i": 24,
"j": 25
},
{
"pair_index": 25,
"translation_m": 0.0521831021438143,
"rotation_deg": 0.18489023256895315,
"i": 25,
"j": 26
},
{
"pair_index": 26,
"translation_m": 0.08267778191372169,
"rotation_deg": 0.9059437837656906,
"i": 25,
"j": 27
},
{
"pair_index": 27,
"translation_m": 0.038589920757972004,
"rotation_deg": 0.8404190717226915,
"i": 26,
"j": 27
},
{
"pair_index": 28,
"translation_m": 0.05488335734683258,
"rotation_deg": 1.5217701038732556,
"i": 26,
"j": 28
},
{
"pair_index": 29,
"translation_m": 0.022562161090077113,
"rotation_deg": 1.5336717731182645,
"i": 26,
"j": 29
},
{
"pair_index": 30,
"translation_m": 0.016794927865731942,
"rotation_deg": 1.4945757001638067,
"i": 27,
"j": 30
},
{
"pair_index": 31,
"translation_m": 0.08618762377782405,
"rotation_deg": 1.5364181917868804,
"i": 30,
"j": 31
},
{
"pair_index": 32,
"translation_m": 0.06980420384374487,
"rotation_deg": 0.7086752430201493,
"i": 30,
"j": 32
},
{
"pair_index": 33,
"translation_m": 0.032647030192474995,
"rotation_deg": 1.2921319816631744,
"i": 32,
"j": 33
},
{
"pair_index": 34,
"translation_m": 0.0345860258877476,
"rotation_deg": 1.374123371412574,
"i": 32,
"j": 34
},
{
"pair_index": 35,
"translation_m": 0.05213790087077668,
"rotation_deg": 0.46701073818826994,
"i": 32,
"j": 35
},
{
"pair_index": 36,
"translation_m": 0.023288081661695596,
"rotation_deg": 0.15454273191995999,
"i": 33,
"j": 34
},
{
"pair_index": 37,
"translation_m": 0.035256268159629366,
"rotation_deg": 0.9445397555422734,
"i": 33,
"j": 35
},
{
"pair_index": 38,
"translation_m": 0.020736197670988343,
"rotation_deg": 1.137619185440334,
"i": 35,
"j": 36
}
]
}
}
@@ -0,0 +1,43 @@
{
"convention": "delta = inverse(reference) @ candidate",
"reference": "results\\01_previous_two_batches\\final_extrinsic_deployment.json",
"candidate": "results\\02_data4_calibration\\consensus\\extrinsic.json",
"candidate_minus_reference_translation_xyz_m": [
0.002616328047628791,
-0.0016393602019739468,
-0.01562078923962118
],
"candidate_minus_reference_rpy_xyz_deg": [
-0.006740470942029009,
0.1911623881626312,
-0.13523257777677677
],
"relative_translation_norm_m": 0.01592299377608647,
"relative_rotation_deg": 0.23416878770707064,
"relative_matrix_4x4": [
[
0.9999916502190469,
0.0023133957658166954,
0.003368633583168801,
0.0029672356800607425
],
[
-0.0023136119190219507,
0.9999973217800623,
6.027097638170339e-05,
-0.0013876386622047616
],
[
-0.0033684851306055737,
-6.80641839411564e-05,
0.999994324321489,
-0.015582416441470737
],
[
0.0,
0.0,
0.0,
1.0
]
]
}
@@ -0,0 +1,207 @@
{
"role": "auxiliary check only; first-batch RTK is sparse",
"blind_with_respect_to_X": true,
"note": "No AX residual was used to select these pairs",
"stations": 34,
"metrics": {
"pairs": 26,
"translation_m": {
"rms": 0.11953342461680516,
"median": 0.059870553274397435,
"p90": 0.11852081980116268,
"p95": 0.27001896145006843,
"max": 0.3924432090639719
},
"rotation_deg": {
"rms": 1.248363434126574,
"median": 0.7472573318442859,
"p90": 1.7836452528035456,
"p95": 2.0119235382379705,
"max": 4.302962170390954
},
"per_pair": [
{
"pair_index": 0,
"translation_m": 0.05017939301544147,
"rotation_deg": 0.7621142667147064,
"i": 0,
"j": 2
},
{
"pair_index": 1,
"translation_m": 0.05476119605552225,
"rotation_deg": 0.39488243852657817,
"i": 2,
"j": 3
},
{
"pair_index": 2,
"translation_m": 0.11117501211320562,
"rotation_deg": 0.29330702638456563,
"i": 2,
"j": 5
},
{
"pair_index": 3,
"translation_m": 0.06643052326439366,
"rotation_deg": 0.5530967273631149,
"i": 3,
"j": 5
},
{
"pair_index": 4,
"translation_m": 0.028182994383263396,
"rotation_deg": 0.5499639343413396,
"i": 5,
"j": 8
},
{
"pair_index": 5,
"translation_m": 0.06086463130525318,
"rotation_deg": 0.6985420749542534,
"i": 6,
"j": 7
},
{
"pair_index": 6,
"translation_m": 0.020630277131513722,
"rotation_deg": 0.6820744752666614,
"i": 6,
"j": 8
},
{
"pair_index": 7,
"translation_m": 0.046848360772821,
"rotation_deg": 0.8723393366986318,
"i": 12,
"j": 14
},
{
"pair_index": 8,
"translation_m": 0.05887647524354169,
"rotation_deg": 1.9071782288631753,
"i": 13,
"j": 16
},
{
"pair_index": 9,
"translation_m": 0.11661768017132504,
"rotation_deg": 2.046838641362902,
"i": 15,
"j": 16
},
{
"pair_index": 10,
"translation_m": 0.06473893802255462,
"rotation_deg": 0.30644679433866145,
"i": 15,
"j": 17
},
{
"pair_index": 11,
"translation_m": 0.04963073408543951,
"rotation_deg": 0.33998803648541337,
"i": 15,
"j": 18
},
{
"pair_index": 12,
"translation_m": 0.03313618555659487,
"rotation_deg": 0.5283154781000993,
"i": 16,
"j": 19
},
{
"pair_index": 13,
"translation_m": 0.0691797699772094,
"rotation_deg": 0.6320358674350408,
"i": 21,
"j": 22
},
{
"pair_index": 14,
"translation_m": 0.042364105706927925,
"rotation_deg": 1.660112276743916,
"i": 21,
"j": 24
},
{
"pair_index": 15,
"translation_m": 0.07073443988383908,
"rotation_deg": 0.8343384286314613,
"i": 22,
"j": 23
},
{
"pair_index": 16,
"translation_m": 0.12042395943100032,
"rotation_deg": 4.302962170390954,
"i": 22,
"j": 25
},
{
"pair_index": 17,
"translation_m": 0.05181438442870987,
"rotation_deg": 0.8455629957527,
"i": 25,
"j": 26
},
{
"pair_index": 18,
"translation_m": 0.032857670083788,
"rotation_deg": 0.7324003969738653,
"i": 25,
"j": 28
},
{
"pair_index": 19,
"translation_m": 0.05285636518357229,
"rotation_deg": 0.4369186254063126,
"i": 26,
"j": 27
},
{
"pair_index": 20,
"translation_m": 0.10251045240958172,
"rotation_deg": 1.0529040723274141,
"i": 26,
"j": 29
},
{
"pair_index": 21,
"translation_m": 0.023054151271876995,
"rotation_deg": 0.24976946582216739,
"i": 27,
"j": 28
},
{
"pair_index": 22,
"translation_m": 0.06593100197822659,
"rotation_deg": 1.2515362824917693,
"i": 27,
"j": 29
},
{
"pair_index": 23,
"translation_m": 0.31988396212309117,
"rotation_deg": 1.0256849788735003,
"i": 29,
"j": 30
},
{
"pair_index": 24,
"translation_m": 0.3924432090639719,
"rotation_deg": 0.8042671494244682,
"i": 29,
"j": 31
},
{
"pair_index": 25,
"translation_m": 0.11357259403662164,
"rotation_deg": 0.901297615800165,
"i": 30,
"j": 32
}
]
}
}
+15 -54
View File
@@ -1,60 +1,21 @@
# 标定结果索引
# 结果目录
本目录保存本次双天线 RTK—3D LiDAR 手眼标定的可复现结果。坐标约定统一为:
本目录只归档轻量标定产物,不包含原始 dlog、rscap、逐帧 NPZ 或完整点云。
```text
X = T_body_lidar
```
即外参将**原始 LiDAR 点**变换到以**后轮轴中心**为原点、X 前/Y 左/Z 上的车体系。
## 部署时只使用这一个文件
[`final_extrinsic_deployment.json`](final_extrinsic_deployment.json) 是唯一用于部署的外参文件:
```text
translation [m] = [ 1.297759692, -0.000067331, 0.720497835 ]
RPY xyz [deg] = [ -0.785151146, 1.202660822, -0.835510053 ]
```
## 顶层汇总文件
| 文件 | 用途 | 是否用于部署 |
| 目录 | 内容 | 使用建议 |
|---|---|---|
| [`final_extrinsic_deployment.json`](final_extrinsic_deployment.json) | 精简、固定的最终外参;含平移、RPY、四元数和 4×4 矩阵。 | **是,唯一入口** |
| [`final_extrinsic_recommended.json`](final_extrinsic_recommended.json) | 同一算法外参的完整求解记录、残差、地面约束、条件数和 Bootstrap。 | 否;用于审计/复现。 |
| [`final_summary.json`](final_summary.json) | 共识结果的摘要。 | 否;用于查看指标。 |
| [`comparison_summary.json`](comparison_summary.json) | Open3D GICP、small_gicp 与共识解的对比。 | 否;用于方法对比。 |
| `01_previous_two_batches/` | 第二批求解、第一批辅助复核的历史部署结果;含 Open3D、small_gicp、consensus B、质量和灵敏度文件 | 当前部署基线 |
| `02_data4_calibration/` | data4 的 Open3D、small_gicp、consensus B 和独立 X | 候选/稳定性证据 |
| `03_comparison/` | 两套 X 的 SE(3) 差以及互换批次后的 AX 残差 | 跨批判断依据 |
`final_extrinsic_deployment.json``final_extrinsic_recommended.json` 的外参数值相同;前者只是删除了求解过程字段,供上游程序稳定读取
最终部署文件是 `01_previous_two_batches/final_extrinsic_deployment.json``02_data4_calibration/final_extrinsic_data4.json` 不应在没有新增独立证据时覆盖它
## 子目录
常见文件:
| 目录 | 内容 | 用途 |
|---|---|---|
| [`common/`](common/) | 第二批每个静止站点拟合得到的地面平面。 | 地面约束检查。 |
| [`open3d_gicp/`](open3d_gicp/) | Open3D GICP 生成的 LiDAR 相对运动 B、质量指标、精筛记录和对应外参。 | 后端一的独立结果。 |
| [`small_gicp/`](small_gicp/) | small_gicp 生成的 LiDAR 相对运动 B、质量指标、精筛记录和对应外参。 | 后端二的独立结果。 |
| [`consensus/`](consensus/) | 两后端一致性筛选后的共识 B,共识外参和第一批辅助复核。 | 主要求解依据。 |
| [`diagnostics/`](diagnostics/) | 单运动对 roll/pitch/yaw 局部灵敏度扫描。 | 仅诊断,不可直接当外参。 |
## 如何识别 B 文件
每个 `.npz` 都是 LiDAR 相对运动集合 `B_ij = T_Li_Lj`,把第 j 个静止站点的点云变换到第 i 个站点 LiDAR 坐标系。它们不是车体—雷达外参,不能直接写入部署配置。
- `B_batch2_*`:第二批 38 站高频 RTK 数据,主要求解使用。
- `B_batch1_*`:第一批 38 站稀疏 RTK 数据,仅作辅助复核。
- `*_estimation.npz`:点云配准的初始候选集合。
- `*_refined.npz`:经过点云质量门限后的集合。
- `consensus/B_batch2_consensus.npz`Open3D 与 small_gicp 对同一运动对的 B 差异不超过 `0.05 m / 0.50°` 后保留的 39 对;它是最终主求解输入。
同名的 `.quality.json/.csv` 记录配准质量,`.refinement.json` 记录精筛过程,`.consensus.json` 记录跨后端一致性筛选。它们都用于追溯,不用于部署。
## 结果使用边界
- 主结果由第二批数据求解;第一批只作为独立辅助检查,不参与主结果拟合。
- B 的筛选先于 X 的求解,并且不读取待求外参 X,不用 AX 残差反过来选择 B。
- 第二批共识集的 AX RMS 为 `0.07985 m / 0.96118°`;旋转残差约 1°,因此不是厘米级绝对精度认证。
- 当前 RTK 轨迹只有位置和 yaw,没有 roll/pitch。AX 残差对 Z 不可观;Z 必须结合地面约束和独立机械量测确认。
更多流程、运行命令和可视化说明见仓库根目录的 [`README.md`](../README.md)。
- `B_*_estimation.npz`:后端原始运动对 B。
- `B_*_quality.json/csv`:配准和 Hessian/信息矩阵质量。
- `B_*_refined.npz`X 无关筛选后的 B。
- `B_*_consensus.npz``consensus/B_consensus.npz`:双后端认可的 B。
- `extrinsic*.json`:由对应 B 和地面约束求出的 X。
- `*_check.json`:把给定 X 应用于另一批 B 的残差,不是重新求 X。
- `ground_planes*.csv`:每站地面平面参数。
+27
View File
@@ -0,0 +1,27 @@
# run
本目录是 PowerShell 运行入口;完整流程、参数解释、验收方法和代码职责统一见仓库根目录 `README.md`
| 脚本 | 使用时机 |
|---|---|
| `export_legacy_stations.ps1` | 第一、第二批式逐站 dlog 导出。 |
| `export_multisensor_stations.ps1` | LiDAR dlog + 独立 RTK/IMU rscap 导出和时间关联。 |
| `prepare_legacy_dataset.ps1` | 旧式导出结果生成 prepared。 |
| `prepare_multisensor_dataset.ps1` | 多传感器 combined 结果生成 prepared。 |
| `run_single_dataset.ps1` | 单批数据运行 small_gicp、Open3D、consensus 和 X 求解。 |
| `run_all.ps1` | 第二批求解、第一批辅助复核的历史流程。 |
| `run_consensus_finish.ps1` | 从已有两个后端 B 重做 consensus。 |
| `run_sensitivity_scan.ps1` | 指定 Pair 的外参角度灵敏度扫描。 |
| `view_result.ps1` | 传入 frames、B、X 查看 3D 配准及增量。 |
最常用的 3D 入口:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\run\view_result.ps1" `
-Frames "D:\你的数据目录\prepared\frames_all" `
-Pairs "D:\你的结果目录\B_consensus.npz" `
-Extrinsic "D:\你的结果目录\extrinsic.json" `
-PairIndex 0
```
`frames_all` 必须与生成 B 时的站点顺序一致。
+34
View File
@@ -0,0 +1,34 @@
param(
[Parameter(Mandatory = $true)][string]$DataRoot,
[Parameter(Mandatory = $true)][string]$OutputRoot,
[string]$LidarObject = "frontlidar",
[string]$Timezone = "+08:00",
[string[]]$StationNames = @(),
[int]$Stride = 1,
[double]$RtkMaxDtMs = 150.0
)
$ErrorActionPreference = "Stop"
$Repo = Split-Path -Parent $PSScriptRoot
$Exporter = Join-Path $Repo "tools\frontlidar_dlog_export.py"
if (-not (Test-Path -LiteralPath $DataRoot)) { throw "DataRoot does not exist: $DataRoot" }
if ($Stride -lt 1) { throw "Stride must be at least 1" }
if ($StationNames.Count -gt 0) {
$Stations = @($StationNames | ForEach-Object { Get-Item -LiteralPath (Join-Path $DataRoot $_) })
} else {
$Stations = @(Get-ChildItem -LiteralPath $DataRoot -Directory | Where-Object {
(Test-Path -LiteralPath (Join-Path $_.FullName "dobject")) -and
(Test-Path -LiteralPath (Join-Path $_.FullName "dobject_recording"))
} | Sort-Object Name)
}
if ($Stations.Count -eq 0) { throw "No station directory containing dobject and dobject_recording was found" }
New-Item -ItemType Directory -Force -Path $OutputRoot | Out-Null
foreach ($Station in $Stations) {
$Out = Join-Path $OutputRoot $Station.Name
Write-Host "[legacy station $($Station.Name)]"
& python $Exporter --dlog $Station.FullName --out $Out --object $LidarObject --format npz `
--timezone $Timezone --stride $Stride --compress --rtk-sidecars --write-reports --resume `
--rtk-max-dt-ms $RtkMaxDtMs
if ($LASTEXITCODE -ne 0) { throw "Export failed for station $($Station.Name)" }
}
Write-Host "Exported stations: $($Stations.Count)"
+89
View File
@@ -0,0 +1,89 @@
param(
[Parameter(Mandatory = $true)][string]$DataRoot,
[Parameter(Mandatory = $true)][string]$OutputRoot,
[Parameter(Mandatory = $true)][string]$RtkCapture,
[Parameter(Mandatory = $true)][string]$ImuCapture,
[string]$LidarObject = "frontlidar",
[string]$Timezone = "+08:00",
[string[]]$StationNames = @(),
[int]$Stride = 1,
[double]$RtkMaxDtMs = 150.0,
[double]$ImuBeforeMs = 100.0,
[double]$ImuAfterMs = 100.0,
[switch]$SkipLidarExport,
[switch]$SkipSerialParsing
)
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
$Exporter = Join-Path $RepoRoot "tools\frontlidar_dlog_export.py"
$Builder = Join-Path $RepoRoot "tools\build_multisensor_npz.py"
$Parser = Join-Path $RepoRoot "tools\rscap_v2\parse_rtk_imu_v2.py"
$Auditor = Join-Path $RepoRoot "tools\rscap_v2\audit_capture_v2.py"
$ExportRoot = Join-Path $OutputRoot "export"
$ParsedRoot = Join-Path $OutputRoot "parsed"
$CombinedRoot = Join-Path $OutputRoot "combined"
function Run-Python {
param([string]$Stage, [string[]]$Arguments)
Write-Host "[$Stage]"
& python @Arguments
if ($LASTEXITCODE -ne 0) {
throw "$Stage failed with Python exit code $LASTEXITCODE"
}
}
foreach ($Path in @($DataRoot, $RtkCapture, $ImuCapture)) {
if (-not (Test-Path -LiteralPath $Path)) { throw "Input does not exist: $Path" }
}
if ($Stride -lt 1) { throw "Stride must be at least 1" }
if ($StationNames.Count -gt 0) {
$Stations = @($StationNames | ForEach-Object { Get-Item -LiteralPath (Join-Path $DataRoot $_) })
} else {
$Stations = @(Get-ChildItem -LiteralPath $DataRoot -Directory | Where-Object {
(Test-Path -LiteralPath (Join-Path $_.FullName "dobject")) -and
(Test-Path -LiteralPath (Join-Path $_.FullName "dobject_recording"))
} | Sort-Object Name)
}
if ($Stations.Count -eq 0) { throw "No station directory containing dobject and dobject_recording was found" }
New-Item -ItemType Directory -Force -Path $OutputRoot | Out-Null
if (-not $SkipSerialParsing) {
New-Item -ItemType Directory -Force -Path $ParsedRoot | Out-Null
Run-Python "capture audit" @($Auditor, $RtkCapture, $ImuCapture, "--out", (Join-Path $OutputRoot "capture_audit.json"))
Run-Python "RTK/IMU parse" @($Parser, "--rtk", $RtkCapture, "--imu", $ImuCapture, "--out", $ParsedRoot)
}
foreach ($Station in $Stations) {
$StationOut = Join-Path $ExportRoot $Station.Name
if (-not $SkipLidarExport) {
Run-Python "LiDAR station $($Station.Name)" @(
$Exporter, "--dlog", $Station.FullName, "--out", $StationOut,
"--object", $LidarObject, "--format", "npz", "--timezone", $Timezone,
"--stride", "$Stride", "--compress", "--skip-rtk", "--write-reports", "--resume"
)
}
if (-not (Test-Path -LiteralPath (Join-Path $StationOut "frames"))) {
throw "Exported frame directory is absent for station $($Station.Name): $StationOut"
}
}
$BuildArgs = @($Builder)
foreach ($Station in $Stations) {
$Frames = Join-Path (Join-Path $ExportRoot $Station.Name) "frames"
$BuildArgs += @("--lidar", "$($Station.Name)=$Frames")
}
$BuildArgs += @(
"--rtk", (Join-Path $ParsedRoot "rtk.jsonl"),
"--imu", (Join-Path $ParsedRoot "imu.jsonl"),
"--out", $CombinedRoot,
"--rtk-max-dt-ms", "$RtkMaxDtMs",
"--imu-before-ms", "$ImuBeforeMs",
"--imu-after-ms", "$ImuAfterMs",
"--overwrite"
)
Run-Python "LiDAR/RTK/IMU association" $BuildArgs
Write-Host "Completed stations: $($Stations.Count)"
Write-Host "Combined NPZ: $CombinedRoot"
+21
View File
@@ -0,0 +1,21 @@
param(
[Parameter(Mandatory = $true)][string]$ExportRoot,
[Parameter(Mandatory = $true)][string]$Output,
[Parameter(Mandatory = $true)][double]$HeadingOffsetDeg,
[Parameter(Mandatory = $true)][double[]]$AntennaLever,
[int]$MinStations = 30,
[int]$ExpectedStations = 0,
[double]$HeadingStdLimitDeg = [double]::PositiveInfinity,
[switch]$Overwrite
)
$ErrorActionPreference = "Stop"
if ($AntennaLever.Count -ne 3) { throw "AntennaLever must contain X,Y,Z in body coordinates" }
$Repo = Split-Path -Parent $PSScriptRoot
$Args = @((Join-Path $Repo "tools\prepare_station_dataset.py"), "--export-root", $ExportRoot,
"--output", $Output, "--heading-offset-deg", "$HeadingOffsetDeg", "--antenna-lever") +
@($AntennaLever | ForEach-Object { "$_" }) + @("--min-stations", "$MinStations",
"--expected-stations", "$ExpectedStations", "--heading-std-limit-deg", "$HeadingStdLimitDeg")
if ($Overwrite) { $Args += "--overwrite" }
& python @Args
if ($LASTEXITCODE -ne 0) { throw "Legacy dataset preparation failed" }
+21
View File
@@ -0,0 +1,21 @@
param(
[Parameter(Mandatory = $true)][string]$CombinedRoot,
[Parameter(Mandatory = $true)][string]$Output,
[Parameter(Mandatory = $true)][double]$HeadingOffsetDeg,
[Parameter(Mandatory = $true)][double[]]$AntennaLever,
[int]$MinStations = 30,
[int]$ExpectedStations = 0,
[double]$HeadingStdLimitDeg = 0.5,
[switch]$Overwrite
)
$ErrorActionPreference = "Stop"
if ($AntennaLever.Count -ne 3) { throw "AntennaLever must contain X,Y,Z in body coordinates" }
$Repo = Split-Path -Parent $PSScriptRoot
$Args = @((Join-Path $Repo "tools\prepare_multisensor_station_dataset.py"), "--combined-root", $CombinedRoot,
"--output", $Output, "--heading-offset-deg", "$HeadingOffsetDeg", "--antenna-lever") +
@($AntennaLever | ForEach-Object { "$_" }) + @("--min-stations", "$MinStations",
"--expected-stations", "$ExpectedStations", "--heading-std-limit-deg", "$HeadingStdLimitDeg")
if ($Overwrite) { $Args += "--overwrite" }
& python @Args
if ($LASTEXITCODE -ne 0) { throw "Multisensor dataset preparation failed" }
+59 -17
View File
@@ -1,32 +1,74 @@
param(
[string]$Batch1Prepared = "C:\Users\admin\Documents\Codex\2026-07-15\wo\outputs\calibration_20260717\prepared",
[string]$Batch2Prepared = "C:\Users\admin\Documents\Codex\2026-07-15\wo\outputs\calibration_data1_20260720\prepared"
[Parameter(Mandatory = $true)][string]$Batch1Prepared,
[Parameter(Mandatory = $true)][string]$Batch2Prepared,
[string]$OutputRoot = ""
)
$ErrorActionPreference = "Stop"
$Repo = Split-Path -Parent $PSScriptRoot
if ([string]::IsNullOrWhiteSpace($OutputRoot)) {
$OutputRoot = Join-Path $Repo "results\01_previous_two_batches"
}
$Code = Join-Path $Repo "code\rigorous_calibration.py"
$Refine = Join-Path $Repo "code\refine_pairs.py"
$Summary = Join-Path $Repo "code\summarize_results.py"
$R = Join-Path $Repo "results"
$B1Frames = Join-Path $Batch1Prepared "frames_all"
$B2Frames = Join-Path $Batch2Prepared "frames_all"
$B1Body = Join-Path $Batch1Prepared "body_poses_rear_gga_raw_rear_to_front.csv"
$B2Body = Join-Path $Batch2Prepared "body_poses_rear_gga_raw_rear_to_front.csv"
python $Code ground --frames $B2Frames --output "$R\common\ground_planes_batch2.csv"
function Run-Python {
param([string]$Stage, [string[]]$Arguments)
Write-Host "[$Stage]"
& python @Arguments
if ($LASTEXITCODE -ne 0) { throw "$Stage failed with Python exit code $LASTEXITCODE" }
}
python $Code pairs --backend small_gicp --frames $B2Frames --body $B2Body --output "$R\small_gicp\B_batch2_estimation.npz" --quality-json "$R\small_gicp\B_batch2_quality.json" --quality-csv "$R\small_gicp\B_batch2_quality.csv"
python $Code pairs --backend open3d --frames $B2Frames --body $B2Body --output "$R\open3d_gicp\B_batch2_estimation.npz" --quality-json "$R\open3d_gicp\B_batch2_quality.json" --quality-csv "$R\open3d_gicp\B_batch2_quality.csv" --max-gap 3 --multistart 1 --iterations 40
python $Refine --pairs "$R\small_gicp\B_batch2_estimation.npz" --quality-json "$R\small_gicp\B_batch2_quality.json" --output "$R\small_gicp\B_batch2_refined.npz"
python $Refine --pairs "$R\open3d_gicp\B_batch2_estimation.npz" --quality-json "$R\open3d_gicp\B_batch2_quality.json" --output "$R\open3d_gicp\B_batch2_refined.npz"
python $Code calibrate --pairs "$R\small_gicp\B_batch2_refined.npz" --ground-planes "$R\common\ground_planes_batch2.csv" --output "$R\small_gicp\extrinsic_batch2_refined.json"
python $Code calibrate --pairs "$R\open3d_gicp\B_batch2_refined.npz" --ground-planes "$R\common\ground_planes_batch2.csv" --output "$R\open3d_gicp\extrinsic_batch2_refined.json"
foreach ($Path in @($B1Frames, $B2Frames, $B1Body, $B2Body)) {
if (-not (Test-Path -LiteralPath $Path)) { throw "Input does not exist: $Path" }
}
$Common = Join-Path $OutputRoot "common"
$Small = Join-Path $OutputRoot "small_gicp"
$Open = Join-Path $OutputRoot "open3d_gicp"
New-Item -ItemType Directory -Force -Path $Common,$Small,$Open | Out-Null
python $Code pairs --backend small_gicp --frames $B1Frames --body $B1Body --output "$R\small_gicp\B_batch1_auxiliary.npz" --quality-json "$R\small_gicp\B_batch1_quality.json" --quality-csv "$R\small_gicp\B_batch1_quality.csv" --max-gap 3 --multistart 1 --iterations 40
python $Code pairs --backend open3d --frames $B1Frames --body $B1Body --output "$R\open3d_gicp\B_batch1_auxiliary.npz" --quality-json "$R\open3d_gicp\B_batch1_quality.json" --quality-csv "$R\open3d_gicp\B_batch1_quality.csv" --max-gap 3 --multistart 1 --iterations 40
python $Refine --pairs "$R\small_gicp\B_batch1_auxiliary.npz" --quality-json "$R\small_gicp\B_batch1_quality.json" --output "$R\small_gicp\B_batch1_auxiliary_refined.npz"
python $Refine --pairs "$R\open3d_gicp\B_batch1_auxiliary.npz" --quality-json "$R\open3d_gicp\B_batch1_quality.json" --output "$R\open3d_gicp\B_batch1_auxiliary_refined.npz"
python $Code validate --pairs "$R\small_gicp\B_batch1_auxiliary_refined.npz" --extrinsic "$R\small_gicp\extrinsic_batch2_refined.json" --output "$R\small_gicp\batch1_auxiliary_check.json"
python $Code validate --pairs "$R\open3d_gicp\B_batch1_auxiliary_refined.npz" --extrinsic "$R\open3d_gicp\extrinsic_batch2_refined.json" --output "$R\open3d_gicp\batch1_auxiliary_check.json"
Run-Python "batch2 ground planes" @($Code, "ground", "--frames", $B2Frames, "--output", (Join-Path $Common "ground_planes_batch2.csv"))
python $Summary --open3d "$R\open3d_gicp\extrinsic_batch2_refined.json" --small "$R\small_gicp\extrinsic_batch2_refined.json" --open3d-quality "$R\open3d_gicp\B_batch2_quality.json" --small-quality "$R\small_gicp\B_batch2_quality.json" --open3d-check "$R\open3d_gicp\batch1_auxiliary_check.json" --small-check "$R\small_gicp\batch1_auxiliary_check.json" --output "$R\comparison_summary.json" --recommended-output "$R\final_extrinsic_recommended.json"
foreach ($Backend in @("small_gicp", "open3d")) {
$Directory = if ($Backend -eq "small_gicp") { $Small } else { $Open }
$Extra = @()
if ($Backend -eq "open3d") { $Extra = @("--max-gap", "3", "--multistart", "1", "--iterations", "40") }
$B2Raw = Join-Path $Directory "B_batch2_estimation.npz"
$B2QualityJson = Join-Path $Directory "B_batch2_quality.json"
$B2QualityCsv = Join-Path $Directory "B_batch2_quality.csv"
Run-Python "$Backend batch2 pairs" (@($Code, "pairs", "--backend", $Backend, "--frames", $B2Frames, "--body", $B2Body,
"--output", $B2Raw, "--quality-json", $B2QualityJson, "--quality-csv", $B2QualityCsv) + $Extra)
$B2Refined = Join-Path $Directory "B_batch2_refined.npz"
Run-Python "$Backend batch2 refinement" @($Refine, "--pairs", $B2Raw, "--quality-json", $B2QualityJson, "--output", $B2Refined)
$Extrinsic = Join-Path $Directory "extrinsic_batch2_refined.json"
Run-Python "$Backend batch2 calibration" @($Code, "calibrate", "--pairs", $B2Refined,
"--ground-planes", (Join-Path $Common "ground_planes_batch2.csv"), "--output", $Extrinsic)
$B1Raw = Join-Path $Directory "B_batch1_auxiliary.npz"
$B1QualityJson = Join-Path $Directory "B_batch1_quality.json"
$B1QualityCsv = Join-Path $Directory "B_batch1_quality.csv"
Run-Python "$Backend batch1 auxiliary pairs" @($Code, "pairs", "--backend", $Backend, "--frames", $B1Frames, "--body", $B1Body,
"--output", $B1Raw, "--quality-json", $B1QualityJson, "--quality-csv", $B1QualityCsv,
"--max-gap", "3", "--multistart", "1", "--iterations", "40")
$B1Refined = Join-Path $Directory "B_batch1_auxiliary_refined.npz"
Run-Python "$Backend batch1 refinement" @($Refine, "--pairs", $B1Raw, "--quality-json", $B1QualityJson, "--output", $B1Refined)
Run-Python "$Backend batch1 auxiliary check" @($Code, "validate", "--pairs", $B1Refined,
"--extrinsic", $Extrinsic, "--output", (Join-Path $Directory "batch1_auxiliary_check.json"))
}
Run-Python "backend summary" @($Summary,
"--open3d", (Join-Path $Open "extrinsic_batch2_refined.json"),
"--small", (Join-Path $Small "extrinsic_batch2_refined.json"),
"--open3d-quality", (Join-Path $Open "B_batch2_quality.json"),
"--small-quality", (Join-Path $Small "B_batch2_quality.json"),
"--open3d-check", (Join-Path $Open "batch1_auxiliary_check.json"),
"--small-check", (Join-Path $Small "batch1_auxiliary_check.json"),
"--output", (Join-Path $OutputRoot "comparison_summary.json"),
"--recommended-output", (Join-Path $OutputRoot "final_extrinsic_recommended.json"))
Write-Host "Two-batch results: $OutputRoot"
+28 -6
View File
@@ -1,12 +1,34 @@
param([string]$ResultRoot = "")
$ErrorActionPreference = "Stop"
$Repo = Split-Path -Parent $PSScriptRoot
if ([string]::IsNullOrWhiteSpace($ResultRoot)) { $ResultRoot = Join-Path $Repo "results\01_previous_two_batches" }
$Code = Join-Path $Repo "code\rigorous_calibration.py"
$Filter = Join-Path $Repo "code\cross_backend_filter.py"
$Finalize = Join-Path $Repo "code\finalize_consensus.py"
$R = Join-Path $Repo "results"
python $Filter --open3d-pairs "$R\open3d_gicp\B_batch2_refined.npz" --small-pairs "$R\small_gicp\B_batch2_refined.npz" --output "$R\consensus\B_batch2_consensus.npz"
python $Filter --open3d-pairs "$R\open3d_gicp\B_batch1_auxiliary_refined.npz" --small-pairs "$R\small_gicp\B_batch1_auxiliary_refined.npz" --output "$R\consensus\B_batch1_consensus.npz" --min-pairs 15
python $Code calibrate --pairs "$R\consensus\B_batch2_consensus.npz" --ground-planes "$R\common\ground_planes_batch2.csv" --output "$R\consensus\extrinsic_batch2_consensus.json"
python $Code validate --pairs "$R\consensus\B_batch1_consensus.npz" --extrinsic "$R\consensus\extrinsic_batch2_consensus.json" --output "$R\consensus\batch1_auxiliary_check.json"
python $Finalize --consensus-extrinsic "$R\consensus\extrinsic_batch2_consensus.json" --consensus-check "$R\consensus\batch1_auxiliary_check.json" --open3d-extrinsic "$R\open3d_gicp\extrinsic_batch2_refined.json" --small-extrinsic "$R\small_gicp\extrinsic_batch2_refined.json" --output "$R\final_extrinsic_recommended.json" --summary "$R\final_summary.json"
function Run-Python {
param([string[]]$Arguments)
& python @Arguments
if ($LASTEXITCODE -ne 0) { throw "Python failed with exit code $LASTEXITCODE" }
}
Run-Python @($Filter, "--open3d-pairs", (Join-Path $ResultRoot "open3d_gicp\B_batch2_refined.npz"),
"--small-pairs", (Join-Path $ResultRoot "small_gicp\B_batch2_refined.npz"),
"--output", (Join-Path $ResultRoot "consensus\B_batch2_consensus.npz"))
Run-Python @($Filter, "--open3d-pairs", (Join-Path $ResultRoot "open3d_gicp\B_batch1_auxiliary_refined.npz"),
"--small-pairs", (Join-Path $ResultRoot "small_gicp\B_batch1_auxiliary_refined.npz"),
"--output", (Join-Path $ResultRoot "consensus\B_batch1_consensus.npz"), "--min-pairs", "15")
Run-Python @($Code, "calibrate", "--pairs", (Join-Path $ResultRoot "consensus\B_batch2_consensus.npz"),
"--ground-planes", (Join-Path $ResultRoot "common\ground_planes_batch2.csv"),
"--output", (Join-Path $ResultRoot "consensus\extrinsic_batch2_consensus.json"))
Run-Python @($Code, "validate", "--pairs", (Join-Path $ResultRoot "consensus\B_batch1_consensus.npz"),
"--extrinsic", (Join-Path $ResultRoot "consensus\extrinsic_batch2_consensus.json"),
"--output", (Join-Path $ResultRoot "consensus\batch1_auxiliary_check.json"))
Run-Python @($Finalize,
"--consensus-extrinsic", (Join-Path $ResultRoot "consensus\extrinsic_batch2_consensus.json"),
"--consensus-check", (Join-Path $ResultRoot "consensus\batch1_auxiliary_check.json"),
"--open3d-extrinsic", (Join-Path $ResultRoot "open3d_gicp\extrinsic_batch2_refined.json"),
"--small-extrinsic", (Join-Path $ResultRoot "small_gicp\extrinsic_batch2_refined.json"),
"--output", (Join-Path $ResultRoot "final_extrinsic_recommended.json"),
"--summary", (Join-Path $ResultRoot "final_summary.json"))
+20 -13
View File
@@ -1,15 +1,22 @@
param([int]$PairIndex = 0)
param(
[int]$PairIndex = 0,
[string]$ResultRoot = ""
)
$ErrorActionPreference = "Stop"
$Repo = Split-Path -Parent $PSScriptRoot
$Code = "$Repo\code\scan_extrinsic_sensitivity.py"
$X = "$Repo\results\final_extrinsic_recommended.json"
$Ground = "$Repo\results\common\ground_planes_batch2.csv"
$Out = "$Repo\results\diagnostics"
if ([string]::IsNullOrWhiteSpace($ResultRoot)) { $ResultRoot = Join-Path $Repo "results\01_previous_two_batches" }
$Code = Join-Path $Repo "code\scan_extrinsic_sensitivity.py"
$X = Join-Path $ResultRoot "final_extrinsic_recommended.json"
$Ground = Join-Path $ResultRoot "common\ground_planes_batch2.csv"
$Out = Join-Path $ResultRoot "diagnostics"
python $Code --pairs "$Repo\results\open3d_gicp\B_batch2_refined.npz" --extrinsic $X --ground-planes $Ground --pair-index $PairIndex --output "$Out\open3d_refined_pair${PairIndex}_left_rpy_scan.json"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
python $Code --pairs "$Repo\results\small_gicp\B_batch2_refined.npz" --extrinsic $X --ground-planes $Ground --pair-index $PairIndex --output "$Out\small_gicp_refined_pair${PairIndex}_left_rpy_scan.json"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
python $Code --pairs "$Repo\results\consensus\B_batch2_consensus.npz" --extrinsic $X --ground-planes $Ground --pair-index $PairIndex --output "$Out\consensus_pair${PairIndex}_left_rpy_scan.json"
exit $LASTEXITCODE
foreach ($Backend in @("open3d_gicp", "small_gicp")) {
$Label = if ($Backend -eq "open3d_gicp") { "open3d_refined" } else { "small_gicp_refined" }
& python $Code --pairs (Join-Path $ResultRoot "$Backend\B_batch2_refined.npz") --extrinsic $X `
--ground-planes $Ground --pair-index $PairIndex --output (Join-Path $Out "${Label}_pair${PairIndex}_left_rpy_scan.json")
if ($LASTEXITCODE -ne 0) { throw "$Backend sensitivity scan failed" }
}
& python $Code --pairs (Join-Path $ResultRoot "consensus\B_batch2_consensus.npz") --extrinsic $X `
--ground-planes $Ground --pair-index $PairIndex --output (Join-Path $Out "consensus_pair${PairIndex}_left_rpy_scan.json")
if ($LASTEXITCODE -ne 0) { throw "Consensus sensitivity scan failed" }
+70
View File
@@ -0,0 +1,70 @@
param(
[Parameter(Mandatory = $true)][string]$Prepared,
[Parameter(Mandatory = $true)][string]$OutputRoot,
[string]$PoseName = "rear_gga_raw_rear_to_front",
[double]$BodyHeight = 0.2335,
[int]$MinPairs = 20,
[int]$Bootstrap = 100
)
$ErrorActionPreference = "Stop"
$Repo = Split-Path -Parent $PSScriptRoot
$Code = Join-Path $Repo "code\rigorous_calibration.py"
$Refine = Join-Path $Repo "code\refine_pairs.py"
$Consensus = Join-Path $Repo "code\cross_backend_filter.py"
$Frames = Join-Path $Prepared "frames_all"
$Body = Join-Path $Prepared "body_poses_$PoseName.csv"
$Common = Join-Path $OutputRoot "common"
$Open = Join-Path $OutputRoot "open3d_gicp"
$Small = Join-Path $OutputRoot "small_gicp"
$ConsensusOut = Join-Path $OutputRoot "consensus"
function Run-Python {
param([string]$Stage, [string[]]$Arguments)
Write-Host "[$Stage]"
& python @Arguments
if ($LASTEXITCODE -ne 0) { throw "$Stage failed with Python exit code $LASTEXITCODE" }
}
foreach ($Path in @($Frames, $Body)) {
if (-not (Test-Path -LiteralPath $Path)) { throw "Input does not exist: $Path" }
}
New-Item -ItemType Directory -Force -Path $Common,$Open,$Small,$ConsensusOut | Out-Null
$Ground = Join-Path $Common "ground_planes.csv"
Run-Python "ground planes" @($Code, "ground", "--frames", $Frames, "--output", $Ground)
foreach ($Backend in @("small_gicp", "open3d")) {
$Directory = if ($Backend -eq "small_gicp") { $Small } else { $Open }
$Raw = Join-Path $Directory "B_estimation.npz"
$QualityJson = Join-Path $Directory "B_quality.json"
$QualityCsv = Join-Path $Directory "B_quality.csv"
$PairArgs = @($Code, "pairs", "--backend", $Backend, "--frames", $Frames, "--body", $Body,
"--output", $Raw, "--quality-json", $QualityJson, "--quality-csv", $QualityCsv,
"--min-pairs", "$MinPairs")
if ($Backend -eq "open3d") { $PairArgs += @("--max-gap", "3", "--multistart", "1", "--iterations", "40") }
Run-Python "$Backend pairs" $PairArgs
Run-Python "$Backend X-independent refinement" @(
$Refine, "--pairs", $Raw, "--quality-json", $QualityJson,
"--output", (Join-Path $Directory "B_refined.npz"), "--min-pairs", "$MinPairs"
)
Run-Python "$Backend calibration" @(
$Code, "calibrate", "--pairs", (Join-Path $Directory "B_refined.npz"),
"--ground-planes", $Ground, "--body-height", "$BodyHeight",
"--bootstrap", "$Bootstrap", "--output", (Join-Path $Directory "extrinsic.json")
)
}
$ConsensusPairs = Join-Path $ConsensusOut "B_consensus.npz"
Run-Python "cross-backend consensus" @(
$Consensus, "--open3d-pairs", (Join-Path $Open "B_refined.npz"),
"--small-pairs", (Join-Path $Small "B_refined.npz"),
"--output", $ConsensusPairs, "--min-pairs", "$MinPairs"
)
Run-Python "consensus calibration" @(
$Code, "calibrate", "--pairs", $ConsensusPairs, "--ground-planes", $Ground,
"--body-height", "$BodyHeight", "--bootstrap", "$Bootstrap",
"--output", (Join-Path $ConsensusOut "extrinsic.json")
)
Write-Host "Calibration results: $OutputRoot"
-14
View File
@@ -1,14 +0,0 @@
param(
[int]$PairIndex = 0,
[string]$Frames = "C:\Users\admin\Documents\Codex\2026-07-15\wo\outputs\calibration_data1_20260720\prepared\frames_all",
[double]$LeftRollDeg = 0.0,
[double]$LeftPitchDeg = 0.0,
[double]$LeftYawDeg = 0.0
)
$Repo = Split-Path -Parent $PSScriptRoot
python "$Repo\code\visualize_pair_3d.py" `
--frames $Frames `
--pairs "$Repo\results\open3d_gicp\B_batch2_refined.npz" `
--extrinsic "$Repo\results\final_extrinsic_recommended.json" `
--pair-index $PairIndex `
--left-rpy-deg $LeftRollDeg $LeftPitchDeg $LeftYawDeg
+19
View File
@@ -0,0 +1,19 @@
param(
[Parameter(Mandatory = $true)][string]$Frames,
[Parameter(Mandatory = $true)][string]$Pairs,
[Parameter(Mandatory = $true)][string]$Extrinsic,
[int]$PairIndex = 0,
[double]$LeftRollDeg = 0.0,
[double]$LeftPitchDeg = 0.0,
[double]$LeftYawDeg = 0.0
)
$ErrorActionPreference = "Stop"
$Repo = Split-Path -Parent $PSScriptRoot
foreach ($Path in @($Frames, $Pairs, $Extrinsic)) {
if (-not (Test-Path -LiteralPath $Path)) { throw "Input does not exist: $Path" }
}
& python (Join-Path $Repo "code\visualize_pair_3d.py") `
--frames $Frames --pairs $Pairs --extrinsic $Extrinsic --pair-index $PairIndex `
--left-rpy-deg $LeftRollDeg $LeftPitchDeg $LeftYawDeg
if ($LASTEXITCODE -ne 0) { throw "Visualization failed with Python exit code $LASTEXITCODE" }
-18
View File
@@ -1,18 +0,0 @@
param(
[int]$PairIndex = 0,
[string]$Frames = "C:\Users\admin\Documents\Codex\2026-07-15\wo\outputs\calibration_data1_20260720\prepared\frames_all",
[string]$Extrinsic = "",
[double]$LeftRollDeg = 0.0,
[double]$LeftPitchDeg = 0.0,
[double]$LeftYawDeg = 0.0
)
$Repo = Split-Path -Parent $PSScriptRoot
if ([string]::IsNullOrWhiteSpace($Extrinsic)) {
$Extrinsic = "$Repo\results\small_gicp\extrinsic_batch2_refined.json"
}
python "$Repo\code\visualize_pair_3d.py" `
--frames $Frames `
--pairs "$Repo\results\small_gicp\B_batch2_refined.npz" `
--extrinsic $Extrinsic `
--pair-index $PairIndex `
--left-rpy-deg $LeftRollDeg $LeftPitchDeg $LeftYawDeg
+26
View File
@@ -0,0 +1,26 @@
# tools
本目录负责从原始 dlog/rscap 导出 NPZ、关联 RTK/IMU 并生成 prepared;完整流程和参数统一见仓库根目录 `README.md`
| 文件 | 作用 |
|---|---|
| `frontlidar_dlog_export.py` | 从单个 Medulla dlog 导出原始 LiDAR NPZ,可匹配站内 RTK。 |
| `prepare_station_dataset.py` | 旧式逐站导出生成 prepared。 |
| `build_multisensor_npz.py` | 以 LiDAR 帧为索引关联独立 RTK/IMU。 |
| `prepare_multisensor_station_dataset.py` | combined NPZ 生成 prepared。 |
| `rscap_v2/capture_format_v2.py` | 读取 rscap v2。 |
| `rscap_v2/audit_capture_v2.py` | 审计 capture 完整性。 |
| `rscap_v2/parse_rtk_imu_v2.py` | 解析 RTK/IMU 为 JSONL。 |
| `rscap_v2/pipeline_common*.py` | 时间和协议解析共用逻辑。 |
统一输出接口:
```text
prepared/
frames_all/station_*.npz
body_poses_rear_gga_raw_rear_to_front.csv
station_summary.csv
manifest.json
```
后续配准必须使用 `points_raw`,不能使用已经由某套外参变换后的点云。
+312
View File
@@ -0,0 +1,312 @@
#!/usr/bin/env python3
"""Build one LiDAR-centric NPZ per frame with matched RTK and an IMU window.
Inputs are LiDAR frame NPZ files from frontlidar_dlog_export.py and parsed
RTK/IMU JSONL files from parse_rtk_imu_v2.py. Raw .rscap files remain the
traceability source; this script never modifies them.
"""
from __future__ import annotations
import argparse
import csv
import json
from pathlib import Path
from typing import Any
import numpy as np
GPS_EPOCH_UNIX_NS = 315964800 * 1_000_000_000
def parse_named_path(text: str) -> tuple[str, Path]:
if "=" not in text:
raise argparse.ArgumentTypeError("expected NAME=PATH")
name, raw_path = text.split("=", 1)
if not name.strip():
raise argparse.ArgumentTypeError("segment name is empty")
return name.strip(), Path(raw_path)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument(
"--lidar",
type=parse_named_path,
action="append",
required=True,
metavar="NAME=FRAMES_DIR",
help="Repeat for each LiDAR segment; directory contains exported *.npz frames.",
)
parser.add_argument("--rtk", type=Path, action="append", required=True, help="Parsed rtk.jsonl; repeat per session.")
parser.add_argument("--imu", type=Path, action="append", required=True, help="Parsed imu.jsonl; repeat per session.")
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--rtk-max-dt-ms", type=float, default=150.0)
parser.add_argument("--imu-before-ms", type=float, default=100.0)
parser.add_argument("--imu-after-ms", type=float, default=100.0)
parser.add_argument("--gps-utc-leap-seconds", type=int, default=18)
parser.add_argument("--overwrite", action="store_true")
return parser.parse_args()
def load_jsonl(paths: list[Path]) -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for source_index, path in enumerate(paths):
source_file = str(path.resolve())
with path.open("r", encoding="utf-8") as stream:
for line_number, line in enumerate(stream, start=1):
if not line.strip():
continue
row = json.loads(line)
row["_source_file"] = source_file
row["_source_index"] = source_index
row["_source_line"] = line_number
rows.append(row)
return rows
def utf8_array(value: Any) -> np.ndarray:
return np.frombuffer(str(value if value is not None else "").encode("utf-8"), dtype=np.uint8)
def scalar(array: np.ndarray) -> Any:
return array.reshape(-1)[0].item()
def nearest_index(times: np.ndarray, target: int) -> int:
if not len(times):
return -1
right = int(np.searchsorted(times, target, side="left"))
candidates = [index for index in (right - 1, right) if 0 <= index < len(times)]
return min(candidates, key=lambda index: abs(int(times[index]) - target))
def estimate_imu_times(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Recover 100 Hz timing inside each serial chunk from device timestamps.
A capture chunk has one host receive timestamp but may contain several IMU
frames. The last frame is anchored to the chunk receive time and earlier
frames are moved backwards by their device timestamp difference.
"""
groups: dict[tuple[int, int], list[dict[str, Any]]] = {}
for row in rows:
if not row.get("crc_valid") or row.get("device_timestamp_ms") is None:
continue
key = (int(row["_source_index"]), int(row.get("source_chunk_sequence_last", -1)))
groups.setdefault(key, []).append(row)
result: list[dict[str, Any]] = []
for group in groups.values():
group.sort(key=lambda row: (int(row["device_timestamp_ms"]), int(row["_source_line"])))
last_device = int(group[-1]["device_timestamp_ms"])
host_ns = int(group[-1]["host_receive_utc_ns"])
for row in group:
delta_ms = (last_device - int(row["device_timestamp_ms"])) & 0xFFFFFFFF
if delta_ms > 60_000:
delta_ms = 0
copied = dict(row)
copied["estimated_time_ns"] = host_ns - delta_ms * 1_000_000
result.append(copied)
result.sort(key=lambda row: int(row["estimated_time_ns"]))
return result
def gnss_utc_ns(row: dict[str, Any], leap_seconds: int) -> int | None:
week, tow_ms = row.get("gnss_week"), row.get("gnss_tow_ms")
if week is None or tow_ms is None:
return None
seconds = int(week) * 604800 + float(tow_ms) / 1000.0 - leap_seconds
return GPS_EPOCH_UNIX_NS + int(round(seconds * 1_000_000_000))
def numeric_array(rows: list[dict[str, Any]], key: str, dtype: Any, default: Any) -> np.ndarray:
return np.asarray([row.get(key, default) if row.get(key) is not None else default for row in rows], dtype=dtype)
def raw_frame_matrix(rows: list[dict[str, Any]]) -> tuple[np.ndarray, np.ndarray]:
frames = [bytes.fromhex(str(row.get("raw_frame_hex", ""))) for row in rows]
lengths = np.asarray([len(frame) for frame in frames], dtype=np.int32)
width = max(lengths, default=0)
matrix = np.zeros((len(frames), width), dtype=np.uint8)
for index, frame in enumerate(frames):
matrix[index, : len(frame)] = np.frombuffer(frame, dtype=np.uint8)
return matrix, lengths
def add_rtk(values: dict[str, np.ndarray], prefix: str, row: dict[str, Any] | None, dt_ns: int | None) -> None:
values[f"{prefix}_valid"] = np.asarray([row is not None], dtype=np.uint8)
values[f"{prefix}_dt_ns"] = np.asarray([dt_ns or 0], dtype=np.int64)
values[f"{prefix}_host_receive_utc_ns"] = np.asarray([0], dtype=np.int64)
values[f"{prefix}_raw_utf8"] = utf8_array("")
values[f"{prefix}_source_file_utf8"] = utf8_array("")
values[f"{prefix}_source_raw_file_offset"] = np.asarray([-1], dtype=np.int64)
values[f"{prefix}_source_raw_byte_length"] = np.asarray([0], dtype=np.int32)
if row is None:
return
values[f"{prefix}_host_receive_utc_ns"] = np.asarray([row.get("host_receive_utc_ns", 0)], dtype=np.int64)
values[f"{prefix}_raw_utf8"] = utf8_array(row.get("raw_line", ""))
values[f"{prefix}_source_file_utf8"] = utf8_array(row.get("_source_file", ""))
values[f"{prefix}_source_raw_file_offset"] = np.asarray([row.get("source_raw_file_offset", -1)], dtype=np.int64)
values[f"{prefix}_source_raw_byte_length"] = np.asarray([row.get("source_raw_byte_length", 0)], dtype=np.int32)
def initialize_rtk_measurements(values: dict[str, np.ndarray]) -> None:
for key, dtype, default in (
("lat_deg", np.float64, np.nan), ("lon_deg", np.float64, np.nan),
("altitude_m", np.float64, np.nan), ("hdop", np.float64, np.nan),
("fix_quality", np.int32, -1), ("gga_satellites", np.int32, -1),
("differential_age_s", np.float64, np.nan),
("gnss_week", np.int32, -1), ("gnss_tow_ms", np.int64, -1),
("baseline_length_m", np.float64, np.nan), ("raw_heading_deg", np.float64, np.nan),
("pitch_deg", np.float64, np.nan), ("heading_stddev_deg", np.float64, np.nan),
("pitch_stddev_deg", np.float64, np.nan), ("heading_satellites", np.int32, -1),
("solution_satellites", np.int32, -1),
):
values[f"rtk_{key}"] = np.asarray([default], dtype=dtype)
values["rtk_fixed"] = np.asarray([0], dtype=np.uint8)
values["rtk_heading_solution_utf8"] = utf8_array("")
values["rtk_heading_gnss_utc_ns"] = np.asarray([0], dtype=np.int64)
values["rtk_heading_host_minus_gnss_ns"] = np.asarray([0], dtype=np.int64)
def main() -> int:
args = parse_args()
if args.out.exists() and any(args.out.iterdir()) and not args.overwrite:
raise FileExistsError(f"{args.out} is non-empty; pass --overwrite")
frames_out = args.out / "frames"
frames_out.mkdir(parents=True, exist_ok=True)
rtk_rows = load_jsonl(args.rtk)
gga = sorted(
[row for row in rtk_rows if row.get("type") == "GGA" and row.get("checksum_valid") and row.get("lat_deg") is not None],
key=lambda row: int(row["host_receive_utc_ns"]),
)
heading = sorted(
[row for row in rtk_rows if row.get("type") == "UNIHEADINGA" and row.get("checksum_valid") and row.get("heading_valid")],
key=lambda row: int(row["host_receive_utc_ns"]),
)
imu = estimate_imu_times(load_jsonl(args.imu))
gga_times = np.asarray([int(row["host_receive_utc_ns"]) for row in gga], dtype=np.int64)
heading_times = np.asarray([int(row["host_receive_utc_ns"]) for row in heading], dtype=np.int64)
imu_times = np.asarray([int(row["estimated_time_ns"]) for row in imu], dtype=np.int64)
manifest: list[dict[str, Any]] = []
global_index = 0
max_rtk_ns = int(args.rtk_max_dt_ms * 1_000_000)
before_ns = int(args.imu_before_ms * 1_000_000)
after_ns = int(args.imu_after_ms * 1_000_000)
for segment_name, frame_dir in args.lidar:
frame_paths = sorted(frame_dir.glob("*.npz"))
if not frame_paths:
raise FileNotFoundError(f"no NPZ frames under {frame_dir}")
for segment_index, source in enumerate(frame_paths):
with np.load(source, allow_pickle=False) as frame:
values = {key: np.asarray(frame[key]) for key in frame.files}
lidar_time_ns = int(scalar(values["unix_time_ns"]))
gga_index = nearest_index(gga_times, lidar_time_ns)
heading_index = nearest_index(heading_times, lidar_time_ns)
gga_row = gga[gga_index] if gga_index >= 0 else None
heading_row = heading[heading_index] if heading_index >= 0 else None
gga_dt = int(gga_times[gga_index]) - lidar_time_ns if gga_index >= 0 else None
heading_dt = int(heading_times[heading_index]) - lidar_time_ns if heading_index >= 0 else None
gga_ok = gga_row is not None and abs(gga_dt or 0) <= max_rtk_ns
heading_ok = heading_row is not None and abs(heading_dt or 0) <= max_rtk_ns
add_rtk(values, "rtk_gga", gga_row if gga_ok else None, gga_dt)
add_rtk(values, "rtk_heading", heading_row if heading_ok else None, heading_dt)
initialize_rtk_measurements(values)
if gga_ok and gga_row:
for key, dtype, default in (
("lat_deg", np.float64, np.nan), ("lon_deg", np.float64, np.nan),
("altitude_m", np.float64, np.nan), ("hdop", np.float64, np.nan),
("fix_quality", np.int32, -1), ("gga_satellites", np.int32, -1),
("differential_age_s", np.float64, np.nan),
):
values[f"rtk_{key}"] = np.asarray([gga_row.get(key, default)], dtype=dtype)
values["rtk_gga_satellites"] = np.asarray([gga_row.get("satellites", -1)], dtype=np.int32)
values["rtk_fixed"] = np.asarray([int(gga_row.get("fix_quality", -1)) in {4, 5}], dtype=np.uint8)
if heading_ok and heading_row:
for key, dtype, default in (
("gnss_week", np.int32, -1), ("gnss_tow_ms", np.int64, -1),
("baseline_length_m", np.float64, np.nan), ("raw_heading_deg", np.float64, np.nan),
("pitch_deg", np.float64, np.nan), ("heading_stddev_deg", np.float64, np.nan),
("pitch_stddev_deg", np.float64, np.nan),
("solution_satellites", np.int32, -1),
):
values[f"rtk_{key}"] = np.asarray([heading_row.get(key, default)], dtype=dtype)
values["rtk_heading_satellites"] = np.asarray([heading_row.get("satellites", -1)], dtype=np.int32)
values["rtk_heading_solution_utf8"] = utf8_array(heading_row.get("heading_solution", ""))
device_ns = gnss_utc_ns(heading_row, args.gps_utc_leap_seconds)
values["rtk_heading_gnss_utc_ns"] = np.asarray([device_ns or 0], dtype=np.int64)
values["rtk_heading_host_minus_gnss_ns"] = np.asarray(
[int(heading_row["host_receive_utc_ns"]) - device_ns if device_ns is not None else 0], dtype=np.int64
)
left = int(np.searchsorted(imu_times, lidar_time_ns - before_ns, side="left"))
right = int(np.searchsorted(imu_times, lidar_time_ns + after_ns, side="right"))
window = imu[left:right]
values["imu_window_count"] = np.asarray([len(window)], dtype=np.int32)
values["imu_valid"] = np.asarray([bool(window)], dtype=np.uint8)
values["imu_time_ns"] = numeric_array(window, "estimated_time_ns", np.int64, 0)
values["imu_host_receive_utc_ns"] = numeric_array(window, "host_receive_utc_ns", np.int64, 0)
for key in ("device_timestamp_ms", "pps_sync_stamp_ms", "tag"):
values[f"imu_{key}"] = numeric_array(window, key, np.int64, -1)
for key in (
"temperature_c", "air_pressure_pa", "accel_x_mps2", "accel_y_mps2", "accel_z_mps2",
"gyro_x_radps", "gyro_y_radps", "gyro_z_radps", "mag_x_ut", "mag_y_ut", "mag_z_ut",
"roll_deg", "pitch_deg", "yaw_deg", "quaternion_w", "quaternion_x", "quaternion_y", "quaternion_z",
):
values[f"imu_{key}"] = numeric_array(window, key, np.float64, np.nan)
values["imu_source_index"] = numeric_array(window, "_source_index", np.int32, -1)
values["imu_source_raw_file_offset"] = numeric_array(window, "source_raw_file_offset", np.int64, -1)
raw_matrix, raw_lengths = raw_frame_matrix(window)
values["imu_raw_frame_bytes"] = raw_matrix
values["imu_raw_frame_length"] = raw_lengths
values["imu_source_files_json_utf8"] = utf8_array(json.dumps([str(path.resolve()) for path in args.imu], ensure_ascii=False))
values["source_lidar_file_utf8"] = utf8_array(source.resolve())
values["segment_name_utf8"] = utf8_array(segment_name)
output = frames_out / f"{segment_name}_{segment_index:06d}.npz"
np.savez_compressed(output, **values)
manifest.append({
"global_index": global_index,
"segment": segment_name,
"segment_index": segment_index,
"output": str(output.relative_to(args.out)),
"source_lidar": str(source.resolve()),
"lidar_time_ns": lidar_time_ns,
"rtk_gga_dt_ns": gga_dt,
"rtk_heading_dt_ns": heading_dt,
"rtk_valid": gga_ok,
"heading_valid": heading_ok,
"rtk_fix_quality": gga_row.get("fix_quality") if gga_ok and gga_row else None,
"rtk_fixed": bool(gga_ok and gga_row and int(gga_row.get("fix_quality", -1)) in {4, 5}),
"imu_window_count": len(window),
})
global_index += 1
fields = sorted({key for row in manifest for key in row})
with (args.out / "manifest.csv").open("w", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=fields)
writer.writeheader()
writer.writerows(manifest)
summary = {
"frames": len(manifest),
"segments": {name: sum(row["segment"] == name for row in manifest) for name, _ in args.lidar},
"rtk_valid": sum(bool(row["rtk_valid"]) for row in manifest),
"heading_valid": sum(bool(row["heading_valid"]) for row in manifest),
"rtk_fixed": sum(bool(row["rtk_fixed"]) for row in manifest),
"imu_window_nonempty": sum(int(row["imu_window_count"]) > 0 for row in manifest),
"rtk_max_dt_ms": args.rtk_max_dt_ms,
"imu_window_ms": [-args.imu_before_ms, args.imu_after_ms],
"time_basis": "LiDAR and serial host UTC; RTK GNSS time and IMU device time are retained for clock-model refinement",
"imu_orientation_warning": "IMU values are in the raw IMU sensor frame; no LiDAR/body extrinsic is applied",
}
(args.out / "dataset_summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(summary, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""Prepare one static LiDAR frame and one yaw-only RTK body pose per NPZ segment."""
from __future__ import annotations
import argparse
import csv
import json
import math
import re
import shutil
from pathlib import Path
from typing import Any
import numpy as np
POSE_FIELDS = ["time", "x", "y", "z", "qx", "qy", "qz", "qw"]
def natural_key(value: str) -> list[Any]:
return [int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", value)]
def truth(value: Any) -> bool:
return str(value).strip().lower() in {"1", "true", "yes", "y"}
def circular_mean_deg(values: np.ndarray) -> float:
radians = np.deg2rad(values)
return float(np.rad2deg(math.atan2(np.mean(np.sin(radians)), np.mean(np.cos(radians)))) % 360.0)
def circular_std_deg(values: np.ndarray) -> float:
radians = np.deg2rad(values)
resultant = max(math.hypot(np.mean(np.cos(radians)), np.mean(np.sin(radians))), 1e-12)
return float(np.rad2deg(math.sqrt(-2.0 * math.log(resultant))))
def geodetic_to_ecef(lat_deg: float, lon_deg: float, height_m: float) -> np.ndarray:
a, e2 = 6378137.0, 6.69437999014e-3
lat, lon = math.radians(lat_deg), math.radians(lon_deg)
sin_lat, cos_lat, sin_lon, cos_lon = math.sin(lat), math.cos(lat), math.sin(lon), math.cos(lon)
n = a / math.sqrt(1.0 - e2 * sin_lat * sin_lat)
return np.array([(n + height_m) * cos_lat * cos_lon, (n + height_m) * cos_lat * sin_lon,
(n * (1.0 - e2) + height_m) * sin_lat], dtype=float)
def ecef_to_enu(ecef: np.ndarray, origin: np.ndarray, lat_deg: float, lon_deg: float) -> np.ndarray:
lat, lon = math.radians(lat_deg), math.radians(lon_deg)
slat, clat, slon, clon = math.sin(lat), math.cos(lat), math.sin(lon), math.cos(lon)
rotation = np.array([[-slon, clon, 0.0], [-slat * clon, -slat * slon, clat],
[clat * clon, clat * slon, slat]], dtype=float)
return rotation @ (ecef - origin)
def yaw_rotation(yaw: float) -> np.ndarray:
c, s = math.cos(yaw), math.sin(yaw)
return np.array([[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]])
def scalar(data: np.lib.npyio.NpzFile, name: str) -> float:
return float(np.asarray(data[name]).reshape(-1)[0])
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--combined-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--pose-name", default="rear_gga_raw_rear_to_front")
parser.add_argument("--heading-offset-deg", type=float, required=True)
parser.add_argument("--antenna-lever", type=float, nargs=3, required=True, metavar=("X", "Y", "Z"))
parser.add_argument("--accepted-fixes", type=int, nargs="+", default=[4, 5])
parser.add_argument("--heading-std-limit-deg", type=float, default=0.5)
parser.add_argument("--min-stations", type=int, default=30)
parser.add_argument("--expected-stations", type=int, default=0)
parser.add_argument("--overwrite", action="store_true")
return parser.parse_args()
def main() -> int:
args = parse_args()
manifest_path = args.combined_root / "manifest.csv"
with manifest_path.open("r", encoding="utf-8-sig", newline="") as stream:
rows = list(csv.DictReader(stream))
required = {"segment", "output", "lidar_time_ns", "rtk_valid", "heading_valid", "rtk_fix_quality"}
if not rows or not required.issubset(rows[0]):
raise ValueError(f"{manifest_path} is empty or lacks {sorted(required)}")
groups: dict[str, list[dict[str, str]]] = {}
for row in rows:
groups.setdefault(row["segment"], []).append(row)
selected, summaries, rejected = [], [], []
accepted_fixes = set(args.accepted_fixes)
for segment in sorted(groups, key=natural_key):
group = sorted(groups[segment], key=lambda row: int(row["lidar_time_ns"]))
good = [row for row in group if truth(row["rtk_valid"]) and truth(row["heading_valid"])
and int(row["rtk_fix_quality"]) in accepted_fixes]
if not good:
rejected.append({"station": segment, "reason": "no associated fixed RTK position and valid heading"})
continue
samples = []
for row in good:
path = args.combined_root / Path(row["output"])
with np.load(path, allow_pickle=False) as data:
samples.append((scalar(data, "rtk_lat_deg"), scalar(data, "rtk_lon_deg"),
scalar(data, "rtk_altitude_m"), scalar(data, "rtk_raw_heading_deg"),
scalar(data, "rtk_pitch_deg"), scalar(data, "rtk_heading_stddev_deg")))
values = np.asarray(samples, dtype=float)
heading_std = circular_std_deg(values[:, 3])
if heading_std > args.heading_std_limit_deg:
rejected.append({"station": segment, "reason": f"heading std {heading_std:.4f} deg exceeds limit"})
continue
frame = good[len(good) // 2]
source = args.combined_root / Path(frame["output"])
selected.append({"station": segment, "source": source, "time": int(frame["lidar_time_ns"]) / 1e9,
"lat": float(np.mean(values[:, 0])), "lon": float(np.mean(values[:, 1])),
"alt": float(np.mean(values[:, 2])), "heading": circular_mean_deg(values[:, 3])})
summaries.append({"station": segment, "frames": len(group), "valid_fixed_frames": len(good),
"heading_mean_deg": circular_mean_deg(values[:, 3]),
"heading_circular_std_deg": heading_std, "rtk_pitch_mean_deg": float(np.mean(values[:, 4])),
"reported_heading_std_mean_deg": float(np.nanmean(values[:, 5])),
"altitude_std_m": float(np.std(values[:, 2])), "selected_source": str(source)})
if args.expected_stations and len(selected) != args.expected_stations:
raise RuntimeError(f"expected {args.expected_stations} usable stations, got {len(selected)}; rejected={rejected}")
if len(selected) < args.min_stations:
raise RuntimeError(f"need at least {args.min_stations} usable stations, got {len(selected)}; rejected={rejected}")
if args.output.exists() and any(args.output.iterdir()) and not args.overwrite:
raise FileExistsError(f"{args.output} is non-empty; pass --overwrite")
frames = args.output / "frames_all"
frames.mkdir(parents=True, exist_ok=True)
origin = selected[0]
origin_ecef = geodetic_to_ecef(origin["lat"], origin["lon"], origin["alt"])
lever = np.asarray(args.antenna_lever, dtype=float)
pose_rows = []
for index, item in enumerate(selected, 1):
destination = frames / f"station_{index:02d}.npz"
shutil.copy2(item["source"], destination)
antenna = ecef_to_enu(geodetic_to_ecef(item["lat"], item["lon"], item["alt"]), origin_ecef,
origin["lat"], origin["lon"])
corrected_heading = (item["heading"] + args.heading_offset_deg) % 360.0
yaw = math.radians(90.0 - corrected_heading)
body = antenna - yaw_rotation(yaw) @ lever
pose_rows.append(dict(zip(POSE_FIELDS, [item["time"], *body, 0.0, 0.0,
math.sin(yaw / 2.0), math.cos(yaw / 2.0)])))
summaries[index - 1].update({"sequence": index, "prepared_frame": destination.name,
"corrected_heading_deg": corrected_heading})
pose_path = args.output / f"body_poses_{args.pose_name}.csv"
with pose_path.open("w", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=POSE_FIELDS); writer.writeheader(); writer.writerows(pose_rows)
with (args.output / "station_summary.csv").open("w", encoding="utf-8", newline="") as stream:
fields = sorted({key for row in summaries for key in row})
writer = csv.DictWriter(stream, fieldnames=fields); writer.writeheader(); writer.writerows(summaries)
document = {"source_combined_root": str(args.combined_root.resolve()), "station_count": len(selected),
"rejected": rejected, "pose_csv": pose_path.name,
"selection_policy": "middle LiDAR frame among fixed-position and valid-heading associations",
"body_pose_configuration": {"raw_heading_offset_deg": args.heading_offset_deg,
"antenna_lever_body_m": args.antenna_lever,
"orientation_model": "yaw-only, identical to the previous calibration workflow"},
"stations": [{"sequence": i + 1, "source_station": item["station"],
"source_frame": str(item["source"]), "prepared_frame": f"station_{i + 1:02d}.npz"}
for i, item in enumerate(selected)]}
(args.output / "manifest.json").write_text(json.dumps(document, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps({"prepared": str(args.output.resolve()), "stations": len(selected),
"rejected": rejected, "pose_csv": pose_path.name}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+22
View File
@@ -0,0 +1,22 @@
from __future__ import annotations
import argparse
from pathlib import Path
from capture_format_v2 import file_summary, read_capture
from pipeline_common import write_json
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("captures", nargs="+", type=Path)
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
summaries = [file_summary(read_capture(path)) for path in args.captures]
write_json(args.out, {"captures": summaries})
for summary in summaries:
print(summary)
if __name__ == "__main__":
main()
+254
View File
@@ -0,0 +1,254 @@
from __future__ import annotations
import binascii
import io
import struct
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import BinaryIO, Iterator
FILE_MAGIC = "RAW_SERIAL_CAPTURE_FILE_V2"
RECORD_MAGIC = "RAW_SERIAL_RECORD_V2"
FOOTER_MAGIC = "RAW_SERIAL_CAPTURE_FOOTER_V2"
def read_7bit_int(stream: BinaryIO) -> int:
value = 0
shift = 0
while True:
raw = stream.read(1)
if not raw:
raise EOFError("truncated .NET string length")
value |= (raw[0] & 0x7F) << shift
if not raw[0] & 0x80:
return value
shift += 7
if shift > 35:
raise ValueError("invalid .NET string length")
def read_dotnet_string(stream: BinaryIO) -> str:
length = read_7bit_int(stream)
raw = stream.read(length)
if len(raw) != length:
raise EOFError("truncated .NET string")
return raw.decode("utf-8")
def read_i32(stream: BinaryIO) -> int:
raw = stream.read(4)
if len(raw) != 4:
raise EOFError("truncated int32")
return struct.unpack("<i", raw)[0]
def read_i64(stream: BinaryIO) -> int:
raw = stream.read(8)
if len(raw) != 8:
raise EOFError("truncated int64")
return struct.unpack("<q", raw)[0]
def read_u32(stream: BinaryIO) -> int:
raw = stream.read(4)
if len(raw) != 4:
raise EOFError("truncated uint32")
return struct.unpack("<I", raw)[0]
@dataclass(frozen=True)
class CaptureHeader:
sensor_kind: str
session_id: str
session_start_utc_ticks: int
session_start_monotonic_ticks: int
monotonic_frequency: int
port: str
baud: int
file_start_utc_ticks: int
@dataclass(frozen=True)
class RawChunk:
sequence: int
receive_utc_ticks: int
receive_monotonic_ticks: int
raw: bytes
record_file_offset: int
raw_file_offset: int
record_crc32: int
crc_valid: bool
@dataclass(frozen=True)
class CaptureFooter:
clean_close: bool
records: int
bytes: int
first_sequence: int
last_sequence: int
dropped_chunks: int
dropped_bytes: int
crc_valid: bool
@dataclass
class CaptureFile:
path: str
header: CaptureHeader
chunks: list[RawChunk]
footer: CaptureFooter | None
truncated_tail: bool = False
def read_header(stream: BinaryIO) -> CaptureHeader:
if read_dotnet_string(stream) != FILE_MAGIC:
raise ValueError("not a V2 raw capture file")
version = read_i32(stream)
if version != 2:
raise ValueError(f"unsupported capture version: {version}")
return CaptureHeader(
sensor_kind=read_dotnet_string(stream),
session_id=read_dotnet_string(stream),
session_start_utc_ticks=read_i64(stream),
session_start_monotonic_ticks=read_i64(stream),
monotonic_frequency=read_i64(stream),
port=read_dotnet_string(stream),
baud=read_i32(stream),
file_start_utc_ticks=read_i64(stream),
)
def parse_record_body(body: bytes, record_file_offset: int, record_crc: int) -> RawChunk:
stream = io.BytesIO(body)
if read_dotnet_string(stream) != RECORD_MAGIC:
raise ValueError("invalid record magic")
sequence = read_i64(stream)
receive_utc_ticks = read_i64(stream)
receive_monotonic_ticks = read_i64(stream)
raw_length = read_i32(stream)
if raw_length < 0 or raw_length > 64 * 1024 * 1024:
raise ValueError(f"invalid raw length: {raw_length}")
raw_offset = record_file_offset + 4 + stream.tell()
raw = stream.read(raw_length)
if len(raw) != raw_length:
raise EOFError("truncated raw bytes")
crc_valid = (binascii.crc32(body) & 0xFFFFFFFF) == record_crc
return RawChunk(
sequence=sequence,
receive_utc_ticks=receive_utc_ticks,
receive_monotonic_ticks=receive_monotonic_ticks,
raw=raw,
record_file_offset=record_file_offset,
raw_file_offset=raw_offset,
record_crc32=record_crc,
crc_valid=crc_valid,
)
def parse_footer(body: bytes, expected_crc: int) -> CaptureFooter:
stream = io.BytesIO(body)
if read_dotnet_string(stream) != FOOTER_MAGIC:
raise ValueError("invalid footer magic")
clean_close = stream.read(1) == b"\x01"
records = read_i64(stream)
raw_bytes = read_i64(stream)
first_sequence = read_i64(stream)
last_sequence = read_i64(stream)
dropped_chunks = read_i64(stream)
dropped_bytes = read_i64(stream)
return CaptureFooter(
clean_close=clean_close,
records=records,
bytes=raw_bytes,
first_sequence=first_sequence,
last_sequence=last_sequence,
dropped_chunks=dropped_chunks,
dropped_bytes=dropped_bytes,
crc_valid=(binascii.crc32(body) & 0xFFFFFFFF) == expected_crc,
)
def read_capture(path: Path) -> CaptureFile:
chunks: list[RawChunk] = []
footer = None
truncated = False
with path.open("rb") as stream:
header = read_header(stream)
while True:
record_offset = stream.tell()
length_raw = stream.read(4)
if not length_raw:
break
if len(length_raw) != 4:
truncated = True
break
length = struct.unpack("<i", length_raw)[0]
try:
if length == -1:
footer_length = read_i32(stream)
if footer_length < 0 or footer_length > 1024 * 1024:
raise ValueError("invalid footer length")
footer_body = stream.read(footer_length)
if len(footer_body) != footer_length:
raise EOFError("truncated footer")
footer = parse_footer(footer_body, read_u32(stream))
break
if length <= 0 or length > 64 * 1024 * 1024:
raise ValueError("invalid record length")
body = stream.read(length)
if len(body) != length:
raise EOFError("truncated record body")
record_crc = read_u32(stream)
chunks.append(parse_record_body(body, record_offset, record_crc))
except (EOFError, ValueError):
truncated = True
break
return CaptureFile(str(path), header, chunks, footer, truncated)
def sequence_gaps(chunks: list[RawChunk]) -> list[tuple[int, int, int]]:
result = []
for previous, current in zip(chunks, chunks[1:]):
if current.sequence > previous.sequence + 1:
result.append((previous.sequence, current.sequence, current.sequence - previous.sequence - 1))
return result
def file_summary(capture: CaptureFile) -> dict:
gaps = sequence_gaps(capture.chunks)
sequences = [chunk.sequence for chunk in capture.chunks]
return {
"path": capture.path,
"sensor": capture.header.sensor_kind,
"session_id": capture.header.session_id,
"port": capture.header.port,
"baud": capture.header.baud,
"chunks_read": len(capture.chunks),
"bytes_read": sum(len(chunk.raw) for chunk in capture.chunks),
"first_sequence": sequences[0] if sequences else None,
"last_sequence": sequences[-1] if sequences else None,
"missing_chunks": sum(gap[2] for gap in gaps),
"gap_count": len(gaps),
"bad_record_crc": sum(not chunk.crc_valid for chunk in capture.chunks),
"truncated_tail": capture.truncated_tail,
"footer": None if capture.footer is None else asdict(capture.footer),
"gaps": gaps[:100],
}
def iter_contiguous_segments(chunks: list[RawChunk]) -> Iterator[tuple[int, list[RawChunk]]]:
if not chunks:
return
segment_id = 0
current = [chunks[0]]
for previous, chunk in zip(chunks, chunks[1:]):
if chunk.sequence != previous.sequence + 1:
yield segment_id, current
segment_id += 1
current = [chunk]
else:
current.append(chunk)
yield segment_id, current
+35
View File
@@ -0,0 +1,35 @@
from __future__ import annotations
import argparse
from pathlib import Path
from capture_format_v2 import file_summary, read_capture
from pipeline_common_corrected import parse_imu_capture, parse_rtk_capture, write_json, write_jsonl
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--rtk", type=Path, required=True)
parser.add_argument("--imu", type=Path, required=True)
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
args.out.mkdir(parents=True, exist_ok=True)
rtk_capture = read_capture(args.rtk)
imu_capture = read_capture(args.imu)
rtk_rows = parse_rtk_capture(rtk_capture)
imu_rows = parse_imu_capture(imu_capture)
write_jsonl(args.out / "rtk.jsonl", rtk_rows)
write_jsonl(args.out / "imu.jsonl", imu_rows)
write_json(args.out / "parse_summary.json", {
"rtk_capture": file_summary(rtk_capture),
"imu_capture": file_summary(imu_capture),
"rtk_records": len(rtk_rows),
"rtk_checksum_valid": sum(bool(row.get("checksum_valid")) for row in rtk_rows),
"imu_frames": len(imu_rows),
"imu_crc_valid": sum(bool(row.get("crc_valid")) for row in imu_rows),
})
print(f"RTK records={len(rtk_rows)}, IMU frames={len(imu_rows)}")
if __name__ == "__main__":
main()
+300
View File
@@ -0,0 +1,300 @@
from __future__ import annotations
import binascii
import json
import math
import struct
from pathlib import Path
from typing import Iterable
from capture_format_v2 import CaptureFile, RawChunk, iter_contiguous_segments, read_capture
DOTNET_UNIX_EPOCH_TICKS = 621355968000000000
def ticks_to_unix_ns(ticks: int) -> int:
return (ticks - DOTNET_UNIX_EPOCH_TICKS) * 100
def safe_float(value: str, default=None):
try:
return float(value)
except (TypeError, ValueError):
return default
def safe_int(value: str, default=None):
try:
return int(value)
except (TypeError, ValueError):
return default
def nmea_checksum_valid(line: str) -> bool:
star = line.rfind("*")
if star < 0:
return False
try:
expected = int(line[star + 1:star + 3], 16)
except ValueError:
return False
value = 0
for char in line[1:star]:
value ^= ord(char)
return value == expected
def unicore_crc32(text: str) -> int:
crc = 0
for value in text.encode("ascii", "replace"):
crc ^= value
for _ in range(8):
crc = (crc >> 1) ^ (0xEDB88320 if crc & 1 else 0)
return crc & 0xFFFFFFFF
def unicore_checksum_valid(line: str) -> bool:
star = line.rfind("*")
if star < 0 or len(line) < star + 9:
return False
try:
expected = int(line[star + 1:star + 9], 16)
except ValueError:
return False
return unicore_crc32(line[1:star]) == expected
def parse_checksum(line: str) -> bool:
if line.startswith("$"):
return nmea_checksum_valid(line)
if line.startswith("#"):
return unicore_checksum_valid(line)
return False
def parse_nmea_latlon(value: str, hemisphere: str):
raw = safe_float(value)
if raw is None:
return None
degrees = math.floor(raw / 100.0)
result = degrees + (raw - degrees * 100.0) / 60.0
if hemisphere.upper() in ("S", "W"):
result = -result
return result
def parse_gga(line: str) -> dict:
fields = line[:line.rfind("*")].split(",")
if len(fields) < 10:
raise ValueError("GGA has too few fields")
return {
"type": "GGA",
"position_time_utc": fields[1],
"lat_deg": parse_nmea_latlon(fields[2], fields[3]),
"lon_deg": parse_nmea_latlon(fields[4], fields[5]),
"fix_quality": safe_int(fields[6], -1),
"satellites": safe_int(fields[7], -1),
"hdop": safe_float(fields[8]),
"altitude_m": safe_float(fields[9]),
"geoid_separation_m": safe_float(fields[11]) if len(fields) > 11 else None,
"differential_age_s": safe_float(fields[13]) if len(fields) > 13 else None,
"station_id": fields[14].strip('"') if len(fields) > 14 else "",
}
def parse_heading(line: str) -> dict:
before_crc = line[:line.rfind("*")]
header, payload = before_crc.split(";", 1)
header_fields = header.split(",")
fields = payload.split(",")
if len(fields) < 7:
raise ValueError("UNIHEADINGA has too few fields")
raw_heading = safe_float(fields[3])
return {
"type": "UNIHEADINGA",
"gnss_week": safe_int(header_fields[4]) if len(header_fields) > 4 else None,
"gnss_tow_ms": safe_int(header_fields[5]) if len(header_fields) > 5 else None,
"heading_status": fields[0],
"heading_solution": fields[1],
"baseline_length_m": safe_float(fields[2]),
"raw_heading_deg": raw_heading,
"pitch_deg": safe_float(fields[4]),
"heading_stddev_deg": safe_float(fields[6]),
"pitch_stddev_deg": safe_float(fields[7]) if len(fields) > 7 else None,
"station_id": fields[8].strip('"') if len(fields) > 8 else "",
"satellites": safe_int(fields[9], -1) if len(fields) > 9 else -1,
"solution_satellites": safe_int(fields[10], -1) if len(fields) > 10 else -1,
"observations": safe_int(fields[11], -1) if len(fields) > 11 else -1,
"multi_count": safe_int(fields[12], -1) if len(fields) > 12 else -1,
"heading_valid": fields[0] == "SOL_COMPUTED" and fields[1] in {"NARROW_INT", "NARROW_FLOAT"},
}
def chunk_source(chunks: list[RawChunk], offset: int, end: int) -> dict:
first = chunks[0]
last = chunks[-1]
cursor = 0
start_chunk = first
end_chunk = last
for chunk in chunks:
chunk_start = cursor
chunk_end = cursor + len(chunk.raw)
if chunk_start <= offset < chunk_end:
start_chunk = chunk
if chunk_start < end <= chunk_end:
end_chunk = chunk
break
cursor = chunk_end
return {
"source_segment_id": None,
"source_chunk_sequence_first": start_chunk.sequence,
"source_chunk_sequence_last": end_chunk.sequence,
"source_raw_file_offset": start_chunk.raw_file_offset + max(0, offset - sum(len(c.raw) for c in chunks if c.sequence < start_chunk.sequence)),
"source_raw_byte_length": max(0, end - offset),
}
def parse_rtk_capture(capture: CaptureFile) -> list[dict]:
rows = []
for segment_id, chunks in iter_contiguous_segments(capture.chunks):
stream = b"".join(chunk.raw for chunk in chunks)
cursor = 0
while cursor < len(stream):
newline = stream.find(b"\n", cursor)
if newline < 0:
break
end = newline + 1
raw_line = stream[cursor:end].rstrip(b"\r\n")
cursor = end
if not raw_line:
continue
line = raw_line.decode("ascii", "replace")
valid = parse_checksum(line)
row = {
"type": "UNKNOWN",
"raw_line": line,
"checksum_valid": valid,
"host_receive_utc_ns": ticks_to_unix_ns(chunks[-1].receive_utc_ticks),
"host_receive_monotonic_ticks": chunks[-1].receive_monotonic_ticks,
"source_segment_id": segment_id,
"source_byte_offset_in_segment": cursor - len(raw_line) - 1,
"source_byte_length": len(raw_line) + 1,
}
try:
if line.startswith("$GNGGA") or line.startswith("$GPGGA"):
row.update(parse_gga(line))
elif line.startswith("#UNIHEADINGA"):
row.update(parse_heading(line))
except ValueError as ex:
row["parse_error"] = str(ex)
rows.append(row)
return rows
def crc16_hi13(data: bytes) -> int:
crc = 0
for value in data:
crc ^= value << 8
for _ in range(8):
crc = ((crc << 1) ^ 0x1021) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF
return crc
def decode_hi91(frame: bytes) -> dict:
f32 = lambda i: struct.unpack_from("<f", frame, i)[0]
return {
"tag": 0x91,
"pps_sync_stamp_ms": int.from_bytes(frame[7:9], "little"),
"temperature_c": struct.unpack_from("<b", frame, 9)[0],
"air_pressure_pa": f32(10),
"device_timestamp_ms": int.from_bytes(frame[14:18], "little"),
"accel_x_mps2": f32(18) * 9.80665,
"accel_y_mps2": f32(22) * 9.80665,
"accel_z_mps2": f32(26) * 9.80665,
"gyro_x_radps": f32(30) * math.pi / 180.0,
"gyro_y_radps": f32(34) * math.pi / 180.0,
"gyro_z_radps": f32(38) * math.pi / 180.0,
"mag_x_ut": f32(42), "mag_y_ut": f32(46), "mag_z_ut": f32(50),
"roll_deg": f32(54), "pitch_deg": f32(58), "yaw_deg": f32(62),
"quaternion_w": f32(66), "quaternion_x": f32(70),
"quaternion_y": f32(74), "quaternion_z": f32(78),
}
def decode_hi92(frame: bytes) -> dict:
i16 = lambda i: struct.unpack_from("<h", frame, i)[0]
i32 = lambda i: struct.unpack_from("<i", frame, i)[0]
return {
"tag": 0x92,
"status": int.from_bytes(frame[7:9], "little"),
"temperature_c": struct.unpack_from("<b", frame, 9)[0],
"pps_sync_stamp_ms": int.from_bytes(frame[10:12], "little"),
"air_pressure_pa": i16(12) + 100000.0,
"heave_m": i16(14) * 0.001,
"gyro_x_radps": i16(16) * 0.001, "gyro_y_radps": i16(18) * 0.001, "gyro_z_radps": i16(20) * 0.001,
"accel_x_mps2": i16(22) * 0.0048828, "accel_y_mps2": i16(24) * 0.0048828, "accel_z_mps2": i16(26) * 0.0048828,
"mag_x_ut": i16(28) * 0.030517, "mag_y_ut": i16(30) * 0.030517, "mag_z_ut": i16(32) * 0.030517,
"roll_deg": i32(34) * 0.001, "pitch_deg": i32(38) * 0.001, "yaw_deg": i32(42) * 0.001,
"quaternion_w": i16(46) * 0.0001, "quaternion_x": i16(48) * 0.0001,
"quaternion_y": i16(50) * 0.0001, "quaternion_z": i16(52) * 0.0001,
}
def parse_imu_capture(capture: CaptureFile) -> list[dict]:
rows = []
for segment_id, chunks in iter_contiguous_segments(capture.chunks):
stream = b"".join(chunk.raw for chunk in chunks)
cursor = 0
while True:
start = stream.find(b"\x5a\xa5", cursor)
if start < 0 or start + 6 > len(stream):
break
payload_length = int.from_bytes(stream[start + 2:start + 4], "little")
frame_length = 6 + payload_length
if payload_length <= 0 or payload_length > 512:
cursor = start + 1
continue
if start + frame_length > len(stream):
break
frame = stream[start:start + frame_length]
expected = int.from_bytes(frame[4:6], "little")
actual = crc16_hi13(frame[:4] + frame[6:])
end = start + frame_length
source = chunk_source(chunks, start, end)
source["source_segment_id"] = segment_id
row = {
"type": "HI13",
"tag": frame[6],
"frame_length": frame_length,
"crc_valid": expected == actual,
"host_receive_utc_ns": ticks_to_unix_ns(chunks[-1].receive_utc_ticks),
"host_receive_monotonic_ticks": chunks[-1].receive_monotonic_ticks,
"source_segment_id": segment_id,
"source_byte_offset_in_segment": start,
"source_byte_length": frame_length,
"raw_frame_hex": frame.hex(),
}
if expected == actual:
try:
row.update(decode_hi91(frame) if frame[6] == 0x91 else decode_hi92(frame) if frame[6] == 0x92 else {})
except (IndexError, struct.error, ValueError) as ex:
row["parse_error"] = str(ex)
rows.append(row)
cursor = end
return rows
def write_jsonl(path: Path, rows: Iterable[dict]) -> None:
with path.open("w", encoding="utf-8", newline="\n") as stream:
for row in rows:
stream.write(json.dumps(row, ensure_ascii=False, separators=(",", ":")) + "\n")
def write_json(path: Path, value: dict) -> None:
path.write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8")
def load_jsonl(path: Path) -> list[dict]:
with path.open("r", encoding="utf-8") as stream:
return [json.loads(line) for line in stream if line.strip()]
+106
View File
@@ -0,0 +1,106 @@
from __future__ import annotations
import bisect
from pipeline_common import *
from capture_format_v2 import CaptureFile, RawChunk, iter_contiguous_segments
_SPAN_CACHE: dict[int, tuple[list[RawChunk], list[int]]] = {}
def _chunk_starts(chunks: list[RawChunk]) -> list[int]:
key = id(chunks)
cached = _SPAN_CACHE.get(key)
if cached is not None and cached[0] is chunks:
return cached[1]
starts = []
cursor = 0
for chunk in chunks:
starts.append(cursor)
cursor += len(chunk.raw)
_SPAN_CACHE[key] = (chunks, starts)
return starts
def source_for_span(chunks: list[RawChunk], start: int, end: int, segment_id: int) -> dict:
starts = _chunk_starts(chunks)
start_index = max(0, min(len(chunks) - 1, bisect.bisect_right(starts, start) - 1))
end_index = max(start_index, min(len(chunks) - 1, bisect.bisect_left(starts, end) - 1))
start_chunk = chunks[start_index]
end_chunk = chunks[end_index]
return {
"source_segment_id": segment_id,
"source_chunk_sequence_first": start_chunk.sequence,
"source_chunk_sequence_last": end_chunk.sequence,
"source_raw_file_offset": start_chunk.raw_file_offset + (start - starts[start_index]),
"source_raw_byte_length": end - start,
"host_receive_utc_ns": ticks_to_unix_ns(end_chunk.receive_utc_ticks),
"host_receive_monotonic_ticks": end_chunk.receive_monotonic_ticks,
}
def parse_rtk_capture(capture: CaptureFile) -> list[dict]:
rows = []
for segment_id, chunks in iter_contiguous_segments(capture.chunks):
stream = b"".join(chunk.raw for chunk in chunks)
cursor = 0
while cursor < len(stream):
newline = stream.find(b"\n", cursor)
if newline < 0:
break
end = newline + 1
raw_line = stream[cursor:end].rstrip(b"\r\n")
start = cursor
cursor = end
if not raw_line:
continue
line = raw_line.decode("ascii", "replace")
row = {"type": "UNKNOWN", "raw_line": line, "checksum_valid": parse_checksum(line)}
row.update(source_for_span(chunks, start, end, segment_id))
try:
if line.startswith("$GNGGA") or line.startswith("$GPGGA"):
row.update(parse_gga(line))
elif line.startswith("#UNIHEADINGA"):
row.update(parse_heading(line))
except ValueError as ex:
row["parse_error"] = str(ex)
rows.append(row)
return rows
def parse_imu_capture(capture: CaptureFile) -> list[dict]:
rows = []
for segment_id, chunks in iter_contiguous_segments(capture.chunks):
stream = b"".join(chunk.raw for chunk in chunks)
cursor = 0
while True:
start = stream.find(b"\x5a\xa5", cursor)
if start < 0 or start + 6 > len(stream):
break
payload_length = int.from_bytes(stream[start + 2:start + 4], "little")
frame_length = 6 + payload_length
if payload_length <= 0 or payload_length > 512:
cursor = start + 1
continue
if start + frame_length > len(stream):
break
frame = stream[start:start + frame_length]
expected = int.from_bytes(frame[4:6], "little")
actual = crc16_hi13(frame[:4] + frame[6:])
end = start + frame_length
row = {
"type": "HI13",
"tag": frame[6],
"frame_length": frame_length,
"crc_valid": expected == actual,
"raw_frame_hex": frame.hex(),
}
row.update(source_for_span(chunks, start, end, segment_id))
if row["crc_valid"]:
try:
row.update(decode_hi91(frame) if frame[6] == 0x91 else decode_hi92(frame) if frame[6] == 0x92 else {})
except (IndexError, struct.error, ValueError) as ex:
row["parse_error"] = str(ex)
rows.append(row)
cursor = end
return rows