Engine and tracking worker managers
Source: src/app/application/managers
Two managers own heavyweight, per-session objects: the detection engine (a loaded model) and the tracking worker (a background thread that links detections into tracks). Parent overview: Application managers.
Where this sits
Here the "service" is DetectionService / TrackingService; the
infrastructure side is DetectionEngine, TrackingWorker and the session's
layer store.
Detection engine reuse and replacement
Solid arrows are the create/update decision; dashed arrows are failure and removal paths.
create_or_update(s_id, model_name): the first call creates an engine withDetectionEngineAdapterFactory; later calls keep the same engine object and callset_model, which reloads the model inside it.remove(s_id): setssession.detection_engine = None. This is whatDetectionServicedoes when the model name is"None".- The engine is also created lazily: single-frame detection calls
create_or_updatewhensession.detection_engineisNone.
Ordering is the caller's job
DetectionService.set_detection_model_for_session_id stops and clears the
session's detection worker, resets the model's layers, and only then
updates or removes the engine. The manager itself does not stop workers.
Tracking worker lifecycle
Solid arrows are the happy path; dashed boxes are exceptions raised to the service and then to the handler, which shows the dialog.
- Guard. A session with a running tracker cannot start another.
- Source layer. Only A or B are accepted. For B, any frame missing from B is first seeded from A using cloned boxes that pass the B filter; frames already in B (including manual edits) are left alone.
- Empty check. An empty source raises
EmptyLayerException. - Clear C and D.
session.data.clear_layers_by_name([C, D])removes the previous tracking output and the reviewed tracks, so earlier D edits are discarded on every run. - Create. The factory builds a
TrackingWorkerwithstart=True. The worker deep-copies the source boxes and the settings at construction, so later edits to A/B or to settings do not affect a running job.
Stop and read
stop(s_id)callsworker.stop(), then clearssession.tracking_worker.stop()only sets a flag that the worker checks once per frame and before post-processing; the thread is not joined here. AValueErroris logged and the worker reference is kept.get_tracked_data(s_id)returns the worker's results as a deep copy made under the worker's mutex, so the GUI thread never sees a half-written result. It raisesTrackingWorkerNotFoundExceptionwhen there is no worker. The manager does not write layer C;TrackingService.sync_tracking_cachedoes, and seeds only uninitialized D frames.
Worked example: running tracking
Arrows between the handler and the worker are Qt signals delivered to the GUI thread; the others are ordinary calls.
TrackingHandlervalidates the UI, shows the loading state, and requests the start through the Application.- The manager seeds B if needed, clears C/D, and starts the worker.
- The handler connects
progress_updated,finished_processinganderror_occurredafter the start call returns. - When the worker finishes,
_on_tracking_finishedcallssync_tracking_cache, which reads the copy through the manager and writes C (and seeds D). The handler then selects the tracking tab and re-renders.
Failure and cleanup paths
| Where | Current behaviour |
|---|---|
| Start rejected (running, layer, empty) | Exception to the handler; layers untouched |
| Worker factory raises (e.g. missing optional tracker dependency) | Exception to the handler; C/D are already cleared; no worker attached |
| Error inside the worker thread | Logged with traceback; error_occurred then finished_processing are emitted |
Session.close() |
Calls tracking_worker.stop() (flag only) |
Limitations and discrepancies
Clear-before-create
C and D are cleared before the worker is created. If creation fails, the old tracks are already gone.
Finished signal also fires after errors and stops
The worker's finally block always emits finished_processing, and the
handler's finished slot does not check is_complete(). After an error or
a stop, sync_tracking_cache therefore reads an empty result and overwrites
C with nothing.
Stopping is cooperative
TrackingWorker.stop() only sets a flag; the manager does not wait for the
thread. TrackingWorkerManager.switch_active_session exists but is not
called by the services in the code searched; tracking is not stopped
automatically on session switch.
A failed model switch is not atomic
DetectionEngine.set_model records the new name before loading, so a
failed reload leaves the previous model active under the new name. The
manager does not roll back and does not coordinate with in-flight
inference.