Skip to main content
Version: Current

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.