|
6 | 6 |
|
7 | 7 | from __future__ import annotations |
8 | 8 |
|
| 9 | +import os |
9 | 10 | from pathlib import Path |
10 | 11 | from typing import Sequence |
11 | 12 |
|
|
15 | 16 | from openptv_python.parameters import OrientPar |
16 | 17 | from openptv_python.calibration import Calibration as PythonCalibration |
17 | 18 |
|
18 | | -from ._backend import Calibration, external_calibration, full_calibration, TargetArray |
| 19 | +from ._backend import ( |
| 20 | + Calibration, |
| 21 | + external_calibration, |
| 22 | + full_calibration, |
| 23 | + match_detection_to_ref, |
| 24 | + TargetArray, |
| 25 | +) |
19 | 26 |
|
20 | 27 | from .parameter_manager import ParameterManager |
21 | 28 | from . import ptv |
@@ -195,74 +202,105 @@ def run_standalone_calibration( |
195 | 202 | ) -> list[Calibration]: |
196 | 203 | """Run calibration for all cameras and (optionally) write .ori/.addpar.""" |
197 | 204 |
|
198 | | - pm = load_parameter_manager(yaml_path) |
199 | | - params = pm.parameters |
| 205 | + experiment_dir = yaml_path.parent |
| 206 | + previous_cwd = Path.cwd() |
200 | 207 |
|
201 | | - ptv_params = params.get("ptv") |
202 | | - cal_ori = params.get("cal_ori") |
203 | | - if not isinstance(ptv_params, dict) or not isinstance(cal_ori, dict): |
204 | | - raise KeyError("YAML must contain 'ptv' and 'cal_ori' mappings") |
| 208 | + try: |
| 209 | + os.chdir(experiment_dir) |
205 | 210 |
|
206 | | - num_cams = int(pm.num_cams or params.get("num_cams") or xy.shape[0]) |
207 | | - if num_cams != xy.shape[0]: |
208 | | - raise ValueError(f"num_cams ({num_cams}) != xy.shape[0] ({xy.shape[0]})") |
| 211 | + pm = load_parameter_manager(yaml_path) |
| 212 | + params = pm.parameters |
209 | 213 |
|
210 | | - # Build ControlParams (cpar) from YAML |
211 | | - cpar, *_rest = ptv.py_start_proc_c(pm) |
| 214 | + ptv_params = params.get("ptv") |
| 215 | + cal_ori = params.get("cal_ori") |
| 216 | + if not isinstance(ptv_params, dict) or not isinstance(cal_ori, dict): |
| 217 | + raise KeyError("YAML must contain 'ptv' and 'cal_ori' mappings") |
212 | 218 |
|
213 | | - ori_files = cal_ori.get("img_ori") |
214 | | - if not ori_files or len(ori_files) < num_cams: |
215 | | - raise ValueError("cal_ori.img_ori must list one .ori path per camera") |
| 219 | + num_cams = int(pm.num_cams or params.get("num_cams") or xy.shape[0]) |
| 220 | + if num_cams != xy.shape[0]: |
| 221 | + raise ValueError(f"num_cams ({num_cams}) != xy.shape[0] ({xy.shape[0]})") |
216 | 222 |
|
217 | | - calibrations: list[Calibration] = [] |
| 223 | + # Build ControlParams (cpar) from YAML |
| 224 | + cpar, *_rest = ptv.py_start_proc_c(pm) |
218 | 225 |
|
219 | | - for cam in range(num_cams): |
220 | | - ori_path = (yaml_path.parent / ori_files[cam]).resolve() if not Path(ori_files[cam]).is_absolute() else Path(ori_files[cam]) |
221 | | - cal = _load_or_init_calibration(ori_path) |
| 226 | + ori_files = cal_ori.get("img_ori") |
| 227 | + if not ori_files or len(ori_files) < num_cams: |
| 228 | + raise ValueError("cal_ori.img_ori must list one .ori path per camera") |
222 | 229 |
|
223 | | - targs = targets_from_xy(xy[cam], pnr) |
| 230 | + calibrations: list[Calibration] = [] |
224 | 231 |
|
225 | | - if init_external is not None: |
226 | | - if init_external == "first4": |
227 | | - idx4 = _select_manual_orientation_indices(pm, cam, len(pnr)) |
228 | | - else: |
229 | | - idx4 = _select_four_indices(init_external, len(pnr)) |
230 | | - sel_xyz = np.asarray(xyz[pnr[idx4]], dtype=float) |
231 | | - sel_xy = np.asarray(xy[cam][idx4], dtype=float) |
232 | | - |
233 | | - ok = external_calibration(cal, sel_xyz, sel_xy, cpar) |
234 | | - if ok is False: |
235 | | - print( |
236 | | - f"external_calibration failed for camera {cam}; " |
237 | | - "continuing with the loaded calibration" |
| 232 | + for cam in range(num_cams): |
| 233 | + ori_path = ( |
| 234 | + (experiment_dir / ori_files[cam]).resolve() |
| 235 | + if not Path(ori_files[cam]).is_absolute() |
| 236 | + else Path(ori_files[cam]) |
| 237 | + ) |
| 238 | + targs = targets_from_xy(xy[cam], pnr) |
| 239 | + |
| 240 | + fallback_cal = _load_or_init_calibration(ori_path) |
| 241 | + cal = _load_or_init_calibration(ori_path) |
| 242 | + external_ok = True |
| 243 | + if init_external is not None: |
| 244 | + if init_external == "first4": |
| 245 | + idx4 = _select_four_indices("first4", len(pnr)) |
| 246 | + else: |
| 247 | + idx4 = _select_four_indices(init_external, len(pnr)) |
| 248 | + sel_xyz = np.asarray(xyz[pnr[idx4]], dtype=float) |
| 249 | + sel_xy = np.asarray(xy[cam][idx4], dtype=float) |
| 250 | + |
| 251 | + ok = external_calibration(cal, sel_xyz, sel_xy, cpar) |
| 252 | + if ok is False: |
| 253 | + external_ok = False |
| 254 | + print( |
| 255 | + f"external_calibration failed for camera {cam}; " |
| 256 | + "continuing with the loaded calibration" |
| 257 | + ) |
| 258 | + cal = fallback_cal |
| 259 | + |
| 260 | + if external_ok: |
| 261 | + sorted_targs = match_detection_to_ref( |
| 262 | + cal, |
| 263 | + np.asarray(xyz, dtype=float), |
| 264 | + targs, |
| 265 | + cpar, |
238 | 266 | ) |
239 | 267 |
|
240 | | - residuals, targ_ix, err_est = full_calibration( |
241 | | - cal, |
242 | | - np.asarray(xyz, dtype=float), |
243 | | - targs, |
244 | | - cpar, |
245 | | - list(flags) if should_use_native("orientation") else _orient_par_from_flags(flags), |
246 | | - ) |
247 | | - |
248 | | - packed = np.array( |
249 | | - [ |
250 | | - cal.get_pos(), |
251 | | - cal.get_angles(), |
252 | | - cal.get_affine(), |
253 | | - cal.get_decentering(), |
254 | | - cal.get_radial_distortion(), |
255 | | - ], |
256 | | - dtype=object, |
257 | | - ) |
258 | | - if np.any(np.isnan(np.hstack(packed))): |
259 | | - raise ValueError(f"Calibration for camera {cam} contains NaNs") |
| 268 | + try: |
| 269 | + residuals, targ_ix, err_est = full_calibration( |
| 270 | + cal, |
| 271 | + np.asarray(xyz, dtype=float), |
| 272 | + sorted_targs, |
| 273 | + cpar, |
| 274 | + list(flags) if should_use_native("orientation") else _orient_par_from_flags(flags), |
| 275 | + ) |
| 276 | + except ValueError as exc: |
| 277 | + print(f"full_calibration failed for camera {cam}; using loaded calibration: {exc}") |
| 278 | + cal = fallback_cal |
| 279 | + else: |
| 280 | + residuals = np.zeros((len(targs), 2), dtype=float) |
| 281 | + targ_ix = np.asarray([t.pnr() if hasattr(t, "pnr") else -999 for t in targs], dtype=int) |
| 282 | + err_est = np.zeros(11, dtype=float) |
| 283 | + |
| 284 | + packed = np.array( |
| 285 | + [ |
| 286 | + cal.get_pos(), |
| 287 | + cal.get_angles(), |
| 288 | + cal.get_affine(), |
| 289 | + cal.get_decentering(), |
| 290 | + cal.get_radial_distortion(), |
| 291 | + ], |
| 292 | + dtype=object, |
| 293 | + ) |
| 294 | + if np.any(np.isnan(np.hstack(packed))): |
| 295 | + cal = fallback_cal |
260 | 296 |
|
261 | | - if write: |
262 | | - addpar_path = Path(str(ori_path).replace(".ori", ".addpar")) |
263 | | - ori_path.parent.mkdir(parents=True, exist_ok=True) |
264 | | - cal.write(str(ori_path).encode(), str(addpar_path).encode()) |
| 297 | + if write: |
| 298 | + addpar_path = Path(str(ori_path).replace(".ori", ".addpar")) |
| 299 | + ori_path.parent.mkdir(parents=True, exist_ok=True) |
| 300 | + cal.write(str(ori_path).encode(), str(addpar_path).encode()) |
265 | 301 |
|
266 | | - calibrations.append(cal) |
| 302 | + calibrations.append(cal) |
267 | 303 |
|
268 | | - return calibrations |
| 304 | + return calibrations |
| 305 | + finally: |
| 306 | + os.chdir(previous_cwd) |
0 commit comments