lardon3d/docs/architecture/feature_store.md

627 lines
17 KiB
Markdown

# Feature Store v1/v2
## Status
```text
CURRENT_PROJECT_DB_SCHEMA=v25
FEATURE_STORE_STATUS=IMPLEMENTED
FEATURE_FILE_V1=FROZEN
FEATURE_FILE_V2=IMPLEMENTED
HISTORICAL_FEATURE_TASK=features.extract/1
CURRENT_FEATURE_BATCH_TASK=features.extract.batch/1
PROJECT_DB_V25_FEATURE_BATCH=IMPLEMENTED/VALIDATED
PER_IMAGE_FEATURE_RESULT=ATOMIC
PER_IMAGE_ATOMICITY_REQUIRES_CROSS_IMAGE_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
REAL_A6000_PRE_SFM=PASS/FROZEN
```
The Feature Store is the persistent local visual-memory layer for Lardon3D.
The scientific identity of a Feature Set remains per image and immutable after publication. Project DB
v25 adds an operational selected-execution batch Task, but does not change Feature Set identity,
Feature File formats, ORB descriptor semantics or the historical single-image Task contract.
## Role and identity model
A logical `FeatureSet` belongs to one `image_id` and identifies:
- one extractor kind;
- one extractor version;
- one canonical parameter fingerprint;
- one exact source image content identity;
- one immutable published Feature Asset.
Two logical images with identical source content and identical extractor configuration keep distinct
`feature_set_id` values. They may share the same immutable physical Feature Asset when the resulting
Feature File bytes are identical.
Downstream correspondence identity references:
```text
feature_set_id + feature_index
```
Both components are immutable after Feature Set publication.
`feature_set_id` and `feature_asset_id` are SQLite `AUTOINCREMENT` identities. A committed published
identity is never reassigned to another object. SQLite stores logical relations, hashes, sizes,
extractor metadata and light metrics; keypoint arrays and descriptor arrays remain outside SQLite.
## Extractor registry and scientific configuration
The historical production ORB extractor uses OpenCV 5 behind a C boundary. No C++ exception or
`cv::Mat` crosses the public C API.
The historical registered Task Kind is:
```text
features.extract/1
```
That Task processes one image as one scientific unit.
The current selected-execution operational Task Kind is:
```text
features.extract.batch/1
```
It processes multiple independently selected images under one durable owner while preserving one
immutable Feature result per image.
### ORB v1 configuration
The ORB v1 configuration contains three `uint32_t` fields:
- `max_features`, range `1..8192`;
- `pyramid_levels`, range `1..16`;
- `fast_threshold`, range `1..255`.
Its SHA-256 parameter fingerprint is computed from the canonical 24-byte domain containing:
```text
L3DORBP1
version
max_features
pyramid_levels
fast_threshold
```
with fixed little-endian integer encoding.
The fingerprint does not depend on C struct padding, locale, Task ID, resource reservation,
admitted CPU count or admitted batch size.
ORB v1 produces 32-byte binary descriptors.
## OpenCV execution control
OpenCV thread count is process-wide operational state, not scientific identity.
Before Queue execution, Lardon3D establishes the validated OpenCV baseline from the actual host
compute pool. The active heavy callback applies the count required by its admitted contract and
restores the baseline on every exit path.
The Queue retains one active callback. This prevents unrelated heavy Tasks from racing the same
process-wide OpenCV configuration.
Historical `features.extract/1` may use the admitted OpenCV CPU count inside one image extraction.
For `features.extract.batch/1`, cross-image participants are the primary concurrency mechanism.
Independent participants prepare different images; Lardon3D does not blindly multiply a full OpenCV
thread team inside every participant.
The admitted CPU count is resource policy and never enters:
- ORB v1 fingerprint;
- Feature Set identity;
- Feature File bytes;
- source-image identity.
The controlled OpenCV 5.0.0 validation at 1, 2, 4, 8 and 12 threads required identical:
- feature count;
- keypoint order;
- binary32 values of all six persisted keypoint fields;
- descriptor row order and bytes;
- complete Feature File SHA-256.
Twelve threads are reference-host validation evidence, not a portable product ceiling.
`cv::KeyPoint::class_id` is not persisted by the frozen Feature File record and therefore cannot
define scientific identity.
## Decode bound
The 100,000,000-pixel limit is checked after `cv::imread`.
The API used by this implementation does not provide a reliable multi-format dimension probe without
decoding. Therefore decode peak memory can precede rejection.
Lardon3D does not claim pre-decode memory bounding and does not maintain a parallel JPEG/PNG dimension
parser for this purpose.
## Feature File v1
Feature File v1 is little-endian and requires IEEE-754 binary32.
Limits:
```text
maximum file size 16 MiB
maximum feature count 8192
ORB descriptor U8 x 32
```
### Header
| Offset | Size | Field |
| ---: | ---: | --- |
| 0 | 8 | magic `L3DFEAT\0` |
| 8 | 4 | format version = 1 |
| 12 | 4 | header size = 160 |
| 16 | 4 | feature count |
| 20 | 4 | descriptor dimension = 32 |
| 24 | 4 | descriptor type = U8 |
| 28 | 4 | keypoint record size = 24 |
| 32, 36 | 4 + 4 | decoded image width and height |
| 40, 48, 56 | 8 + 8 + 8 | keypoint offset, descriptor offset, total size |
| 64 | 32 | source image asset SHA-256 |
| 96 | 32 | parameter fingerprint |
| 128 | 16 | `orb\0` followed by reserved zero bytes |
| 144 | 4 | extractor version = 1 |
| 148 | 12 | reserved, must be zero |
Each keypoint contains six 32-bit words:
1. `x` binary32;
2. `y` binary32;
3. `size` binary32;
4. orientation binary32;
5. response binary32;
6. signed octave.
`x` and `y` are image pixels in the OpenCV-decoded image with top-left origin.
`size` is the neighborhood diameter in pixels.
Orientation is in degrees in `[0,360)`.
Keypoint record `i` corresponds exactly to descriptor row `i`.
## Feature File v1 validation
The reader validates:
- magic;
- format version;
- reserved-zero bytes;
- descriptor type;
- descriptor dimension;
- count bounds;
- offset bounds;
- exact total size;
- checked multiplication;
- external SHA-256;
- Project DB metadata consistency.
A future format version is distinguished from corruption when both file and DB record are otherwise
coherent.
The reader uses `pread`, reads at most 256 features per call and does not require loading the entire
Feature File.
The Project DB path must equal the canonical path derived from the stored SHA-256 before the file is
opened.
Outcomes include:
- missing file -> `NOT_FOUND`;
- truncation -> `CORRUPT`;
- hash mismatch -> `CORRUPT`;
- header/DB disagreement -> `CORRUPT`;
- coherent unsupported future version -> `UNSUPPORTED_VERSION`.
## Publication and persistence
Physical layout:
```text
assets/features/<first-2-hex>/<full-lowercase-sha256>
```
The SHA-256 covers the complete Feature File.
The publication protocol is:
```text
local temporary file
-> write
-> fsync file
-> hash complete Feature File
-> atomic no-overwrite link/publication
-> fully validate any concurrently existing identical asset
-> fsync parent directory
-> SQLite transaction
-> logical READY state
```
If the final parent-directory `fsync` fails after publication, durability is recorded as
`PUBLISHED_NOT_DURABLE`.
If SQLite fails after the physical file is published, the valid file remains an orphan and no partial
logical Feature Set row is invented.
A later exact retry may revalidate the physical asset, complete a successful directory `fsync` and
promote durability to `DURABLE` in the Project DB transaction.
## Historical Project DB Feature foundation
The Feature Store persistence foundation separates:
- `feature_assets`;
- `feature_sets`;
- `feature_extract_tasks`.
Logical uniqueness is based on image, extractor kind, extractor version and parameter fingerprint.
The historical `features.extract/1` Task is persisted before enqueue, admitted by Queue/Governor,
executes one complete image and checkpoints at its durable boundaries.
Its historical conservative execution shape includes:
```text
one image
one atomic ORB extraction/publication
no intra-image restart
```
The existing conservative single-image resource descriptor remains valid for those Tasks and their
restart compatibility. It must not be generalized into a rule requiring all future independent images
to execute serially.
Pause and cancellation are cooperative before and after the OpenCV call. The OpenCV extraction itself
is not interruptible.
The managed source image is rehashed before extraction.
A uniform image may legitimately publish a READY Feature Set with zero features.
## Selected-execution Feature batch — Project DB v25
Project DB v25 adds only:
```text
feature_extract_batch_tasks
```
and the durable Task Kind:
```text
features.extract.batch/1
```
The v24 -> v25 migration is transactional, additive and DDL-only.
It does not:
- create Feature Sets;
- convert historical `features.extract/1` Tasks;
- infer an `image_id`;
- infer a Feature Set;
- change ORB v1 parameters;
- change Feature File v1/v2;
- change Feature Set identity.
### Durable owner identity
One `features.extract.batch/1` owner is bound to:
- one immutable selected execution;
- one monotonic `next_item_index`;
- the exact ORB extractor kind;
- the exact ORB extractor version;
- the exact ORB parameters;
- the exact ORB parameter fingerprint.
The selected execution order is authoritative.
### Execution shape
The current operational path is:
```text
one admitted owner Task
-> bounded selected-item window
-> bounded independent image participants
-> each participant prepares one per-image Feature result
-> participants perform no SQLite publication
-> join
-> owner validates or reuses the exact READY Feature Set
-> owner publishes in selected-item order
-> owner advances the durable Feature cursor
-> generic Task progress/checkpoint follows
```
Each participant therefore computes an ordinary per-image scientific Feature result.
The owner-only publication stage preserves deterministic selected-item order without requiring
independent image preparation to run serially.
### Atomicity and concurrency
The scientific atomic result remains one Feature Set for one image.
```text
PER_IMAGE_FEATURE_RESULT=ATOMIC
PER_IMAGE_ATOMICITY_REQUIRES_CROSS_IMAGE_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
```
Cross-image preparation may be concurrent when the Governor admits a useful bounded window.
This is not a generic DAG scheduler and does not add a second global worker pool. The Task Queue still
owns one active callback; the batch callback contains the bounded participant work.
### Crash and restart
An immutable Feature Set can become durable before the Feature-batch cursor or generic Task checkpoint
advances.
Therefore, after a crash:
```text
durable Feature Set may be ahead of Task checkpoint
```
but:
```text
Task checkpoint must never invent a Feature Set that is not durable
```
Restart revalidates and reuses the exact READY Feature Set and then converges the cursor. It does not
derive identity from path, processing order or Task-local position.
### Resource adaptation
For the selected-execution Feature batch, increasing admitted CPU while the item window remains one
cannot exercise additional independent images.
The current validated resource model may therefore couple CPU and batch growth where required by the
Feature-batch rung contract.
That behavior is Task-specific operational policy, not a universal Governor rule.
The canonical repository policy remains:
```text
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
```
Preserve the interactive host reserve and all RAM/CPU/IO/GPU safety constraints first. Within that
safe envelope, use the maximum useful validated execution width.
Per-item atomicity is not evidence that unrelated items must be serialized.
## GPU boundary
Feature Extraction currently remains CPU.
The current production GPU audit classifies Feature Extraction as:
```text
FEATURE_GPU=REJECTED_WITH_MEASURED_OR_IMPLEMENTATION_EVIDENCE
```
On the validated OpenCV 5.0.0 host:
- no usable ORB/SIFT Vulkan/OpenCL extraction seam is validated;
- CUDA is unavailable in the installed OpenCV build;
- no GPU path has proven byte-identical Feature Files for this boundary.
Therefore Feature Extraction receives no production GPU path merely because the host has a GPU.
This does not authorize avoidable CPU serialism. Cross-image CPU parallelism remains valid where its
Task contract has been proven.
## Environment and reproducibility
Lardon3D does not claim ORB Feature File bytes are identical across arbitrary:
- OpenCV versions;
- platforms;
- extractor backends.
Idempotence is defined inside the supported environment and extractor-version contract.
Any change that modifies durable Feature semantics or bytes requires explicit review and, when
necessary, an extractor-version change. It must not silently reuse the old scientific identity.
## Feature File v2 and multi-descriptor support
**IMPLEMENTED v1A.**
ORB continues to write the historical Feature File v1 U8x32 representation.
SIFT and RootSIFT write Feature File v2 F32x128.
### v2 header
Feature File v2 has a 176-byte header.
Its fields include:
- magic;
- explicit version;
- header size;
- feature count;
- descriptor dimension;
- descriptor type;
- scalar size;
- keypoint-record size;
- decoded image dimensions;
- capabilities;
- keypoint/descriptor block offsets;
- total size;
- source SHA-256 at offset 72;
- parameter fingerprint at offset 104;
- 16-byte extractor kind at offset 136;
- extractor version at offset 152;
- twenty reserved zero bytes.
Stable descriptor type values are:
```text
U8 = 1
F32 = 2
```
with scalar sizes 1 and 4.
Every binary32 value is explicitly encoded little-endian; persistent output is not a C/C++ struct
dump.
Writer and reader reject NaN and Inf.
The typed descriptor readers:
- use `pread`;
- refuse incompatible descriptor type;
- return at most 256 features per call.
File-size limits remain:
```text
Feature File v1 16 MiB
Feature File v2 64 MiB
```
The extraction facade also carries `descriptor_bytes`.
Publication requires exactly:
```text
feature_count * descriptor_dimension * scalar_size
```
and rejects both truncated and oversized descriptor buffers before file creation/publication.
The detailed multipass, grid and RootSIFT scientific contracts remain in
[`precision_feature_pipeline.md`](precision_feature_pipeline.md).
## Downstream consumers
The Feature Store currently feeds implemented downstream stages:
```text
Feature Store
|
v
Visual Index
|
v
Candidate Pair
|
v
Matcher
|
v
Geometric Verification
|
v
Tracks
```
Visual Index consumes the bounded Feature reader without changing Feature File format.
Candidate Pair consumes Feature Set identities indirectly through Visual Index membership.
Matcher consumes persisted Feature Sets and their descriptors.
Geometric Verification and Tracks consume later correspondence products; they do not rewrite Feature
Store identity.
Sparse SfM Gates C through G are implemented and PASS/FROZEN, but real known-calibration Sparse SfM
was not executed in the retained historical S21/A6000 campaigns.
## Real A6000 evidence
The retained real A6000 selected execution contains:
```text
Feature Sets 689
Candidate Pairs 38,420
Match Results 38,420
```
The v25 selected Feature-batch path completed all selected images.
The retained checkpoint is:
```text
real-a6000-pre-sfm-2026-09-02
REAL_A6000_PRE_SFM=PASS/FROZEN
```
The later GV/Tracks continuation required:
```text
Feature replay 0
Candidate replay 0
Matcher replay 0
```
and continued from retained immutable upstream products.
This proves the current Feature-batch operational path and restart/reuse boundary on that campaign.
It does not create calibration for that campaign and does not constitute real Sparse SfM execution.
## Current unfinished work
The following remain separate from the Feature Store scientific contract:
- global orphan-file reconciliation/scrub;
- EXIF orientation handling where still required;
- generic dependency/DAG scheduling;
- future extractor/backend work only after explicit equivalence/version review.
Multi-image Feature execution itself is no longer an unfinished item:
`features.extract.batch/1` is implemented and validated.
## Summary
```text
CURRENT_PROJECT_DB_SCHEMA=v25
FEATURE_STORE_STATUS=IMPLEMENTED
FEATURE_FILE_V1=FROZEN
FEATURE_FILE_V2=IMPLEMENTED
HISTORICAL_FEATURE_TASK=features.extract/1
CURRENT_FEATURE_BATCH_TASK=features.extract.batch/1
PROJECT_DB_V25_FEATURE_BATCH=IMPLEMENTED/VALIDATED
FEATURE_RESULT_IDENTITY=PER_IMAGE
PER_IMAGE_FEATURE_RESULT=ATOMIC
PER_IMAGE_ATOMICITY_REQUIRES_CROSS_IMAGE_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
FEATURE_GPU=REJECTED_WITH_MEASURED_OR_IMPLEMENTATION_EVIDENCE
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
REAL_A6000_FEATURE_SETS=689
REAL_A6000_PRE_SFM=PASS/FROZEN
```