Skip to main content
Version: v1.4

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 typeExisting examplesProcessor selected by PROCESSOR_NAMEPython objects exported by application packageInputOutput
Detection with trackingcar-detection, face-detection, pedestrian-detection, vehicle-detectiondetector_tracker_processorDetector, Trackerframebbox
Detection onlycar-detection-pure, face-detection-pure, model-switch-detectiondetector_processorDetectorframebbox
Classificationage-classification, gender-classification, category-identification, exposure-identification, license-plate-recognitionclassifier_processorClassifierbboxtext
ROI-aware classificationage-classification-roi, gender-classification-roi, category-identification-roi, exposure-identification-roi, license-plate-recognition-roiroi_classifier_processorRoi_Classifierbboxtext
Structured traffic servicestraffic-detection, road-context-segmentation, vehicle-trajectory-prediction, risk-graph-generationstructured_processorStructured_Processorlist-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:

FieldMeaning
positioncloud, edge, or both. Most processor applications use both so the scheduler can place them on cloud or edge nodes.
pod-template.imageImage name. It is expanded with registry, repository, and tag from template/base.yaml.
PROCESSOR_NAMESelects the base processor implementation.
DETECTOR_PARAMETERS / CLASSIFIER_PARAMETERS / ROI_CLASSIFIER_PARAMETERS / STRUCTURED_PROCESSOR_PARAMETERSConstructor parameters for the exported application object.
SCENARIOS_EXTRACTORSScenario features reported to scheduling policies, such as obj_num and obj_size.
PRO_QUEUE_NAMEProcessor task queue implementation. Existing services use simple.
USE_TENSORRTEnables TensorRT model loading in applications that support it.
file-mount.pathRelative 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:

  • id is the DAG node type shown in the frontend. It must be unique.
  • service is the runtime service name. It should match the image name and Python module after converting hyphens to underscores.
  • input and output must 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].
  • yaml points to a file under template/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.yaml contains the new service entry.
  • template/processor/<service-id>.yaml exists and uses the correct PROCESSOR_NAME.
  • The application package exports the objects required by PROCESSOR_NAME.
  • DETECTOR_PARAMETERS, CLASSIFIER_PARAMETERS, ROI_CLASSIFIER_PARAMETERS, or STRUCTURED_PROCESSOR_PARAMETERS match 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.hcl contains a target for the image, and make validate-build succeeds.

After the system starts, open the frontend DAG orchestration page and verify that the new service appears with the expected input and output types.