Commit 730211c2 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: fopen_pose_compare.py — remove egomotion.csv support, --xml only

*-egomotion.csv is a human-readable output file for Calc analysis, not
a machine input source.  *-INTERFRAME.corr-xml is the internal state
always present alongside processed data and is the correct programmatic
source for x/y/z/a/t/r poses.

- remove parse_egomotion_csv() and --ego argument
- --xml is now required
- remove IMS/PIMU col_specs (not in XML)
- rename CSV output columns: ego_* → xml_*, ers_*_rad → xml_*_rad
Co-authored-by: 's avatarClaude <claude@elphel.com>
parent 81f67ed0
......@@ -4,9 +4,8 @@ fopen_pose_compare.py — Compare COLMAP camera poses with imagej-elphel ERS/GPS
Reads:
- COLMAP images.txt (IMAGE_ID QW QX QY QZ TX TY TZ CAMERA_ID NAME)
- imagej-elphel *-INTERFRAME.corr-xml (--xml, always present)
OR *-egomotion.csv (--ego, optional output file)
--ego takes precedence if both are given (it has IMS/PIMU columns too).
- imagej-elphel *-INTERFRAME.corr-xml (machine-readable internal state,
always present alongside the processed data)
Computes a 7-DOF Sim(3) alignment between the two POSITION sets (Umeyama 1991):
COLMAP_center ≈ s * R_align * ego_pos + t_align
......@@ -25,7 +24,6 @@ Usage:
python3 scripts/fopen_pose_compare.py \\
--images /path/to/sparse/0_txt/images.txt \\
--xml /path/to/*-INTERFRAME.corr-xml \\
[--ego /path/to/*-egomotion.csv] \\
[--out pose_compare.csv] \\
[--plot] \\
[--skip 3 17 ...] # pose row indices to skip (0-based)
......@@ -188,61 +186,6 @@ def parse_images_txt(path):
return entries
def parse_egomotion_csv(path, skip_rows=None):
"""
Parse imagej-elphel egomotion.csv.
Valid rows: tab-separated, ≥10 columns, col[0] is a numeric scene index.
Columns of interest (0-indexed):
0 scene_index
1 timestamp
2 x(m) ERS-corrected position
3 y(m)
4 z(m)
5 a(rad) azimuth
6 tilt(rad)
7 roll(rad)
98 imsX
99 imsY
100 imsZ
110 pimuX-C IMU corrected
111 pimuY-C
112 pimuZ-C
Returns list of dicts in file order.
skip_rows: set of 0-based valid-row indices to exclude.
"""
skip_set = set(skip_rows or [])
rows = []
valid_idx = 0
with open(path) as f:
for line in f:
line = line.rstrip('\n')
parts = line.split('\t')
if len(parts) < 10:
continue
try:
scene_idx = int(parts[0])
except ValueError:
continue
if valid_idx in skip_set:
valid_idx += 1
continue
def fg(i):
try: return float(parts[i])
except: return float('nan')
rows.append({
'valid_idx': valid_idx,
'scene_idx': scene_idx,
'timestamp': fg(1),
'x': fg(2), 'y': fg(3), 'z': fg(4),
'az': fg(5), 'tilt': fg(6), 'roll': fg(7),
'imsX': fg(98), 'imsY': fg(99), 'imsZ': fg(100),
'pimuX_C': fg(110), 'pimuY_C': fg(111), 'pimuZ_C': fg(112),
})
valid_idx += 1
return rows
def parse_interframe_xml(path, skip_rows=None):
"""
......@@ -250,12 +193,10 @@ def parse_interframe_xml(path, skip_rows=None):
Returns list of dicts sorted by timestamp — same order as COLMAP frame_NNNN
(elphel_to_colmap.py assigns frame numbers by sorting timestamps).
Fields match parse_egomotion_csv() for compatibility:
scene_idx — 0-based sequential index
timestamp — float seconds
x/y/z — position (m) relative to reference scene
az/tilt/roll — orientation (rad)
imsX/Y/Z, pimuX_C/Y_C/Z_C — NaN (not in XML)
"""
skip_set = set(skip_rows or [])
PREFIX = "EYESIS_DCT_AUX."
......@@ -282,7 +223,6 @@ def parse_interframe_xml(path, skip_rows=None):
ts_float = float(ts_str.replace("_", ".", 1))
raw[ts_float] = vals
nan = float('nan')
rows = []
for i, (ts, vals) in enumerate(sorted(raw.items())):
if i in skip_set:
......@@ -293,8 +233,6 @@ def parse_interframe_xml(path, skip_rows=None):
'timestamp': ts,
'x': x, 'y': y, 'z': z,
'az': az, 'tilt': tilt, 'roll': roll,
'imsX': nan, 'imsY': nan, 'imsZ': nan,
'pimuX_C': nan, 'pimuY_C': nan, 'pimuZ_C': nan,
})
return rows
......@@ -498,28 +436,19 @@ def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--images', required=True, help='COLMAP sparse/0_txt/images.txt')
ap.add_argument('--xml', default=None, help='imagej-elphel *-INTERFRAME.corr-xml (preferred)')
ap.add_argument('--ego', default=None, help='imagej-elphel *-egomotion.csv (optional; overrides --xml if given)')
ap.add_argument('--xml', required=True, help='imagej-elphel *-INTERFRAME.corr-xml')
ap.add_argument('--out', default=None, help='Output CSV (optional)')
ap.add_argument('--plot', action='store_true', help='3-D trajectory plot')
ap.add_argument('--skip', type=int, nargs='*', default=[],
metavar='ROW', help='0-based pose rows to skip')
args = ap.parse_args()
if not args.xml and not args.ego:
ap.error("provide at least one of --xml or --ego")
# ── Load ───────────────────────────────────────────────────────────────
print(f"Loading COLMAP images: {args.images}")
colmap_entries = parse_images_txt(args.images)
print(f" {len(colmap_entries)} frames "
f"(frame_{colmap_entries[0]['frame']:04d} … frame_{colmap_entries[-1]['frame']:04d})")
if args.ego:
print(f"Loading egomotion: {args.ego}")
ego_rows = parse_egomotion_csv(args.ego, skip_rows=args.skip)
print(f" {len(ego_rows)} rows (scene {ego_rows[0]['scene_idx']} … {ego_rows[-1]['scene_idx']})")
else:
print(f"Loading INTERFRAME XML: {args.xml}")
ego_rows = parse_interframe_xml(args.xml, skip_rows=args.skip)
print(f" {len(ego_rows)} rows (ts {ego_rows[0]['timestamp']:.3f} … {ego_rows[-1]['timestamp']:.3f})")
......@@ -527,11 +456,9 @@ def main():
pairs = match_sequences(colmap_entries, ego_rows)
print(f" {len(pairs)} matched pairs")
# ── Position alignment for each egomotion column set ───────────────────
# ── Position alignment ─────────────────────────────────────────────────
col_specs = [
('ERS x/y/z', 'x', 'y', 'z' ),
('IMS imsX/Y/Z', 'imsX', 'imsY', 'imsZ' ),
('PIMU pimuX-C/Y-C/Z-C', 'pimuX_C', 'pimuY_C', 'pimuZ_C'),
('x/y/z', 'x', 'y', 'z'),
]
pos_results = {} # label → (s, R_align, t, res)
......@@ -540,8 +467,6 @@ def main():
for label, kx, ky, kz in col_specs:
s, R_align, t, res = compute_pos_alignment(pairs, kx, ky, kz)
if s is None:
if not args.ego:
continue # IMS/PIMU are always NaN when using XML — no need to warn
print(f"\n[SKIP] {label}: insufficient valid data")
continue
print_pos_alignment_report(label, s, R_align, t, res)
......@@ -558,11 +483,8 @@ def main():
base_fields = [
'frame', 'scene_idx', 'timestamp',
'colmap_X', 'colmap_Y', 'colmap_Z',
'ego_x', 'ego_y', 'ego_z',
'ego_imsX', 'ego_imsY', 'ego_imsZ',
'ego_pimuX_C', 'ego_pimuY_C', 'ego_pimuZ_C',
# Raw ERS orientation
'ers_az_rad', 'ers_tilt_rad', 'ers_roll_rad',
'xml_x', 'xml_y', 'xml_z',
'xml_az_rad', 'xml_tilt_rad', 'xml_roll_rad',
]
extra_fields = []
for label, _, _, _ in col_specs:
......@@ -589,14 +511,10 @@ def main():
'colmap_X': ce['center'][0],
'colmap_Y': ce['center'][1],
'colmap_Z': ce['center'][2],
'ego_x': er['x'], 'ego_y': er['y'], 'ego_z': er['z'],
'ego_imsX': er['imsX'],'ego_imsY': er['imsY'],'ego_imsZ': er['imsZ'],
'ego_pimuX_C': er['pimuX_C'],
'ego_pimuY_C': er['pimuY_C'],
'ego_pimuZ_C': er['pimuZ_C'],
'ers_az_rad': er['az'],
'ers_tilt_rad': er['tilt'],
'ers_roll_rad': er['roll'],
'xml_x': er['x'], 'xml_y': er['y'], 'xml_z': er['z'],
'xml_az_rad': er['az'],
'xml_tilt_rad': er['tilt'],
'xml_roll_rad': er['roll'],
}
for label, kx, ky, kz in col_specs:
if label not in pos_results:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment