Architecture¶
Runtime lifecycle¶
canonical data root
-> hash config + 12 model artifacts
-> load detector, landmark, pose, parsing, occlusion, and measure models
-> Assessor (one re-entrant lock around mutable inference sessions)
-> read and validate BGR uint8 image
-> preprocessing Session
-> 28 isolated component results
-> typed AssessmentResult
-> Python typed API
-> legacy mapping adapter
-> canonical CSV adapter
Assessor is the public lifecycle boundary. Construction fails before assessment output is
created if the config/model profile is missing or does not hash to the verified OFIQ v1.1.0
artifacts. Model sessions are preloaded, so corrupt model serialization also fails at
initialization rather than halfway through a batch.
OpenCV DNN networks and several ONNX/cv2.ml objects are mutable during inference. One
Assessor therefore serializes assessment with an RLock. Batch workers each own a
separate Assessor; the default is one worker because a model graph consumes substantial
memory. Multi-worker batches use a clean spawn context, never a fork of already-created
ONNX/OpenCV threads.
Measurements support that default. On the tested 64-image real-data workload, version 0.2.0 processed 2.649 images/s with one worker, 2.391 with two, and 2.140 with four, while median process-tree RSS rose from 1.292 GiB to 2.450 and 4.723 GiB. See Runtime performance for the complete host, input, alternation, and idle-guard contract.
Construction also disables OpenCV's process-wide optimized kernels with
cv2.setUseOptimized(False). The opencv-python-headless 4.5.5 wheel's optimized
float-resize path differs numerically from OFIQ's tested Conan OpenCV 4.5.5 CPU build;
the generic path produced matching tensors. This setting is idempotent but global:
applications that share a process with other OpenCV workloads must account for the
performance and last-bit effects. Separate worker processes contain that setting.
Preprocessing graph¶
BGR uint8 image
-> SSD face detection
-> 3DDFA-V2 pose
-> ADNet-98 landmarks
-> five-point LMEDS alignment (616 x 616)
-> BiSeNet parsing (400 x 400)
-> face-occlusion segmentation (616 x 616)
-> landmarked face-region mask
-> component executor
Each product is stored once on Session and reused by dependent measures. The component
executor catches exceptions at the same single/compound-measure granularity used by OFIQ:
crop produces four results, head pose produces three, and the remaining measures produce
one or two defined results. A failed group becomes typed FailureToAssess; other groups
remain available.
C++ comparison¶
| Concern | OFIQ C++ v1.1.0 | ofiqpy 0.2.0 |
|---|---|---|
| Configuration | Selectable measures and parameter overrides | Exact canonical config hash only |
| Executor | Builds configured measure list | Fixed canonical 28 outputs |
| Result status | QualityMeasureReturnCode per component |
Typed component status plus image status |
| Measure failure | Isolated in Executor |
Isolated per single/compound group |
| Preprocessing failure | All configured measures FTA | All 28 components FTA |
| Thread safety | Internal synchronization around mutable runtime | Assessor-level RLock |
| CSV identity | Supplied image path | Supplied/discovered image path |
| CSV columns | Active configured map | Fixed canonical 28-component map |
This is algorithm/profile parity, not full API/config parity. Passing a modified JAXN file is rejected even if it would be accepted by OFIQ C++.
Package map¶
ofiqpy/
profile.py canonical artifact manifest and integrity verification
config.py JAXN reader and verified model resolver
assessor.py preflight, locking, input validation, typed lifecycle
results.py component/image statuses and result types
pipeline.py OFIQ preprocessing graph
session.py shared preprocessing products
measures/ canonical component executor and algorithms
output.py canonical semicolon CSV encoding
batch.py recursive/resumable execution
conformance.py live reference runner and strict comparison report