Skip to content

Commit 7001a08

Browse files
committed
updated engine selection
1 parent 370f4f7 commit 7001a08

22 files changed

Lines changed: 563 additions & 115 deletions

README.md

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,40 @@
11
# openptv-python
22

3-
Unified Python/OpenPTV repository for the combined `openptv_python` core library and `pyptv` GUI.
3+
Unified repository for the merged `openptv_python` core library and `pyptv` GUI.
44

5-
Current repository version: 0.5.0
5+
Current version: 0.5.0
66

7-
## Overview
7+
## What This Repo Is
88

9-
This repository now exposes one shared API and one shared version stream. The main pieces are:
9+
This repository exposes one public API and one shared version stream.
1010

11-
- `openptv_python`: the Python library layer, used for pure-Python execution, Numba-accelerated kernels, and shared parameter/data handling.
12-
- `pyptv`: the GUI and batch-processing layer, built on top of the same Python API.
13-
- `optv`: native bindings used where available for delegated operations such as image preprocessing and full-frame target recognition.
11+
- `openptv_python`: the core Python library, with Numba-accelerated kernels and a Python fallback path for debugging and inspection.
12+
- `pyptv`: the GUI and batch layer that calls the same shared API.
13+
- `optv`: the preferred native engine when installed; it is used automatically for supported operations and treated as the fast reference path.
1414

15-
The current setup is intentionally structured so that the same codebase can run in three modes:
15+
The engine choice is exposed in both GUI and CLI:
1616

17-
1. Pure Python for readability, debugging, and visual inspection.
18-
2. Pure Python plus Numba for accelerated kernels where available.
19-
3. Native `optv` delegation for selected operations when the bindings are installed.
17+
- default engine: `optv`
18+
- override for debugging/testing: `python`
19+
- the selected engine is stored in the experiment YAML as `engine`
2020

21-
The GUI version shown in PyPTV comes from the shared version module in `src/openptv_python/version.py`.
21+
When `optv` is unavailable, the shared API falls back to the Python/Numba implementation and reports the reason once per session.
2222

2323
## Versioning
2424

25-
Versioning is now centralized:
25+
The canonical version source is [src/openptv_python/version.py](src/openptv_python/version.py).
2626

27-
- Canonical version source: [src/openptv_python/version.py](src/openptv_python/version.py)
28-
- Package version: 0.5.0
29-
- Release boundary: this is the first combined version after merging the older `openptv_python` and `pyptv` package streams
30-
31-
To bump the version, run:
27+
To bump the repository version, run:
3228

3329
```bash
34-
python src/pyptv/bump_version.py --patch
30+
python scripts/bump_version.py --patch
3531
```
3632

3733
Use `--minor` or `--major` for larger increments. The script updates the shared version module and `pyproject.toml` together.
3834

3935
## Installation
4036

41-
The project supports Python `>=3.11,<3.14`.
37+
Python support: `>=3.11,<3.14`
4238

4339
### Recommended: uv
4440

@@ -67,21 +63,19 @@ uv sync --extra dev
6763
### Alternative: pip
6864

6965
```bash
70-
conda create -n openptv-python -c conda-forge python=3.12
71-
conda activate openptv-python
7266
pip install .
7367
```
7468

7569
GUI extras:
7670

7771
```bash
78-
pip install "[gui]"
72+
pip install ".[gui]"
7973
```
8074

8175
Developer extras:
8276

8377
```bash
84-
pip install "[dev]"
78+
pip install ".[dev]"
8579
```
8680

8781
## Usage
@@ -90,21 +84,38 @@ pip install "[dev]"
9084

9185
```python
9286
import openptv_python
93-
9487
print(openptv_python.__version__)
9588
```
9689

9790
### GUI
9891

9992
```bash
100-
uv run pyptv
93+
uv run pyptv --engine optv
10194
```
10295

103-
The GUI is versioned from the same shared source as the library.
96+
Use `--engine python` only for debugging, testing, or visualization work where you want to force the fallback backend.
97+
98+
### Batch
99+
100+
Serial batch processing:
101+
102+
```bash
103+
uv run python -m pyptv.pyptv_batch path/to/parameters.yaml 10000 10010 --engine optv
104+
```
105+
106+
Parallel batch processing:
107+
108+
```bash
109+
uv run python -m pyptv.pyptv_batch_parallel path/to/parameters.yaml 10000 10010 4 --engine optv
110+
```
111+
112+
Both batch entrypoints accept `--engine python` for debugging/testing.
104113

105114
## Documentation
106115

107-
Detailed guides live under [docs/](docs/), especially the PyPTV guides in [docs/pyptv](docs/pyptv/README.md).
116+
Start with [docs/index.md](docs/index.md) for the full documentation tree.
117+
118+
The PyPTV topic guides are in [docs/pyptv/README.md](docs/pyptv/README.md).
108119

109120
## Testing
110121

@@ -114,12 +125,12 @@ Run the focused test suites with:
114125
uv run --with pytest pytest -q tests/openptv_python tests/pyptv
115126
```
116127

117-
The repository also contains test datasets in [tests/testing_folder](tests/testing_folder/).
128+
The test datasets live in [tests/testing_folder](tests/testing_folder/).
118129

119-
## Repository Structure
130+
## Repository Layout
120131

121-
- `src/openptv_python/`: shared Python library code and canonical version module
122-
- `src/pyptv/`: GUI, batch tools, and compatibility helpers
132+
- `src/openptv_python/`: shared Python library, engine selector, and canonical version module
133+
- `src/pyptv/`: GUI, batch tools, and compatibility layer
123134
- `docs/`: Sphinx documentation
124135
- `tests/`: library, GUI, and fixture tests
125136

docs/index.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Welcome to openptv_python's documentation!
22

3-
Python version of the OpenPTV library.
3+
This is the documentation tree for the unified OpenPTV Python repository.
44

5-
Start with the README for installation and backend selection details. The short
6-
version is:
5+
Start with the root [README.md](../README.md) for installation, versioning, and engine-selection details.
76

8-
- install runtime dependencies with `uv sync` on Python 3.12 or 3.13
7+
Short version:
8+
9+
- install runtime dependencies with `uv sync`
910
- install contributor tooling with `uv sync --extra dev`
10-
- use the same Python API regardless of backend
11-
- get pure Python behavior everywhere, Numba acceleration in selected kernels,
12-
and automatic native `optv` delegation for preprocessing and full-frame
13-
target recognition when available
11+
- use `optv` by default for supported operations
12+
- use `--engine python` in GUI or batch runs when you want to force the fallback backend for debugging/testing
13+
- the shared API automatically falls back to Python/Numba when `optv` is unavailable
1414

1515
```{toctree}
1616
:caption: 'Contents:'

docs/pyptv/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This documentation tree is the detailed guide for the unified repository.
44

5-
Start with the top-level [README.md](../../README.md) for the current combined project overview, versioning, and installation entry points.
5+
Start with the top-level [README.md](../../README.md) for the current combined project overview, versioning, engine selection, and installation entry points.
66

77
Use the pages in this directory for deeper topic-specific documentation:
88

docs/pyptv/installation.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ The script will:
6464
- Build and install OpenPTV (liboptv)
6565
- Install PyPTV in development mode
6666

67+
If you want the GUI and batch tools to use the Python fallback engine for debugging or testing, install the repo normally and launch with `--engine python`.
68+
6769
### Method 2: Manual Installation
6870

6971
If you prefer manual control or need to customize the installation:
@@ -115,8 +117,12 @@ python -c "import pyptv; print('PyPTV installed successfully!')"
115117
# Launch the GUI (should open without errors)
116118
python -m pyptv.pyptv_gui
117119

120+
# force Python fallback for debugging/testing
121+
python -m pyptv.pyptv_gui --engine python
122+
118123
# Run the test suite
119124
pytest tests/
125+
```
120126

121127
## Testing: Headless vs GUI
122128

docs/pyptv/quick-start.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Get up and running with PyPTV using the included test dataset in under 10 minute
99

1010
PyPTV uses a modern conda environment (`environment.yml`) and separates tests into headless (`tests/`) and GUI (`tests_gui/`) categories. See the README for details.
1111

12+
By default the GUI and batch tools use `optv` when it is available. Pass `--engine python` if you want to force the Python/Numba backend while debugging or comparing results.
13+
1214
## Overview
1315

1416
This guide walks you through:
@@ -33,6 +35,9 @@ Start the PyPTV graphical interface:
3335

3436
```bash
3537
python -m pyptv.pyptv_gui
38+
39+
# force the Python engine instead of optv
40+
python -m pyptv.pyptv_gui --engine python
3641
```
3742

3843
The main PyPTV window should open with a parameter tree on the left and camera views on the right.

src/openptv_python/_native_compat.py

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
"""Optional compatibility layer for reusing optv py_bind as a native provider."""
1+
"""Compatibility layer for selecting between optv and Python/Numba engines."""
22

33
from __future__ import annotations
44

55
from importlib import import_module
66
from types import ModuleType
7-
from typing import Any
7+
from typing import Any, Literal
8+
import warnings
9+
10+
EngineName = Literal["optv", "python"]
11+
12+
DEFAULT_ENGINE: EngineName = "optv"
13+
ENGINE_OPTV: EngineName = "optv"
14+
ENGINE_PYTHON: EngineName = "python"
15+
16+
_ENGINE_PREFERENCE: EngineName = DEFAULT_ENGINE
17+
_ENGINE_REASON: str = ""
18+
_ENGINE_WARNING_EMITTED = False
819

920

1021
def _optional_import(module_name: str) -> ModuleType | None:
@@ -57,6 +68,134 @@ def _optional_import(module_name: str) -> ModuleType | None:
5768
)
5869

5970

71+
def _emit_engine_warning(reason: str) -> None:
72+
global _ENGINE_WARNING_EMITTED
73+
if _ENGINE_WARNING_EMITTED:
74+
return
75+
76+
warnings.warn(reason, RuntimeWarning, stacklevel=3)
77+
_ENGINE_WARNING_EMITTED = True
78+
79+
80+
def _set_engine_state(engine: EngineName, reason: str) -> None:
81+
global _ENGINE_PREFERENCE, _ENGINE_REASON, _ENGINE_WARNING_EMITTED
82+
_ENGINE_PREFERENCE = engine
83+
_ENGINE_REASON = reason
84+
_ENGINE_WARNING_EMITTED = False
85+
86+
87+
def _resolve_engine_request(engine: EngineName | str | None) -> EngineName:
88+
if engine is None:
89+
return DEFAULT_ENGINE
90+
91+
normalized = str(engine).strip().lower()
92+
if normalized in {"optv", "native", "c", "fast"}:
93+
return ENGINE_OPTV
94+
if normalized in {"python", "numba", "pure-python", "fallback"}:
95+
return ENGINE_PYTHON
96+
97+
raise ValueError(f"Unknown engine '{engine}'. Use 'optv' or 'python'.")
98+
99+
100+
def _resolve_active_engine() -> tuple[EngineName, str]:
101+
if _ENGINE_PREFERENCE == ENGINE_PYTHON:
102+
return ENGINE_PYTHON, "Forced Python engine"
103+
104+
if HAS_OPTV:
105+
return ENGINE_OPTV, "Using optv engine"
106+
107+
return ENGINE_PYTHON, "optv is unavailable; using Python/Numba fallback"
108+
109+
110+
def set_engine(engine: EngineName | str | None = None, *, warn_once: bool = True) -> EngineName:
111+
"""Set the preferred engine for native-backed calls.
112+
113+
Parameters
114+
----------
115+
engine:
116+
Preferred engine. ``optv`` is the default and ``python`` forces the
117+
Python/Numba code path.
118+
warn_once:
119+
Emit a one-time warning when the selected engine cannot be used.
120+
"""
121+
122+
requested = _resolve_engine_request(engine)
123+
124+
if requested == ENGINE_PYTHON:
125+
_set_engine_state(requested, "Forced Python engine")
126+
return ENGINE_PYTHON
127+
128+
if HAS_OPTV:
129+
_set_engine_state(requested, "Using optv engine")
130+
return ENGINE_OPTV
131+
132+
reason = "optv is unavailable; using Python/Numba fallback"
133+
_set_engine_state(requested, reason)
134+
if warn_once:
135+
_emit_engine_warning(reason)
136+
return ENGINE_PYTHON
137+
138+
139+
def get_engine_preference() -> EngineName:
140+
"""Return the user-requested engine preference."""
141+
142+
return _ENGINE_PREFERENCE
143+
144+
145+
def get_active_engine() -> EngineName:
146+
"""Return the engine currently used for native-backed calls."""
147+
148+
active, _ = _resolve_active_engine()
149+
return active
150+
151+
152+
def get_engine_reason() -> str:
153+
"""Return a human-readable explanation of the active engine choice."""
154+
155+
active, reason = _resolve_active_engine()
156+
if active == ENGINE_PYTHON and _ENGINE_PREFERENCE == ENGINE_PYTHON:
157+
return reason
158+
if active == ENGINE_PYTHON and _ENGINE_PREFERENCE == ENGINE_OPTV:
159+
return reason
160+
return reason
161+
162+
163+
def get_engine_status() -> str:
164+
"""Return a short status string for GUI and batch reporting."""
165+
166+
return f"engine={get_active_engine()} ({get_engine_reason()})"
167+
168+
169+
def should_use_native(feature_name: str | None = None) -> bool:
170+
"""Return True when the native optv implementation should be used.
171+
172+
The selector prefers optv by default and falls back to Python/Numba when
173+
optv is unavailable or the user forced the Python engine.
174+
"""
175+
176+
if get_engine_preference() == ENGINE_PYTHON:
177+
return False
178+
179+
if feature_name in {None, "", "preprocess_image"}:
180+
return HAS_NATIVE_PREPROCESS
181+
182+
if feature_name == "target_recognition":
183+
return HAS_NATIVE_SEGMENTATION
184+
185+
if feature_name == "calibration":
186+
return HAS_NATIVE_CALIBRATION
187+
188+
if feature_name == "targets":
189+
return HAS_NATIVE_TARGETS
190+
191+
return HAS_OPTV
192+
193+
194+
# Initialize once so the default preference is explicit and optv availability
195+
# is reported immediately in sessions where it is missing.
196+
set_engine(DEFAULT_ENGINE, warn_once=True)
197+
198+
60199
def native_preprocess_image(*args: Any, **kwargs: Any) -> Any:
61200
if not HAS_NATIVE_PREPROCESS or optv_image_processing is None:
62201
raise RuntimeError("optv native preprocess_image is not available")

src/openptv_python/image_processing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import numpy as np
66
from numba import njit
77

8-
from ._native_compat import HAS_NATIVE_PREPROCESS, native_preprocess_image
8+
from ._native_compat import native_preprocess_image, should_use_native
99
from ._native_convert import to_native_control_par
1010
from .parameters import ControlPar
1111

@@ -261,7 +261,7 @@ def prepare_image(
261261

262262
def preprocess_image(img, filter_hp, cpar, dim_lp) -> np.ndarray:
263263
"""Decorate prepare_image with default parameters."""
264-
if HAS_NATIVE_PREPROCESS:
264+
if should_use_native("preprocess_image"):
265265
native_cpar = to_native_control_par(cpar)
266266
return native_preprocess_image(
267267
img,

0 commit comments

Comments
 (0)