Layer service helpers
Source: src/app/application/services/helpers
Small, stateless functions shared by the detection and tracking layer services. They hold the rules that must be identical for layers A/B and C/D: how a reviewed layer is seeded, how loose inputs become enums, how boxes become files, and how imported boxes merge into a layer. For the workflow they serve, read Layer services first.
Annotation data only
These helpers handle boxes in layers. They do not save projects or render video; see Session and project services and Export services.
Where this sits
Helpers are the second-to-last box: plain functions called by the services, with no Qt, no workers and no UI dependency. The big picture shows the full stack.
Worked example: a frame is opened and seeded
When the user steps to frame 40 in the Tracking tab, the controller asks for
that tab's boxes. UnifiedLayerService.get_tab_frame_boxes_for_session_id
calls seed_layer_frame_from_layer(C, D, session, 40):
- D has no entry for frame 40, and C has two boxes.
- One box is below the minimum tracker confidence, so one clone is stored in D.
- The service returns D's boxes; the UI table and overlay show that single track.
Opening frame 40 again finds the entry and changes nothing, so edits survive. The decision flow is drawn in Seeding; the import equivalent, with a sequence diagram, is in Layer services.
Current limitations
- Seeding reads thresholds from settings at that moment; frames seeded earlier are not changed when settings change.
ensure_layer_enumaccepts any valid layer; supported-layer checks live in each service.MERGE_RENAMEdoes not deduplicate collisions within one file.
What each helper does
| Module | Public functions / types | Used by |
|---|---|---|
layer_seeding |
seed_layer_frame_from_layer |
UnifiedLayerService, TrackingLayerService |
import_mode |
ImportMode |
Import dialog, Application, import services |
layer_coercion |
ensure_layer_enum, ensure_import_mode |
Application, import/export services, handler |
layer_io |
serialize_layer_to_json/csv, deserialize_layer_from_json/csv, apply_import_to_layer |
Import and export services |
Helpers take a session or store as an argument and keep no state of their own.
Their log output uses the Application->LayerSeeding area (seeding) and the callers' areas.
seed_layer_frame_from_layer(source, target, session, frame_index)
Initialises one frame of an editable layer (B from A, D from C):
- Acquire the session data lock (reentrant, so callers already holding it are fine).
- If the target frame already has an entry (even an empty list), stop.
- If the source frame has no boxes, stop; nothing is created.
- Otherwise clone each source box that passes
new_passes_filterfor the target layer and store the list in the target.
Cloning keeps edits in B/D from mutating A/C. The filter thresholds come from the session settings at the moment of seeding. Later changes to those settings do not rewrite already-seeded frames; use the unified service's re-filter or reset instead.
ImportMode
A str enum whose values (overwrite, merge_replace, merge_ignore,
merge_rename) double as labels in the dialog through display_label. Behaviour of
each mode is shown with an example in
Import modes.
Coercion
Qt can deliver enums as plain strings (for example through QComboBox.currentData()
or signal arguments). The two ensure_* functions make that harmless:
from app.application.services.helpers.layer_coercion import ensure_layer_enum
ensure_layer_enum("b") # VideoDataLayer.B
ensure_layer_enum("B") # VideoDataLayer.B
ensure_layer_enum("x") # ValueError
Resolution order is the enum itself, then the lower-cased value, then a
case-insensitive name. Unknown input raises ValueError rather than being
guessed. They do not check which layers a service supports; that is the
service's job.
JSON and CSV I/O (layer_io)
| Function | Contract |
|---|---|
serialize_layer_to_json(name, data, path) |
Writes {meta, frames}; skips empty frames; returns the box count |
serialize_layer_to_csv(name, data, path) |
One row per box under a fixed header; name is unused and kept for symmetry |
deserialize_layer_from_json(path) |
Reads only frames; returns {frame_index: [boxes]} |
deserialize_layer_from_csv(path) |
Same result shape; empty confidence becomes None |
apply_import_to_layer(store, layer, incoming, mode) |
Applies the mode to the store and returns boxes written |
Serialisers create missing parent directories and overwrite existing files. JSON
boxes go through box_to_dict / dict_to_box in app.shared.box_io; CSV
rows are built directly. Field layouts and examples are in
File formats.
apply_import_to_layer is the only place the four modes are implemented.
OVERWRITE replaces the layer through the store, MERGE_REPLACE and
MERGE_IGNORE assign whole frame lists, and MERGE_RENAME appends clones
(adding imp- to key and id on collision). Imported boxes are stored as
parsed for the replace modes and cloned for rename.
Failure semantics
Deserialisers complete before any layer is touched, so a malformed file
raises (json.JSONDecodeError, KeyError, or ValueError for an unknown
source) and leaves the layer unchanged. Merging itself is not
transactional: an error part-way through OVERWRITE can leave the layer
cleared or partially loaded. Helpers raise; logging and dialogs belong to
the services and handler (see failure paths).
See the generated API reference for signatures.