Skip to content

Commit 5ecbf8d

Browse files
committed
improving compatibility
1 parent c5bc2fd commit 5ecbf8d

7 files changed

Lines changed: 142 additions & 51 deletions

File tree

src/openptv_python/_native_compat.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,56 @@ def should_use_native(feature_name: str | None = None) -> bool:
191191
return HAS_OPTV
192192

193193

194+
def get_num_cams(control_params: Any) -> int:
195+
"""Return the camera count from either backend's control parameter object."""
196+
197+
num_cams = getattr(control_params, "num_cams", None)
198+
if num_cams is not None:
199+
return int(num_cams)
200+
201+
getter = getattr(control_params, "get_num_cams", None)
202+
if callable(getter):
203+
return int(getter())
204+
205+
raise AttributeError("ControlParams object does not expose a camera count")
206+
207+
def get_multimedia_par(control_params: Any) -> Any:
208+
"""Return the multimedia-parameter object from either backend."""
209+
210+
multimedia = getattr(control_params, "mm", None)
211+
if multimedia is not None:
212+
return multimedia
213+
214+
getter = getattr(control_params, "get_multimedia_par", None)
215+
if callable(getter):
216+
multimedia = getter()
217+
else:
218+
getter = getattr(control_params, "get_multimedia_params", None)
219+
if callable(getter):
220+
multimedia = getter()
221+
else:
222+
raise AttributeError(
223+
"ControlParams object does not expose multimedia parameters"
224+
)
225+
226+
try:
227+
from openptv_python.parameters import MultimediaPar
228+
except Exception:
229+
return multimedia
230+
231+
if isinstance(multimedia, MultimediaPar):
232+
return multimedia
233+
234+
converted = MultimediaPar(
235+
nlay=int(multimedia.get_nlay()) if hasattr(multimedia, "get_nlay") else 1,
236+
n1=float(multimedia.get_n1()) if hasattr(multimedia, "get_n1") else float(getattr(multimedia, "n1", 1.0)),
237+
n2=list(multimedia.get_n2()) if hasattr(multimedia, "get_n2") else list(getattr(multimedia, "n2", [1.0])),
238+
d=list(multimedia.get_d()) if hasattr(multimedia, "get_d") else list(getattr(multimedia, "d", [0.0])),
239+
n3=float(multimedia.get_n3()) if hasattr(multimedia, "get_n3") else float(getattr(multimedia, "n3", 1.0)),
240+
)
241+
return converted
242+
243+
194244
# Initialize once so the default preference is explicit and optv availability
195245
# is reported immediately in sessions where it is missing.
196246
set_engine(DEFAULT_ENGINE, warn_once=True)

src/openptv_python/_native_convert.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,29 @@ def to_native_calibration(cal: Calibration):
5151
return native
5252

5353

54+
def from_native_calibration(calibration_obj):
55+
if isinstance(calibration_obj, Calibration):
56+
return calibration_obj
57+
58+
converted = Calibration()
59+
converted.set_pos(np.array(calibration_obj.get_pos()))
60+
converted.set_angles(np.array(calibration_obj.get_angles()))
61+
converted.set_primary_point(np.array(calibration_obj.get_primary_point()))
62+
converted.set_radial_distortion(np.array(calibration_obj.get_radial_distortion()))
63+
converted.set_decentering(np.array(calibration_obj.get_decentering()))
64+
65+
affine = np.array(calibration_obj.get_affine())
66+
if hasattr(converted, "set_affine_trans"):
67+
converted.set_affine_trans(affine)
68+
elif hasattr(converted, "set_affine_distortion"):
69+
converted.set_affine_distortion(affine)
70+
else:
71+
raise AttributeError("Calibration object does not support affine setters")
72+
73+
converted.set_glass_vec(np.array(calibration_obj.get_glass_vec()))
74+
return converted
75+
76+
5477
def to_native_control_par(cpar: ControlPar):
5578
if not isinstance(cpar, ControlPar):
5679
return cpar

src/openptv_python/correspondences.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
NMAX,
1313
PT_UNUSED,
1414
)
15+
from ._native_compat import get_multimedia_par, get_num_cams
1516
from .epi import epi_mm
1617
from .find_candidate import find_candidate
1718
from .parameters import ControlPar, VolumePar
@@ -388,8 +389,9 @@ def match_pairs(
388389
"""
389390
count = 0
390391

391-
for i1 in range(cpar.num_cams - 1):
392-
for i2 in range(i1 + 1, cpar.num_cams):
392+
num_cams = get_num_cams(cpar)
393+
for i1 in range(num_cams - 1):
394+
for i2 in range(i1 + 1, num_cams):
393395
for i in range(frm.num_targets[i1]):
394396
# if corrected[i1][i].x == PT_UNUSED: # no idea why it's here
395397
# continue
@@ -399,7 +401,7 @@ def match_pairs(
399401
corrected[i1][i].y,
400402
calib[i1],
401403
calib[i2],
402-
cpar.mm,
404+
get_multimedia_par(cpar),
403405
vpar,
404406
)
405407

@@ -554,7 +556,7 @@ def py_correspondences(
554556
num_targs - total number of targets (must be greater than the sum of
555557
previous 3).
556558
"""
557-
num_cams = cparam.num_cams
559+
num_cams = get_num_cams(cparam)
558560
frm = Frame(num_cams, MAX_TARGETS)
559561

560562
# Special case of a single camera, follow the single_cam_correspondence docstring
@@ -679,20 +681,21 @@ def correspondences(
679681
680682
"""
681683
nmax = NMAX
684+
num_cams = get_num_cams(cpar)
682685

683686
# Allocation of scratch buffers for internal tasks and return-value space
684-
con0 = np.recarray((nmax * cpar.num_cams,), dtype=n_tupel_dtype)
687+
con0 = np.recarray((nmax * num_cams,), dtype=n_tupel_dtype)
685688
con0.p = 0
686689
con0.corr = 0.0
687690

688-
con = np.recarray((nmax * cpar.num_cams,), dtype=n_tupel_dtype)
691+
con = np.recarray((nmax * num_cams,), dtype=n_tupel_dtype)
689692
con.p = 0
690693
con.corr = 0.0
691694

692-
tim = safely_allocate_target_usage_marks(cpar.num_cams, nmax)
695+
tim = safely_allocate_target_usage_marks(num_cams, nmax)
693696

694697
# allocate memory for lists of correspondences
695-
corr_list = safely_allocate_adjacency_lists(cpar.num_cams, frm.num_targets)
698+
corr_list = safely_allocate_adjacency_lists(num_cams, frm.num_targets)
696699

697700
# if I understand correctly, the number of matches cannot be more than the number of
698701
# targets (dots) in the first image. In the future we'll replace it by the maximum
@@ -704,38 +707,38 @@ def correspondences(
704707
match_pairs(corr_list, corrected, frm, vpar, cpar, calib)
705708

706709
# search consistent quadruplets in the corr_list
707-
if cpar.num_cams == 4:
710+
if num_cams == 4:
708711
four_camera_matching(
709712
corr_list, frm.num_targets[0], vpar.corrmin, con0, 4 * nmax
710713
)
711714

712-
match_counts[0] = take_best_candidates(con0, con, cpar.num_cams, tim)
715+
match_counts[0] = take_best_candidates(con0, con, num_cams, tim)
713716
match_counts[3] += match_counts[0]
714717

715718
# search consistent triplets: 123, 124, 134, 234
716-
if (cpar.num_cams == 4 and cpar.all_cam_flag == 0) or cpar.num_cams == 3:
719+
if (num_cams == 4 and cpar.all_cam_flag == 0) or num_cams == 3:
717720
three_camera_matching(
718-
corr_list, cpar.num_cams, frm.num_targets, vpar.corrmin, con0, 4 * nmax, tim
721+
corr_list, num_cams, frm.num_targets, vpar.corrmin, con0, 4 * nmax, tim
719722
)
720723

721724
match_counts[1] = take_best_candidates(
722-
con0, con[match_counts[3] :].view(np.recarray), cpar.num_cams, tim
725+
con0, con[match_counts[3] :].view(np.recarray), num_cams, tim
723726
)
724727
match_counts[3] += match_counts[1]
725728

726729
# Search consistent pairs: 12, 13, 14, 23, 24, 34
727-
if cpar.num_cams > 1 and cpar.all_cam_flag == 0:
730+
if num_cams > 1 and cpar.all_cam_flag == 0:
728731
consistent_pair_matching(
729-
corr_list, cpar.num_cams, frm.num_targets, vpar.corrmin, con0, 4 * nmax, tim
732+
corr_list, num_cams, frm.num_targets, vpar.corrmin, con0, 4 * nmax, tim
730733
)
731734
match_counts[2] = take_best_candidates(
732-
con0, con[match_counts[3] :].view(np.recarray), cpar.num_cams, tim
735+
con0, con[match_counts[3] :].view(np.recarray), num_cams, tim
733736
)
734737
match_counts[3] += match_counts[2]
735738

736739
# Give each used pix the correspondence number
737740
for i in range(match_counts[3]):
738-
for j in range(cpar.num_cams):
741+
for j in range(num_cams):
739742
# Skip cameras without a correspondence obviously.
740743
if con[i].p[j] < 0:
741744
continue

src/openptv_python/multimed.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from numba import njit
55

66
from .calibration import Calibration
7+
from ._native_compat import get_multimedia_par, get_num_cams
78
from .parameters import (
89
ControlPar,
910
MultimediaPar,
@@ -263,17 +264,18 @@ def init_mmlut(vpar: VolumePar, cpar: ControlPar, cal: Calibration) -> Calibrati
263264

264265
# intersect with image vertices rays
265266
cal_t = Calibration(mmlut=cal.mmlut.copy())
267+
mm = get_multimedia_par(cpar)
266268

267269
for i in range(2):
268270
for j in range(2):
269271
x, y = pixel_to_metric(xc[i], yc[j], cpar)
270272
x -= cal.int_par.xh
271273
y -= cal.int_par.yh
272274
x, y = correct_brown_affine(x, y, cal.added_par)
273-
pos, a = ray_tracing(x, y, cal, cpar.mm)
275+
pos, a = ray_tracing(x, y, cal, mm)
274276
xyz = move_along_ray(z_min, pos, a)
275277
xyz_t, _, _, cal_t.ext_par.z0 = trans_cam_point(
276-
cal.ext_par, cpar.mm, cal.glass_par, xyz
278+
cal.ext_par, mm, cal.glass_par, xyz
277279
)
278280

279281
if xyz_t[2] < z_min_t:
@@ -290,7 +292,7 @@ def init_mmlut(vpar: VolumePar, cpar: ControlPar, cal: Calibration) -> Calibrati
290292

291293
xyz = move_along_ray(z_max, pos, a)
292294
xyz_t, _, _, cal_t.ext_par.z0 = trans_cam_point(
293-
cal.ext_par, cpar.mm, cal.glass_par, xyz
295+
cal.ext_par, mm, cal.glass_par, xyz
294296
)
295297

296298
if xyz_t[2] < z_min_t:
@@ -326,7 +328,7 @@ def init_mmlut(vpar: VolumePar, cpar: ControlPar, cal: Calibration) -> Calibrati
326328
for i in range(nr):
327329
for j in range(nz):
328330
xyz = np.r_[Ri[i] + cal_t.ext_par.x0, cal_t.ext_par.y0, Zi[j]]
329-
cal.mmlut_data.flat[i * nz + j] = multimed_r_nlay(cal_t, cpar.mm, xyz)
331+
cal.mmlut_data.flat[i * nz + j] = multimed_r_nlay(cal_t, mm, xyz)
330332

331333
# print(f"filled mmlut data with {data}")
332334
# cal.mmlut_data = data
@@ -405,6 +407,7 @@ def volumedimension(
405407
cal: List[Calibration],
406408
) -> Tuple[float, float, float, float, float, float]:
407409
"""Calculate the volume dimensions."""
410+
mm = get_multimedia_par(cpar)
408411
xc = [0.0, cpar.imx]
409412
yc = [0.0, cpar.imy]
410413

@@ -416,7 +419,7 @@ def volumedimension(
416419
if vpar.z_max_lay[1] > z_max:
417420
z_max = vpar.z_max_lay[1]
418421

419-
for i_cam in range(cpar.num_cams):
422+
for i_cam in range(get_num_cams(cpar)):
420423
for i in range(2):
421424
for j in range(2):
422425
x, y = pixel_to_metric(xc[i], yc[j], cpar)
@@ -426,7 +429,7 @@ def volumedimension(
426429

427430
x, y = correct_brown_affine(x, y, cal[i_cam].added_par)
428431

429-
pos, a = ray_tracing(x, y, cal[i_cam], cpar.mm)
432+
pos, a = ray_tracing(x, y, cal[i_cam], mm)
430433

431434
# TODO: seems that it should be + pos[2] instead of - pos[2]
432435
X = pos[0] + (z_min + pos[2]) * a[0] / a[2]

src/openptv_python/track.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
TR_MAX_CAMS,
2222
TR_UNUSED,
2323
)
24+
from ._native_compat import get_multimedia_par, get_num_cams
2425
from .imgcoord import img_coord
2526
from .orientation import point_position
2627
from .parameters import ControlPar, SequencePar, TrackPar, TrackParTuple, VolumePar, convert_track_par_to_tuple
@@ -427,11 +428,12 @@ def searchquader(
427428
mins = np.array([tpar.dvxmin, tpar.dvymin, tpar.dvzmin])
428429
maxes = np.array([tpar.dvxmax, tpar.dvymax, tpar.dvzmax])
429430

431+
num_cams = get_num_cams(cpar)
430432
quader = np.zeros((8, 3))
431-
xr = np.zeros(cpar.num_cams)
432-
xl = np.zeros(cpar.num_cams)
433-
yd = np.zeros(cpar.num_cams)
434-
yu = np.zeros(cpar.num_cams)
433+
xr = np.zeros(num_cams)
434+
xl = np.zeros(num_cams)
435+
yd = np.zeros(num_cams)
436+
yu = np.zeros(num_cams)
435437

436438
for pt in range(8):
437439
quader[pt] = point.copy()
@@ -444,7 +446,7 @@ def searchquader(
444446
# print(f" pt {pt} {quader[pt]}")
445447

446448
# calculation of search area in each camera
447-
for i in range(cpar.num_cams):
449+
for i in range(num_cams):
448450
# initially large or small values
449451
xr[i] = 0
450452
xl[i] = cpar.imx
@@ -574,7 +576,7 @@ def point_to_pixel(point: np.ndarray, cal: Calibration, cpar: ControlPar) -> np.
574576
# print(f"cal {cal}")
575577
# print(f"cpar.mm {cpar.mm}")
576578

577-
x, y = img_coord(point, cal, cpar.mm)
579+
x, y = img_coord(point, cal, get_multimedia_par(cpar))
578580
# print("img coord x, y", x, y)
579581
x, y = metric_to_pixel(x, y, cpar)
580582
# print("metric to pixel x, y", x, y)
@@ -648,18 +650,17 @@ def assess_new_position(
648650
649651
"""
650652
# Output variables
651-
targ_pos = np.array(
652-
[[COORD_UNUSED, COORD_UNUSED] for _ in range(run.cpar.num_cams)]
653-
)
654-
cand_inds = np.array([[-1] * MAX_CANDS for _ in range(run.cpar.num_cams)])
653+
num_cams = get_num_cams(run.cpar)
654+
targ_pos = np.array([[COORD_UNUSED, COORD_UNUSED] for _ in range(num_cams)])
655+
cand_inds = np.array([[-1] * MAX_CANDS for _ in range(num_cams)])
655656

656657
# Search rectangle limits
657658
left, right, up, down = ADD_PART, ADD_PART, ADD_PART, ADD_PART
658659

659-
# for cam in range(run.cpar.num_cams):
660+
# for cam in range(num_cams):
660661
# targ_pos[cam] = [COORD_UNUSED, COORD_UNUSED]
661662

662-
for cam in range(run.cpar.num_cams):
663+
for cam in range(num_cams):
663664
# Convert 3D search position to 2D pixel coordinates
664665
pixel = point_to_pixel(pos, run.cal[cam], run.cpar)
665666
# print(f"pos {pos}")
@@ -686,7 +687,7 @@ def assess_new_position(
686687

687688
valid_cams = 0
688689

689-
for cam in range(run.cpar.num_cams):
690+
for cam in range(num_cams):
690691
if (targ_pos[cam][0] != COORD_UNUSED) and (targ_pos[cam][1] != COORD_UNUSED):
691692
# Convert pixel coordinates to metric coordinates
692693
x, y = pixel_to_metric(targ_pos[cam][0], targ_pos[cam][1], run.cpar)
@@ -808,8 +809,9 @@ def trackcorr_c_loop(run_info, step):
808809
cpar = run_info.cpar
809810
curr_targets = fb.buf[1].targets
810811

811-
v1 = np.zeros((cpar.num_cams, 2)) # volume center projection on cameras
812-
v2 = np.zeros((cpar.num_cams, 2)) # volume center projection on cameras
812+
num_cams = get_num_cams(cpar)
813+
v1 = np.zeros((num_cams, 2)) # volume center projection on cameras
814+
v2 = np.zeros((num_cams, 2)) # volume center projection on cameras
813815

814816
# try to track correspondences from previous 0 - corp, variable h
815817
orig_parts = fb.buf[1].num_parts
@@ -936,7 +938,7 @@ def trackcorr_c_loop(run_info, step):
936938
if quali >= 2:
937939
in_volume = 0 # inside volume
938940

939-
dl, X[4] = point_position(v2, cpar.num_cams, cpar.mm, cal)
941+
dl, X[4] = point_position(v2, num_cams, get_multimedia_par(cpar), cal)
940942

941943
# volume check
942944
if (
@@ -1006,7 +1008,7 @@ def trackcorr_c_loop(run_info, step):
10061008
if quali >= 2:
10071009
X[3] = vec_copy(X[2])
10081010
in_volume = 0
1009-
dl, X[3] = point_position(v2, fb.num_cams, cpar.mm, cal)
1011+
dl, X[3] = point_position(v2, fb.num_cams, get_multimedia_par(cpar), cal)
10101012

10111013
# in volume check
10121014
if (
@@ -1210,7 +1212,7 @@ def trackback_c(run_info: TrackingRun):
12101212
# vec_copy(X[3], X[2])
12111213
in_volume = 0
12121214

1213-
_, X[3] = point_position(v2, fb.num_cams, cpar.mm, cal)
1215+
_, X[3] = point_position(v2, fb.num_cams, get_multimedia_par(cpar), cal)
12141216

12151217
# volume check
12161218
if (

0 commit comments

Comments
 (0)