본문으로 건너뛰기
SDK Version: 2.3.3

DX-APP Example Source Structure

# DX-APP Example Source Structure

This guide is intended for contributors and maintainers who need to understand or extend the refactored DX-APP example layout.


Overview

The src/ directory is the implementation root for DX-APP examples and shared runtime logic.

Its structure is designed to:

  • separate C++ and Python example implementations clearly
  • organize examples by AI task and then by model family
  • share post-processing logic across languages
  • make onboarding of new models predictable
  • support testing, validation, and benchmarking workflows

Directory Hierarchy & Organization

Top-Level Layout Strategy

src/
├── cpp_example/ # C++ examples organized by task/model
│ └── common/ # ← C++ shared runtime layer
├── python_example/ # Python examples organized by task/model
│ └── common/ # ← Python shared runtime layer
├── postprocess/ # C++ post-processing (consumed by pybind11 bindings)
├── bindings/ # pybind11 bridge exposing src/postprocess/ to Python
└── utility/ # Shared utility code used by build flow
  • src/cpp_example/: end-to-end C++ example applications — each model directory contains thin entry-point files that delegate to the shared common/ layer
  • src/cpp_example/common/: shared C++ runtime layer — base interfaces (4 hpp), processors (45 hpp), runners (24 hpp), visualizers (12 hpp), input sources (5 hpp), config (1 hpp), and utilities (8 hpp)
  • src/python_example/: end-to-end Python example applications — same factory-based delegation pattern as C++
  • src/python_example/common/: shared Python runtime layer — base interfaces (4 py), processors (35 py), runners (5 py), visualizers (10 py), input sources (5 py), config (1 py), and utilities (7 py)
  • src/postprocess/: C++ post-processing libraries consumed by the pybind11 bindings (src/bindings/). This enables *_cpp_postprocess variants to use C++ decode logic from Python
  • src/bindings/: pybind11 bridge exposing src/postprocess/ to Python as the dx_postprocess package
  • src/utility/: common support code shared across the build flow

Both cpp_example/common/ and python_example/common/ share the same 7-module structure: base/, config/, processors/, runner/, inputs/, visualizers/, utility/. They are language-specific implementations of the same architectural pattern.

Task-First Organization

Both cpp_example/ and python_example/ follow the same task-first structure.

Representative task directories include:

  • classification/
  • object_detection/
  • face_detection/
  • pose_estimation/
  • semantic_segmentation/
  • instance_segmentation/
  • depth_estimation/
  • embedding/
  • image_denoising/
  • image_enhancement/
  • super_resolution/
  • obb_detection/
  • ppu/
  • hand_landmark/
  • attribute_recognition/
  • reid/
  • face_alignment/

This makes it easy to answer both questions below:

  • what task does this example belong to?
  • where should a new model example be added?

Model-First Subdirectories

Within each task directory, examples are split again by model family.

Examples:

src/cpp_example/object_detection/yolov9s/
src/cpp_example/object_detection/yolov8/
src/python_example/object_detection/yolov9s/
src/python_example/object_detection/ssdmv1/

This gives each model family its own isolated workspace for:

  • config files
  • source files
  • factory helpers
  • model-specific wiring

Language-Specific Structure & Variants

Common Runtime Layer (common/)

Shared Runtime Layer (common/)

Both src/cpp_example/common/ and src/python_example/common/ implement the same shared runtime architecture. Each provides base interfaces, configuration loading, input sources, processors, runners, visualizers, and utilities — so individual model directories only need thin entry points and a factory.

C++ Common Structure

src/cpp_example/common/
├── base/ # Abstract interfaces (.hpp)
│ ├── i_factory.hpp # IFactory — assembles processor + visualizer + runner
│ ├── i_processor.hpp # IProcessor — postprocess(outputs, meta) → results
│ ├── i_visualizer.hpp # IVisualizer — draw(frame, results) → frame
│ └── i_input_source.hpp # IInputSource — image/video/camera/RTSP abstraction
├── config/
│ └── model_config.hpp # ModelConfig — loads config.json
├── processors/ # 45 shared processors
│ ├── yolov5_postprocessor.hpp
│ ├── yolov8_postprocessor.hpp
│ ├── scrfd_postprocessor.hpp
│ ├── nanodet_postprocessor.hpp
│ ├── damoyolo_postprocessor.hpp
│ ├── ssd_postprocessor.hpp
│ ├── segmentation_postprocessor.hpp
│ ├── instance_seg_postprocessor.hpp
│ ├── depth_postprocessor.hpp
│ └── ...
├── runner/ # 24 task-specific runner pairs
│ ├── sync_detection_runner.hpp # Sequential detection loop
│ ├── async_detection_runner.hpp # Pipelined detection loop
│ ├── sync_classification_runner.hpp
│ ├── async_classification_runner.hpp
│ ├── sync_segmentation_runner.hpp
│ ├── async_segmentation_runner.hpp
│ └── ... # 12 sync + 12 async = 24 total
├── inputs/ # 5 input source headers
│ ├── camera_source.hpp
│ ├── image_source.hpp
│ ├── video_source.hpp
│ ├── rtsp_source.hpp
│ └── input_factory.hpp
├── visualizers/ # 12 task-specific visualizers
│ ├── detection_visualizer.hpp
│ ├── classification_visualizer.hpp
│ ├── segmentation_visualizer.hpp
│ ├── face_visualizer.hpp
│ ├── pose_visualizer.hpp
│ └── ...
├── utility/ # 8 utility headers
│ ├── common_util.hpp
│ ├── labels.hpp
│ ├── preprocessing.hpp
│ ├── profiling.hpp
│ ├── run_dir.hpp
│ ├── safe_queue.hpp
│ ├── verify_serialize.hpp
│ └── visualization.hpp
└── third_party/ # Third-party header-only libraries
└── nlohmann_json.hpp # JSON for Modern C++

C++ runners are task-specific: each task type has a dedicated sync/async runner pair (e.g., sync_detection_runner, async_detection_runner). This provides optimal performance for each task's specific data flow.

Python Common Structure

src/python_example/common/
├── base/ # Abstract interfaces (.py)
│ ├── i_factory.py # IFactory — assembles processor + visualizer + runner
│ ├── i_processor.py # IProcessor — postprocess(outputs, meta) → results
│ ├── i_visualizer.py # IVisualizer — draw(frame, results) → frame
│ └── i_input_source.py # IInputSource — image/video/camera/RTSP abstraction
├── config/
│ └── model_config.py # ModelConfig — loads config.json (input size, labels, thresholds)
├── processors/ # 35 shared processors
│ ├── yolo_postprocessor.py # YOLOv5/v7/v8/v9/v10/v11/v12/YOLOX
│ ├── face_postprocessor.py # SCRFD, YOLOv5Face, YOLOv7Face
│ ├── segmentation_postprocessor.py # BiSeNet, DeepLabV3+, SegFormer
│ ├── instance_seg_postprocessor.py # YOLOv8Seg, YOLOv26Seg
│ ├── obb_postprocessor.py # YOLOv26OBB
│ ├── pose_postprocessor.py # YOLOv8-Pose
│ ├── ppu_postprocessor.py # PPU variants (YOLOv5/v7/SCRFD/Pose)
│ ├── classification_postprocessor.py # EfficientNet, AlexNet, etc.
│ ├── depth_postprocessor.py # FastDepth, MiDaS
│ ├── nanodet_postprocessor.py # NanoDet
│ ├── ssd_postprocessor.py # SSD MobileNet
│ ├── damoyolo_postprocessor.py # DAMOYOLO
│ ├── embedding_postprocessor.py # CLIP, ArcFace
│ ├── restoration_postprocessor.py # DnCNN, Zero-DCE
│ ├── nms_utils.py # Shared NMS / box utilities
│ ├── letterbox_preprocessor.py # Shared letterbox preprocessing
│ └── ...
├── runner/ # 5 generic runner files
│ ├── sync_runner.py # SyncRunner — sequential Pre→Infer→Post→Display loop
│ ├── async_runner.py # AsyncRunner — pipelined multi-thread runner
│ ├── args.py # Unified CLI argument parser (--model, --image, --video, etc.)
│ ├── run_dir.py # Directory-based batch runner
│ └── verify_serialize.py # Serialize results to JSON for numerical verification
├── inputs/ # 5 input source files
│ ├── image_source.py
│ ├── video_source.py
│ ├── camera_source.py
│ ├── rtsp_source.py
│ └── input_factory.py
├── visualizers/ # 10 task-specific visualizers
│ ├── detection_visualizer.py
│ ├── classification_visualizer.py
│ ├── segmentation_visualizer.py
│ ├── face_visualizer.py
│ ├── pose_visualizer.py
│ ├── instance_seg_visualizer.py
│ ├── obb_visualizer.py
│ └── ...
└── utility/ # 7 utility files
├── common_util.py # General utilities
├── labels.py # COCO / ImageNet label constants
├── preprocessing.py # Shared resize/normalize/letterbox
├── profiling.py # Stage-wise latency profiler
├── safe_queue.py # Thread-safe queue for async pipeline
├── skeleton.py # Pose skeleton definitions
└── visualization.py # Drawing helpers (boxes, text, masks)

Python runners are generic: SyncRunner and AsyncRunner work for all task types via the factory pattern. This provides simplicity and uniform usage across all models.

Key Difference: C++ vs Python Runners

AspectC++ (cpp_example/common/runner/)Python (python_example/common/runner/)
Runner count24 (12 sync + 12 async)5 (2 runners + args + run_dir + verify)
DispatchTask-specific runner per categoryGeneric runner for all tasks
Examplesync_detection_runner.hppsync_runner.py

How Model Directories Connect to common/

C++ model directory

src/cpp_example/object_detection/yolov9s/
├── config.json
├── factory/
│ └── yolov9s_factory.hpp # Assembles processor + visualizer from common/
├── yolov9s_sync.cpp # Entry point → sync_detection_runner
└── yolov9s_async.cpp # Entry point → async_detection_runner

Python model directory

src/python_example/object_detection/yolov9s/
├── config.json
├── factory/
│ └── yolov9s_factory.py # Assembles processor + visualizer from common/
├── yolov9s_sync.py # Entry point → SyncRunner
├── yolov9s_async.py # Entry point → AsyncRunner
├── yolov9s_sync_cpp_postprocess.py # Entry point → SyncRunner + C++ binding
└── yolov9s_async_cpp_postprocess.py # Entry point → AsyncRunner + C++ binding

The factory imports shared components from common/:

// C++ factory example
#include "common/processors/yolov8_postprocessor.hpp"
#include "common/visualizers/detection_visualizer.hpp"
# Python factory example
from common.processors import YOLOv5Postprocessor
from common.visualizers import DetectionVisualizer

The entry-point script/program delegates to the runner:

# Python
from common.runner import SyncRunner
runner = SyncRunner(factory)
runner.run()

This means adding a new model typically requires only a config.json and a factory file — the shared infrastructure handles everything else.

Variant Naming Rules

C++ variants

A typical C++ model directory contains:

config.json
factory/
<model>_sync.cpp
<model>_async.cpp

Example:

src/cpp_example/object_detection/yolov9s/
├── config.json
├── factory/
├── yolov9s_sync.cpp
└── yolov9s_async.cpp

Common C++ variant patterns:

  • *_sync.cpp: sequential execution path
  • *_async.cpp: pipelined or threaded execution path
  • task/model-specific additional variants when required

Python variants

A typical Python model directory contains:

config.json
factory/
<model>_sync.py
<model>_async.py
<model>_sync_cpp_postprocess.py
<model>_async_cpp_postprocess.py

Example:

src/python_example/object_detection/yolov9s/
├── config.json
├── factory/
├── yolov9s_sync.py
├── yolov9s_async.py
├── yolov9s_sync_cpp_postprocess.py
└── yolov9s_async_cpp_postprocess.py

Common Python variant patterns:

  • *_sync.py: Python-only synchronous path
  • *_async.py: Python-only asynchronous path
  • *_sync_cpp_postprocess.py: synchronous path using shared C++ post-processing bindings
  • *_async_cpp_postprocess.py: asynchronous path using shared C++ post-processing bindings

Cross-Language Integration & Frameworks

Shared Post-Processing Layer

C++ and Python Shared Runtime (common/)

Both cpp_example/ and python_example/ contain a common/ directory with the same 7-module architecture:

ModuleC++PythonRole
base/4 interfaces (.hpp)4 interfaces (.py)IFactory, IProcessor,
IVisualizer, IInputSource
config/model_config.hppmodel_config.pyLoads config.json
(input size, labels, thresholds)
processors/45 header files35 Python filesShared processors for
all model families
runner/24 runner headers5 runner filesSync/Async execution engines
with profiling
inputs/5 source headers5 source filesImage, Video, Camera,
RTSP input abstraction
visualizers/12 visualizer headers10 visualizer filesTask-specific result rendering
utility/8 utility headers7 utility filesLabels, preprocessing,
profiling, drawing, run_dir,
queue, verify

This is the core architectural pattern of DX-APP: model directories are thin wrappers (factory + entry points) that delegate all heavy logic to their respective common/ layer.

Pybind11 Post-Processing Bridge (src/postprocess/ + src/bindings/)

src/postprocess/ contains C++ post-processing implementations that are not used by cpp_example/common/processors/ directly. Instead, they are consumed by the pybind11 bindings under src/bindings/python/dx_postprocess/.

This bridge enables the *_cpp_postprocess.py Python variants to use C++ decode logic for higher performance, while the pure Python variants (*_sync.py, *_async.py) use python_example/common/processors/ natively.

VariantPost-processing source
*_sync.cpp / *_async.cppcpp_example/common/processors/
*_sync.py / *_async.pypython_example/common/processors/
*_sync_cpp_postprocess.py / *_async_cpp_postprocess.pysrc/postprocess/ via pybind11

Numerical Verification Framework

DX-APP includes an automated verification pipeline that validates model outputs after inference:

  • scripts/validate_models.sh --numerical: runs all supported models through NPU inference and checks output correctness
  • scripts/verify_inference_output.py: task-specific validators (14 types) that check bounding boxes, class IDs, confidence ranges, segmentation masks, depth maps, etc.
  • scripts/inference_verify_rules.json: configurable thresholds per task type
  • common/runner/verify_serialize.py: serializes postprocess results to JSON for comparison

This framework catches regressions such as broken post-processing, incorrect model configurations, or NPU output changes.


Management & Extension

Model Registry & Automation

config/model_registry.json

The model registry is a JSON array that serves as the single source of truth for model metadata:

{
"model_name": "yolov9s",
"dxnn_file": "YoloV9S.dxnn",
"add_model_task": "object_detection",
"postprocessor": "yolov8",
"input_width": 640,
"input_height": 640,
"config": { "conf_threshold": 0.5, "num_classes": 80 },
"supported": true
}

scripts/add_model.sh

The add_model.sh script reads an entry from the registry and auto-generates:

  • config.json with correct input dimensions and thresholds
  • factory/{model}_factory.py wired to the correct processor and visualizer
  • 4 entry-point scripts (sync/async × python/cpp_postprocess)
  • C++ equivalents under src/cpp_example/

This enables onboarding a new model with zero manual code in most cases.

Contributor Rules

Adding New Examples

When adding a new example, keep the following rules:

  • (1) choose the correct task directory first
  • (2) create a dedicated model directory under that task
  • (3) follow existing variant naming conventions
  • (4) place shared decode logic in src/cpp_example/common/processors/ (C++) or src/python_example/common/processors/ (Python) when appropriate
  • (5) add pybind11 bindings in src/postprocess/ + src/bindings/ only if the new flow needs *_cpp_postprocess.py variants
  • (6) register the model in config/model_registry.json if it should be part of the standard validation flow
  • (7) update validation/test registration where required

Contributor checklist

When a new example is intended to become part of the maintained repository flow, confirm all of the following:

  • source files are placed under the correct task/model directory
  • naming follows the current variant convention
  • required shared post-processing logic exists or is added under src/cpp_example/common/processors/ (C++) or src/python_example/common/processors/ (Python)
  • model assets can be prepared through the standard setup flow
  • validation passes
  • test registration is updated if automated coverage is required
  • user-facing or contributor-facing docs are updated when the structure meaningfully changes

Supplementary Information

Connection to Testing

The source tree and the test tree are related, but not identical.

Shared Test Infrastructure (tests/common/)

tests/common/ provides shared constants and utilities used by both C++ and Python test suites:

  • constants.py — paths, timeout values, suffix patterns (e.g., _sync, _async)
  • utils.py — helper functions for executable/script discovery, process execution, result validation

C++ tests

C++ tests are organized into four categories:

CategoryFileDescription
CLI Helptest_cli_help.pyValidates --help output for all executables
CLI Basictest_cli_basic.pyValidates --model argument handling
E2Etest_e2e.pyEnd-to-end inference with image/video inputs
Visualizationtest_visualization.pyValidates output image generation with --save
Feature: Save Modetest_save_mode.pyTests --save / --save-dir behavior
Feature: Dump Tensorstest_dump_tensors.pyTests --dump-tensors output
Feature: Verifytest_verify.pyTests DXAPP_VERIFY environment variable
Feature: Multi-Looptest_multi_loop.pyTests --loop repeated execution
Feature: Signaltest_signal_handling.pyTests SIGINT/SIGTERM graceful shutdown

All tests auto-discover executables from bin/ using tests/common/utils.py.

Python tests

  • Python example tests are driven by centralized configuration under tests/python_example/framework/
  • test_visualization.py validates output image generation across all task types
  • adding a new source directory alone does not automatically guarantee full test coverage

Relevant files include:

  • tests/python_example/framework/config.py
  • tests/python_example/framework/performance_collector.py
  • tests/python_example/test_visualization.py
  • tests/python_example/<task>/ (14 task directories)
  • config/test_models.conf

Model validation

  • scripts/validate_models.sh runs registry-driven validation across all supported models
  • scripts/validate_models.sh --numerical additionally performs numerical verification using verify_inference_output.py
  • config/model_registry.json is the primary reference for which models are validated

This means source layout, test coverage, and registry entries must be updated together when onboarding new examples.

Developer Resources

Recommended Onboarding

When you add or refactor a model example:

  • (1) create or update the source layout under src/
  • (2) prepare the required model assets
  • (3) build the repository
  • (4) validate the example structure
  • (5) run relevant tests

Typical related commands:

./setup.sh
./build.sh --clean
./scripts/dx_tool.sh validate
./scripts/validate_models.sh --numerical --lang py
./run_tc.sh --cpp --cli
./run_tc.sh --python

If the example is intended to be part of the standard repository workflow, also review the relevant documentation pages so that user and contributor guidance stays aligned with the current structure.

See Also

  • src/cpp_example/
  • src/cpp_example/common/
  • src/python_example/
  • src/python_example/common/
  • src/postprocess/ (pybind11 source)
  • src/bindings/python/dx_postprocess/
  • config/model_registry.json
  • scripts/validate_models.sh
  • scripts/verify_inference_output.py
  • tests/cpp_example/
  • tests/python_example/
  • docs/10_DX-APP_DX-Tool_Guide.md