Skip to content

Commit facaeda

Browse files
committed
updated more of optv - see native_backend_unification.md
1 parent 2ce5206 commit facaeda

11 files changed

Lines changed: 345 additions & 55 deletions

docs/native_backend_unification.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ points:
3939
- `openptv_python.image_processing.preprocess_image()`
4040
- `openptv_python.segmentation.targ_rec()`
4141

42+
## Native optv submodules
43+
44+
When `optv` is installed, the backend can use these submodules directly:
45+
46+
| optv submodule | Used for |
47+
| --- | --- |
48+
| `optv.calibration` | Calibration objects and calibration-file handling |
49+
| `optv.correspondences` | Stereo correspondences and single-camera correspondences |
50+
| `optv.epipolar` | Epipolar-curve generation |
51+
| `optv.image_processing` | High-pass preprocessing |
52+
| `optv.imgcoord` | Image-coordinate conversion helpers |
53+
| `optv.orientation` | Point reconstruction and calibration routines |
54+
| `optv.parameters` | Control, sequence, target, tracking, and volume parameters |
55+
| `optv.segmentation` | Target recognition |
56+
| `optv.tracker` | Tracking orchestration |
57+
| `optv.tracking_framebuf` | Targets, target arrays, frames, and target-file IO |
58+
| `optv.transforms` | Pixel/metric conversion helpers |
59+
| `optv.version` | Package version metadata |
60+
61+
The current backend preference is to route to these native modules whenever the
62+
`optv` engine is selected and the submodule is available, while preserving the
63+
Python compatibility path as the fallback.
64+
4265
The rest of the 3D-PTV pipeline is already callable from Python without native
4366
bindings, and the stress suite shows that several later stages also have native
4467
parity paths available:

src/openptv_python/_native_compat.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,31 @@ def _optional_import(module_name: str) -> ModuleType | None:
2626

2727

2828
optv_calibration = _optional_import("optv.calibration")
29+
optv_correspondences = _optional_import("optv.correspondences")
30+
optv_epipolar = _optional_import("optv.epipolar")
2931
optv_image_processing = _optional_import("optv.image_processing")
32+
optv_imgcoord = _optional_import("optv.imgcoord")
33+
optv_orientation = _optional_import("optv.orientation")
3034
optv_parameters = _optional_import("optv.parameters")
3135
optv_segmentation = _optional_import("optv.segmentation")
36+
optv_tracker = _optional_import("optv.tracker")
3237
optv_tracking_framebuf = _optional_import("optv.tracking_framebuf")
38+
optv_transforms = _optional_import("optv.transforms")
3339

3440
HAS_OPTV = any(
3541
module is not None
3642
for module in (
3743
optv_calibration,
44+
optv_correspondences,
45+
optv_epipolar,
3846
optv_image_processing,
47+
optv_imgcoord,
48+
optv_orientation,
3949
optv_parameters,
4050
optv_segmentation,
51+
optv_tracker,
4152
optv_tracking_framebuf,
53+
optv_transforms,
4254
)
4355
)
4456

@@ -49,6 +61,38 @@ def _optional_import(module_name: str) -> ModuleType | None:
4961
and hasattr(optv_parameters, "ControlParams")
5062
)
5163

64+
HAS_NATIVE_CORRESPONDENCES = (
65+
optv_correspondences is not None
66+
and hasattr(optv_correspondences, "correspondences")
67+
and hasattr(optv_correspondences, "single_cam_correspondence")
68+
and hasattr(optv_correspondences, "MatchedCoords")
69+
)
70+
71+
HAS_NATIVE_ORIENTATION = (
72+
optv_orientation is not None
73+
and hasattr(optv_orientation, "point_positions")
74+
and hasattr(optv_orientation, "multi_cam_point_positions")
75+
and hasattr(optv_orientation, "external_calibration")
76+
and hasattr(optv_orientation, "full_calibration")
77+
and hasattr(optv_orientation, "match_detection_to_ref")
78+
)
79+
80+
HAS_NATIVE_TRANSFORMS = (
81+
optv_transforms is not None
82+
and hasattr(optv_transforms, "convert_arr_pixel_to_metric")
83+
and hasattr(optv_transforms, "convert_arr_metric_to_pixel")
84+
)
85+
86+
HAS_NATIVE_IMGCOORD = (
87+
optv_imgcoord is not None
88+
and hasattr(optv_imgcoord, "image_coordinates")
89+
and hasattr(optv_imgcoord, "flat_image_coordinates")
90+
)
91+
92+
HAS_NATIVE_TRACKER = optv_tracker is not None and hasattr(optv_tracker, "Tracker")
93+
94+
HAS_NATIVE_EPIPOLAR = optv_epipolar is not None and hasattr(optv_epipolar, "epipolar_curve")
95+
5296
HAS_NATIVE_SEGMENTATION = (
5397
optv_segmentation is not None
5498
and hasattr(optv_segmentation, "target_recognition")
@@ -224,15 +268,33 @@ def should_use_native(feature_name: str | None = None) -> bool:
224268
if feature_name in {None, "", "preprocess_image"}:
225269
return HAS_NATIVE_PREPROCESS
226270

271+
if feature_name == "correspondences":
272+
return HAS_NATIVE_CORRESPONDENCES
273+
227274
if feature_name == "target_recognition":
228275
return HAS_NATIVE_SEGMENTATION
229276

277+
if feature_name in {"orientation", "point_positions"}:
278+
return HAS_NATIVE_ORIENTATION
279+
230280
if feature_name == "calibration":
231281
return HAS_NATIVE_CALIBRATION
232282

283+
if feature_name == "transforms":
284+
return HAS_NATIVE_TRANSFORMS
285+
286+
if feature_name == "imgcoord":
287+
return HAS_NATIVE_IMGCOORD
288+
233289
if feature_name == "targets":
234290
return HAS_NATIVE_TARGETS
235291

292+
if feature_name == "tracker":
293+
return HAS_NATIVE_TRACKER
294+
295+
if feature_name == "epipolar":
296+
return HAS_NATIVE_EPIPOLAR
297+
236298
return HAS_OPTV
237299

238300

src/openptv_python/_native_convert.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
optv_tracking_framebuf,
1717
)
1818
from .calibration import Calibration
19-
from .parameters import ControlPar, TargetPar
19+
from .parameters import ControlPar, SequencePar, TargetPar, TrackPar, VolumePar
2020
from .tracking_frame_buf import Target
2121

2222

@@ -114,6 +114,77 @@ def to_native_control_par(cpar: ControlPar):
114114
return native
115115

116116

117+
def to_native_volume_par(vpar: VolumePar):
118+
if not isinstance(vpar, VolumePar):
119+
return vpar
120+
121+
parameters_module = _optv_parameters_module()
122+
native = parameters_module.VolumeParams()
123+
native.set_X_lay(list(vpar.get_X_lay()))
124+
native.set_Zmin_lay(list(vpar.get_Zmin_lay()))
125+
native.set_Zmax_lay(list(vpar.get_Zmax_lay()))
126+
native.set_cn(vpar.get_cn())
127+
native.set_cnx(vpar.get_cnx())
128+
native.set_cny(vpar.get_cny())
129+
native.set_csumg(vpar.get_csumg())
130+
native.set_corrmin(vpar.get_corrmin())
131+
native.set_eps0(vpar.get_eps0())
132+
return native
133+
134+
135+
def to_native_sequence_par(spar: SequencePar):
136+
if not isinstance(spar, SequencePar):
137+
return spar
138+
139+
parameters_module = _optv_parameters_module()
140+
141+
try:
142+
native = parameters_module.SequenceParams(num_cams=spar.get_num_cams())
143+
except TypeError:
144+
native = parameters_module.SequenceParams()
145+
146+
native.set_first(spar.get_first())
147+
native.set_last(spar.get_last())
148+
149+
img_base_name = spar.get_img_base_name()
150+
if isinstance(img_base_name, (list, tuple)):
151+
try:
152+
native.set_img_base_name(list(img_base_name))
153+
except TypeError:
154+
for cam_index, base_name in enumerate(img_base_name):
155+
native.set_img_base_name(cam_index, base_name)
156+
157+
return native
158+
159+
160+
def to_native_track_par(tpar: TrackPar):
161+
if not isinstance(tpar, TrackPar):
162+
return tpar
163+
164+
parameters_module = _optv_parameters_module()
165+
native = parameters_module.TrackingParams()
166+
for attr_name in (
167+
"dvxmin",
168+
"dvxmax",
169+
"dvymin",
170+
"dvymax",
171+
"dvzmin",
172+
"dvzmax",
173+
"dangle",
174+
"dacc",
175+
"add",
176+
"dsumg",
177+
"dn",
178+
"dnx",
179+
"dny",
180+
):
181+
getter = getattr(tpar, f"get_{attr_name}", None)
182+
setter = getattr(native, f"set_{attr_name}", None)
183+
if callable(getter) and callable(setter):
184+
setter(getter())
185+
return native
186+
187+
117188
def to_native_target_par(tpar: TargetPar):
118189
if not isinstance(tpar, TargetPar):
119190
return tpar

src/openptv_python/correspondences.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
PT_UNUSED,
1414
)
1515
from ._native_compat import get_multimedia_par, get_num_cams
16+
from ._native_compat import HAS_NATIVE_CORRESPONDENCES, optv_correspondences, should_use_native
17+
from ._native_convert import (
18+
to_native_calibration,
19+
to_native_control_par,
20+
to_native_volume_par,
21+
)
1622
from .epi import epi_mm
1723
from .find_candidate import find_candidate
1824
from .parameters import ControlPar, VolumePar
@@ -557,6 +563,27 @@ def py_correspondences(
557563
previous 3).
558564
"""
559565
num_cams = get_num_cams(cparam)
566+
567+
if should_use_native("correspondences") and HAS_NATIVE_CORRESPONDENCES and optv_correspondences is not None:
568+
native_cals = [to_native_calibration(cal) for cal in calib]
569+
native_vparam = to_native_volume_par(vparam)
570+
native_cparam = to_native_control_par(cparam)
571+
572+
if num_cams == 1:
573+
return optv_correspondences.single_cam_correspondence(
574+
img_pts,
575+
flat_coords,
576+
native_cals,
577+
)
578+
579+
return optv_correspondences.correspondences(
580+
img_pts,
581+
flat_coords,
582+
native_cals,
583+
native_vparam,
584+
native_cparam,
585+
)
586+
560587
frm = Frame(num_cams, MAX_TARGETS)
561588

562589
# Special case of a single camera, follow the single_cam_correspondence docstring

src/openptv_python/multimed.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@
1616
from .vec_utils import vec_norm
1717

1818

19+
def _mm_nlay(mm: MultimediaPar) -> int:
20+
return int(mm.get_nlay()) if hasattr(mm, "get_nlay") else int(mm.nlay)
21+
22+
23+
def _mm_n1(mm: MultimediaPar) -> float:
24+
return float(mm.get_n1()) if hasattr(mm, "get_n1") else float(mm.n1)
25+
26+
27+
def _mm_n2(mm: MultimediaPar) -> list[float]:
28+
return list(mm.get_n2()) if hasattr(mm, "get_n2") else list(mm.n2)
29+
30+
31+
def _mm_d(mm: MultimediaPar) -> list[float]:
32+
return list(mm.get_d()) if hasattr(mm, "get_d") else list(mm.d)
33+
34+
35+
def _mm_n3(mm: MultimediaPar) -> float:
36+
return float(mm.get_n3()) if hasattr(mm, "get_n3") else float(mm.n3)
37+
38+
1939
def multimed_nlay(
2040
cal: Calibration, mm: MultimediaPar, pos: np.ndarray
2141
) -> Tuple[float, float]:
@@ -32,7 +52,7 @@ def multimed_nlay(
3252
def multimed_r_nlay(cal: Calibration, mm: MultimediaPar, pos: np.ndarray) -> float:
3353
"""Calculate the radial shift for the multimedia model."""
3454
# 1-medium case
35-
if mm.n1 == 1 and mm.nlay == 1 and mm.n2[0] == 1 and mm.n3 == 1:
55+
if _mm_n1(mm) == 1 and _mm_nlay(mm) == 1 and _mm_n2(mm)[0] == 1 and _mm_n3(mm) == 1:
3656
return 1.0
3757

3858
# interpolation using the existing mmlut
@@ -44,11 +64,11 @@ def multimed_r_nlay(cal: Calibration, mm: MultimediaPar, pos: np.ndarray) -> flo
4464
return mmf
4565

4666
mmf = fast_multimed_r_nlay(
47-
mm.nlay,
48-
mm.n1,
49-
np.array(mm.n2),
50-
mm.n3,
51-
np.array(mm.d),
67+
_mm_nlay(mm),
68+
_mm_n1(mm),
69+
np.array(_mm_n2(mm)),
70+
_mm_n3(mm),
71+
np.array(_mm_d(mm)),
5272
cal.ext_par.x0,
5373
cal.ext_par.y0,
5474
cal.ext_par.z0,
@@ -117,7 +137,7 @@ def trans_cam_point(
117137
origin = np.r_[ex.x0, ex.y0, ex.z0] # type: ignore
118138
pos = pos.astype(np.float64)
119139

120-
return fast_trans_cam_point(origin, mm.d[0], glass_dir, pos)
140+
return fast_trans_cam_point(origin, _mm_d(mm)[0], glass_dir, pos)
121141

122142

123143
@njit(fastmath=True, cache=True, nogil=True)
@@ -176,7 +196,7 @@ def back_trans_point(
176196
-------
177197
A numpy array representing the position of the point in the camera coordinate system.
178198
"""
179-
return fast_back_trans_point(glass, mm.d[0], cross_c, cross_p, pos_t)
199+
return fast_back_trans_point(glass, _mm_d(mm)[0], cross_c, cross_p, pos_t)
180200

181201

182202
@njit(fastmath=True, cache=True, nogil=True)

0 commit comments

Comments
 (0)