Add Applications
In Dayu, an application is usually added as a processor service. A processor service is composed of four parts:
- Python application code in
dependency/core/applications/<application_module>/ - A processor deployment template in
template/processor/<service-id>.yaml - A service entry in
template/services.yaml - A Docker image definition in
build/<image-name>.Dockerfile
The existing applications follow this pattern:
| Application type | Existing examples | Processor selected by PROCESSOR_NAME | Python objects exported by application package | Input | Output |
|---|---|---|---|---|---|
| Detection with tracking | car-detection, face-detection, pedestrian-detection, vehicle-detection | detector_tracker_processor | Detector, Tracker | frame | bbox |
| Detection only | car-detection-pure, face-detection-pure, model-switch-detection | detector_processor | Detector | frame | bbox |
| Classification | age-classification, gender-classification, category-identification, exposure-identification, license-plate-recognition | classifier_processor | Classifier | bbox | text |
| ROI-aware classification | age-classification-roi, gender-classification-roi, category-identification-roi, exposure-identification-roi, license-plate-recognition-roi | roi_classifier_processor | Roi_Classifier | bbox | text |
| Structured traffic services | traffic-detection, road-context-segmentation, vehicle-trajectory-prediction, risk-graph-generation | structured_processor | Structured_Processor | list-form labels such as [frame] or [bbox] | list-form labels such as [segmentation] or [graph] |
The id, input, and output fields decide how the service appears in frontend DAG orchestration. The service field
decides which image and application package are used at runtime.
Runtime Convention
When a processor is deployed, Dayu injects PROCESSOR_SERVICE_NAME=processor-<service>. The application loader converts
the service name from hyphen style to Python module style. For example:
processor-car-detection -> core.applications.car_detection
processor-age-classification -> core.applications.age_classification
The imported package must expose the object names required by the selected processor:
# dependency/core/applications/car_detection/__init__.py
from .car_detection import CarDetection as Detector
from .car_tracking import CarTracking as Tracker
__all__ = ["Detector", "Tracker"]
The base processors then obtain these objects with Context.get_instance():
detector_processor -> Detector
detector_tracker_processor -> Detector + Tracker
classifier_processor -> Classifier
roi_classifier_processor -> Roi_Classifier
structured_processor -> Structured_Processor
Parameters are passed through environment variables named after the object. For example, DETECTOR_PARAMETERS is passed
to Detector, CLASSIFIER_PARAMETERS is passed to Classifier, and ROI_CLASSIFIER_PARAMETERS is passed to
Roi_Classifier.
For structured services, STRUCTURED_PROCESSOR_PARAMETERS is passed to Structured_Processor.
Add Application Code
Create a package under dependency/core/applications/. Use underscore style for the Python module name and keep it
aligned with the service name:
dependency/core/applications/helmet_detection/
|-- __init__.py
|-- helmet_detection.py
|-- helmet_detection_with_tensorrt/
|-- helmet_detection_without_tensorrt/
`-- requirements.txt
A detection application should accept a list of frames and return one result per frame. Existing detection services return:
(result_boxes, result_scores, result_class_ids, result_roi_ids)
A minimal detector wrapper looks like this:
from typing import List
import numpy as np
from core.lib.common import Context
class HelmetDetection:
def __init__(self, trt_weights, trt_plugin_library=None, non_trt_weights=None, device=0):
self.trt_weights = Context.get_file_path(trt_weights)
self.trt_plugin_library = Context.get_file_path(trt_plugin_library) if trt_plugin_library else None
self.non_trt_weights = Context.get_file_path(non_trt_weights) if non_trt_weights else None
self.device = device
self.flops = 0
use_tensorrt = Context.get_parameter("USE_TENSORRT", direct=False)
if use_tensorrt:
# Load TensorRT model here.
pass
else:
# Load PyTorch or another non-TensorRT model here.
pass
def infer(self, image: np.ndarray):
# Return boxes, scores, and class IDs for one frame.
raise NotImplementedError
def __call__(self, images: List[np.ndarray]):
output = []
for image in images:
boxes, scores, class_ids = self.infer(image)
roi_ids = list(range(len(boxes)))
output.append((boxes, scores, class_ids, roi_ids))
return output
Then export it in __init__.py:
from .helmet_detection import HelmetDetection as Detector
__all__ = ["Detector"]
If the service uses tracking, also export Tracker and select detector_tracker_processor in the template. If the
service classifies regions from a previous detection result, export Classifier and select classifier_processor. If
it caches classification results by ROI ID, export Roi_Classifier, implement reset_cache(), and select
roi_classifier_processor.
Add Structured Application Code
Use structured_processor when the service needs predecessor outputs from the current DAG or returns richer structured
records than the bbox/text processors. The application package should export Structured_Processor:
# dependency/core/applications/traffic_event_detection/__init__.py
from .traffic_event_detection import TrafficEventDetection as Structured_Processor
__all__ = ["Structured_Processor"]
Structured_Processor receives a payload with task metadata, decoded frames, and predecessor service content:
class TrafficEventDetection:
def __init__(self, trt_weights, trt_plugin_library=None, non_trt_weights=None, device=0):
self.flops = 0
def __call__(self, payload):
frames = payload["frames"]
inputs = payload["inputs"]
return {
"graph": [
{
"frame_index": frame_index,
"items": []
}
for frame_index, _ in enumerate(frames)
]
}
Structured application code returns only outputs. The processor adds the common Dayu content envelope:
{
"service": "traffic-event-detection",
"outputs": {
"graph": [
{
"frame_index": 0,
"items": []
}
]
},
"profile": {
"frame_count": 1
}
}
Every output label must map to a list of records. Each record must include frame_index and an items list. The
current processor profile is intentionally compact and only allows frame_count.
Implementation-specific backends, service output details, and the recommended review DAG are maintained in the Structured Traffic Services reference.
Add Mounted Files
Processor model files are normally mounted under the default file mount prefix configured in template/base.yaml:
default-file-mount-prefix: "/data/dayu-files"
For a processor template with:
file-mount:
- pos: both
path: "processor/helmet-detection/"
Dayu mounts files from /data/dayu-files/processor/helmet-detection/ on the node where the processor runs. Inside
application code, use Context.get_file_path("model.engine") or Context.get_file_path("model.pt") instead of
hard-coding absolute paths.
For the full mounted file structure, see Deploy Mounted Files.
Add Processor Template
Create template/processor/helmet-detection.yaml. A detection-only service can start from this template:
position: both
pod-template:
image: helmet-detection
imagePullPolicy: Always
env:
- name: PROCESSOR_NAME
value: "detector_processor"
- name: DETECTOR_PARAMETERS
value: >
{
'trt_weights': 'helmet.engine',
'trt_plugin_library': 'libmyplugins.so',
'non_trt_weights': 'helmet.pt',
'device': 0
}
- name: SCENARIOS_EXTRACTORS
value: >
['obj_num', 'obj_size']
- name: PRO_QUEUE_NAME
value: "simple"
- name: USE_TENSORRT
value: "True"
port-open:
pos: both
port: 9000
file-mount:
- pos: both
path: "processor/helmet-detection/"
Important fields:
| Field | Meaning |
|---|---|
position | cloud, edge, or both. Most processor applications use both so the scheduler can place them on cloud or edge nodes. |
pod-template.image | Image name. It is expanded with registry, repository, and tag from template/base.yaml. |
PROCESSOR_NAME | Selects the base processor implementation. |
DETECTOR_PARAMETERS / CLASSIFIER_PARAMETERS / ROI_CLASSIFIER_PARAMETERS / STRUCTURED_PROCESSOR_PARAMETERS | Constructor parameters for the exported application object. |
SCENARIOS_EXTRACTORS | Scenario features reported to scheduling policies, such as obj_num and obj_size. |
PRO_QUEUE_NAME | Processor task queue implementation. Existing services use simple. |
USE_TENSORRT | Enables TensorRT model loading in applications that support it. |
file-mount.path | Relative path under default-file-mount-prefix. |
Register the Service
Add the service to template/services.yaml:
- id: helmet-detection
service: helmet-detection
name: helmet detection
description: helmet detection
input: [ frame ]
output: [ bbox ]
yaml: helmet-detection.yaml
Use these conventions:
idis the DAG node type shown in the frontend. It must be unique.serviceis the runtime service name. It should match the image name and Python module after converting hyphens to underscores.inputandoutputmust be YAML lists and must be compatible with adjacent services in a DAG. For example, a detection service usually maps[frame] -> [bbox], while a classification service maps[bbox] -> [text].yamlpoints to a file undertemplate/processor/.
Multiple service entries may share the same service image and package. For example, age-classification and
age-classification-roi both use service: age-classification, but they point to different processor templates.
Add Dockerfile and Build Registration
Create a Dockerfile under build/. Existing processor images copy the shared libraries, the base processor code, the
application package requirements, and the common processor entrypoint:
ARG REG=docker.io
ARG BASE_REPO=dayuhub
ARG TAG=latest
FROM ${REG}/${BASE_REPO}/dayubase:${TAG}
ARG dependency_dir=dependency
ARG lib_dir=dependency/core/lib
ARG base_dir=dependency/core/processor
ARG code_dir=components/processor
ARG app_dir=dependency/core/applications/helmet_detection
ENV TZ=Asia/Shanghai
COPY ${lib_dir}/requirements.txt ./lib_requirements.txt
COPY ${base_dir}/requirements.txt ./base_requirements.txt
COPY ${app_dir}/requirements.txt ./app_requirements.txt
RUN pip3 install --upgrade pip && \
pip3 install -r lib_requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && \
pip3 install -r base_requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple && \
pip3 install -r app_requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
COPY ${dependency_dir} /home/dependency
ENV PYTHONPATH="/home/dependency"
WORKDIR /app
COPY ${code_dir}/* /app/
CMD ["python3", "-m", "gunicorn", "main:app", "-c", "./gunicorn.conf.py"]
Then register the image in docker-bake.hcl:
target "helmet-detection" {
inherits = ["_image-common"]
matrix = {
variant = [
{ name = "default", suffix = "" },
{ name = "jp4", suffix = "-jp4" },
{ name = "jp5", suffix = "-jp5" },
{ name = "jp6", suffix = "-jp6" },
]
}
name = "helmet-detection-${variant.name}"
dockerfile = "build/helmet_detection.Dockerfile"
platforms = ["linux/amd64", "linux/arm64"]
args = {
REG = REGISTRY
BASE_REPO = BASE_REPO
TAG = "${BASE_TAG}${variant.suffix}"
}
tags = ["${REGISTRY}/${REPO}/helmet-detection:${TAG}${variant.suffix}"]
}
Add the target to the appropriate Bake groups, usually default, processors, and all-images. Processor images and
monitor publish JetPack suffix variants such as -jp4, -jp5, and -jp6 through the shared Bake matrix.
Build and push the image:
make build WHAT=helmet-detection
If you use a private registry, set REG, REPO, and TAG before building:
export REG=repo:5000
export REPO=dayuhub
export TAG=current-tag
make build WHAT=helmet-detection
After adding or renaming an image, run the repository build validator:
make validate-build
Check Before Running
Before starting Dayu, check the following:
template/services.yamlcontains the new service entry.template/processor/<service-id>.yamlexists and uses the correctPROCESSOR_NAME.- The application package exports the objects required by
PROCESSOR_NAME. DETECTOR_PARAMETERS,CLASSIFIER_PARAMETERS,ROI_CLASSIFIER_PARAMETERS, orSTRUCTURED_PROCESSOR_PARAMETERSmatch the constructor arguments.- Mounted files exist on every cloud or edge node that may run the service.
- The image name in the processor template can be pulled from the registry configured in
template/base.yaml. docker-bake.hclcontains a target for the image, andmake validate-buildsucceeds.
After the system starts, open the frontend DAG orchestration page and verify that the new service appears with the expected input and output types.