This document is the top-level map of the framework: the subsystems, the contracts between them,
and the build order. Module-level documents (e.g. core-engine.md) go deeper.
- The core is task-agnostic.
lofop.coreknows nothing about vision. It provides the mechanisms every subsystem shares -- registries, configuration, events, plugins, logging, exceptions -- and stays importable in the leanest environments (an edge device running inference should not import training code). - Components over inheritance. Capabilities are added by registering components into named groups, not by subclassing framework internals. A new task (say, pose estimation) is a set of registrations plus configs -- no core changes.
- Configs are data. YAML in, YAML out. Anything executable belongs in a registered component. This keeps experiments diffable, auditable, and safe to ship through CI/CD systems that treat configuration as artifacts.
- Explicit wiring beats magic. Cross-group component references are visibly qualified
(
type: loss/FocalCost); nested dicts are never instantiated by surprise; event topics are plain strings owned by their emitters. - Isolation is first-class. The default hub/bus/plugin-manager are module-level conveniences; every piece of core machinery can be instantiated privately (tests, embedded apps, multi-tenant servers).
| Phase | Package | Contents | Status |
|---|---|---|---|
| 1 | lofop.core |
Registry, Config, EventBus, PluginManager, logging, exceptions | Done |
| 1 | lofop.registries |
Default hub + standard component groups | Done |
| 2 | lofop.data |
Canonical dataset model, COCO/YOLO/VOC adapters, converter, validator, statistics | Done |
| 2b | lofop.data |
Torch data bridge + augmentation (flip; opt-in mosaic + color jitter); letterboxing/caching planned | Done (letterbox planned) |
| 3 | lofop.models |
LOFOP-Detect: RidgeNet, DeltaFusion, ApexHead, losses, dynamic assignment | Done (see docs/lofop-detect.md) |
| 3b | lofop.models |
Task variants on the same detector: LofopSegment (StencilHead prototype masks), LofopPose (VertexHead keypoints) | Done (ONNX export planned) |
| 4 | lofop.training |
Trainer (AMP, EMA, cosine schedule, resume), COCO-protocol evaluator, checkpoints, torch data bridge | Done (DDP path present, not CI-exercised) |
| 5 | lofop.inference |
Image/video/stream predictors, batching, RTSP/webcam sources | Planned |
| 6 | lofop.deploy |
ONNX (fixed + dynamic shapes, verified) + TensorRT export, torch-free post-processing; OpenVINO/REST planned | Done (OpenVINO/REST planned) |
| 7 | lofop.cli |
lofop CLI: dataset (convert/validate/stats/show), train, benchmark, predict, evaluate, export, doctor |
Done |
| 8 | lofop.utils |
Model benchmarking: metric table, FLOPs, FPS, size, CSV/JSON export; dataset visualization lives in lofop.data |
Done |
| -- | CI / packaging | GitHub Actions CI (lint + tests + build) and PyPI/AUR packaging | Done |
| -- | lofop.sdk |
High-level Python SDK: the Detector class (build/train/predict/export) |
Done (see docs/sdk.md) |
| -- | lofop.ops |
Three-tier native box ops (IoU, NMS, Soft-NMS, dense decode): Python fallback, C++ fast path (MSVC/MinGW/g++/clang), optional CUDA tier (nvcc) | Done |
Each phase lands with its own tests and docs before the next begins. Dependencies point downward
only: data/models/training depend on core, never on each other's internals; interaction
happens through registries, configs, and events.
- A
Registryis a flat, string-keyed namespace of component factories for one group (backbone,loss, ...). ARegistryHubowns the groups. Registry.build(spec)instantiatesspec["type"]with the remaining keys as kwargs.- Qualified names (
"group/Name") resolve across groups through the hub, and nested specs are recursively built only when qualified. Unqualified nested mappings pass through as plain data. This rule is what keeps config semantics predictable: you can always tell from the YAML alone what will be instantiated. - Trade-off vs. alternatives: a single global namespace collides at scale; deeply nested hierarchical scopes with inheritance make lookup rules hard to predict. Flat groups + explicit qualification is the middle: one extra token in configs buys unambiguous resolution.
Configwraps nested mappings with attribute access, dottedselect()/update_path(), deepmerge(), and recursivefreeze().Config.load()appliesextends:inheritance (parents deep-merged in order, child on top) and then interpolation:${a.b.c}references config values (type-preserving when the reference is the whole string),${env:VAR:default}reads the environment.- Trade-off: Python-file configs are more expressive but not auditable as data; pure YAML without inheritance/interpolation breeds copy-paste. This design keeps YAML as the source format while removing the duplication that makes YAML painful.
EventBusis synchronous, priority-ordered publish/subscribe over dotted string topics.- Handler failures are isolated by default (logged + returned) so one bad subscriber cannot kill a
training run; emitters that need strictness use
raise_errors=True. - This is the seam where training hooks, experiment trackers, and plugins attach without the trainer knowing about them.
- A plugin is a callable receiving a
PluginContext(hub, events). Distribution is either via thelofop.pluginsentry-point group (discovered from installed packages, loaded lazily) or programmaticadd(). - Failures wrap in
PluginErrorwith the plugin name and source attached; a failed activation never marks the plugin active.
- Every framework error derives from
LofopErrorand can carry structuredcontextdata; each subsystem has its own subclass (ConfigError,RegistryError,BuildError,PluginError,EventError). - Logging namespaces under the
"lofop"root logger.configure_logging()is an opt-in convenience (console + optional file, Rich when available); embedding applications keep full control by simply not calling it.
The flagship detector (working name: LOFOP detector) will be assembled from the standard groups rather than existing as a monolith:
- an anchor-free, multi-scale prediction scheme over a feature pyramid with dynamic fusion,
- lightweight attention in the neck where it pays for itself on edge hardware,
- dynamic label assignment during training,
- every block registered (
backbone/...,neck/...,head/...,loss/...) so variants are config edits, not code forks.
Design rationale for each block lands with the Phase 3 documentation.