How to Properly Register Custom Dataset Class Names Instead of Using COCO Defaults? or Custom Class Names Are Mapping to COCO Labels During Inference – How to Override?

Hi Hailo Team,

I’m working with a custom object detection model trained on my own dataset, which has 3 classes:
Gun, Person_with_Mask, Person.

I trained the model, exported it to ONNX, and successfully ran it through the Hailo Model Zoo flow using .yaml, .alls, and .json files.

However, during inference on the Hailo platform, while the detections themselves are correct (i.e., objects are being detected at the right places), the output class labels are incorrect — they are being mapped to the first 3 COCO class names:

  • Gun → shown as person
  • Person_with_Mask → shown as bicycle
  • Person → shown as car

This suggests that my custom classes are working internally (the .pt.onnx model is detecting them properly), but the system is displaying COCO labels instead of my own.

Here’s what I have already tried:

  • Updated labels_map in the .yaml to point to my ids3cls.names file.
  • Set dataset_name: ids3cls_custom in YAML.
  • Registered my dataset in datasets_info.py with DatasetInfo(class_names=[...]).
  • Verified that BasicDatasetsEnum includes my custom entry.
  • Recompiled and re-optimized everything from scratch.

Still, during evaluation or postprocessing, the COCO labels appear.

:red_question_mark: Where exactly in the Model Zoo pipeline are the class names pulled from during inference or visualization?
:red_question_mark: Is there a better way to make sure my 3 custom class names are used and not overwritten by defaults?

Any insights or guidance would be really helpful. Thank you!

Hey @Ragul_M,

Welcome to the Hailo Community!

The class names you’re seeing aren’t coming from your ONNX model - they’re being added by the Model Zoo during evaluation and post-processing. By default, it uses COCO labels if you don’t specify your own.

Here’s how to fix it:

1. Create a custom post-processing JSON file with your labels:

{ "labels": ["Gun","Person_with_Mask","Person"] }

2. Update your YAML config to point to this file and set a custom dataset name:

postprocessing:
  postprocess_config_file: /path/to/custom_postprocess.json

evaluation:
  dataset_name: ids3cls_custom
  classes: 3

3. Register your dataset in hailo_model_zoo/common/datasets_info.py:

class BasicDatasetsEnum(Enum):
    IDS3CLS_CUSTOM = DatasetInfo(class_names=["Gun", "Person_with_Mask", "Person"])

4. Recompile and evaluate:

hailomz compile --ckpt my_model.onnx --yaml my_config.yaml
hailomz eval my_model --visualize

That’s it! Now your custom labels will show up consistently in evaluation outputs and runtime apps instead of the default COCO classes.

The key is making sure your labels are embedded in both the post-processing config (so they’re baked into the compiled model) and the dataset registry (so evaluation uses them correctly).

Let me know if you need any clarification!

1 Like

Hi @omria and Hailo Team,

Thank you for the detailed and clear guidance! I’m following up with my current setup, confirming the changes I’ve already made, and requesting clarification on one specific point.

:hammer_and_wrench: Platform and Environment

  • Host: Ubuntu x86_64 (not running on Hailo device currently)
  • Model Type: Custom YOLOv8 model
  • Classes: ["Gun", "Person_with_Mask", "Person"]
  • Export Format: ONNX (ids3cls.onnx)
  • Hailo Flow Used: .alls, .yaml, .json, custom TFRecord

Changes Made So Far

1. :white_check_mark: .yaml File (Located: hailo_model_zoo/hailo_model_zoo/cfg/networks/ids3cls.yaml)

ids3cls.yaml 

base:
- base/yolov8.yaml
postprocessing:
  device_pre_post_layers:
    nms: true
  hpp: true
network:
  network_name: ids3cls
paths:
  network_path:
  - /home/graphin/Hailoexecutablefile/ids3cls.onnx
  alls_script: ids3cls.alls
parser:
  nodes:
  - null
  - - /model.23/cv2.0/cv2.0.2/Conv
    - /model.23/cv3.0/cv3.0.2/Conv
    - /model.23/cv2.1/cv2.1.2/Conv
    - /model.23/cv3.1/cv3.1.2/Conv
    - /model.23/cv2.2/cv2.2.2/Conv
    - /model.23/cv3.2/cv3.2.2/Conv
    
evaluation:
  dataset_name: ids3cls_custom
  labels_offset: 0
  classes: 3
  data_set: /home/graphin/.hailomz/data/models_files/ids3cls/2021-06-18/coco_calib2017.tfrecord
  labels_map: /home/graphin/Downloads/Dataset/ids3cls.names
quantization:
  calib_set:
  - /home/graphin/.hailomz/data/models_files/ids3cls/2021-06-18/coco_val2017.tfrecord

info:
  task: object detection
  input_shape: 640x640x3
  output_shape: 80x5x100
  operations: 6.55G
  parameters: 2.6M
  framework: pytorch
  training_data: /home/graphin/Downloads/Dataset/train
  validation_data: /home/graphin/Downloads/Dataset/valid

This is my ids3cls.alls

normalization1 = normalization([0.0, 0.0, 0.0], [255.0, 255.0, 255.0])

change_output_activation(conv54, sigmoid)
change_output_activation(conv65, sigmoid)
change_output_activation(conv80, sigmoid)
nms_postprocess("../../postprocess_config/ids3cls_nms_config.json", meta_arch=yolov8, engine=cpu)

allocator_param(width_splitter_defuse=disabled)


This is my ids3cls_nms_config.json
{
    "nms_scores_th": 0.2,
    "nms_iou_th": 0.7,
    "image_dims": [
        640,
        640
    ],
    "max_proposals_per_class": 100,
    "classes": 3,
    "regression_length": 16,
    "background_removal": false,
    "background_removal_index": 0,
    "bbox_decoders": [
        {
            "name": "bbox_decoder51",
            "stride": 8,
            "reg_layer": "conv51",
            "cls_layer": "conv54"
        },
        {
            "name": "bbox_decoder62",
            "stride": 16,
            "reg_layer": "conv62",
            "cls_layer": "conv65"
        },
        {
            "name": "bbox_decoder77",
            "stride": 32,
            "reg_layer": "conv77",
            "cls_layer": "conv80"
        }
    ]
}

:white_check_mark: Registered Dataset in datasets_info.py like this follows

Path: `hailo_model_zoo/hailo_model_zoo/core/datasets/datasets_info.py

class_names_ids3cls = ["Gun", "Person_with_Mask", "Person"]
IDS3CLS_LABEL_MAP = {i: i for i in range(len(class_names_ids3cls))}

class BasicDatasetsEnum(Enum):
    COCO = "coco_detection"
    IDS3CLS_CUSTOM = "ids3cls_custom"

DATASETS_INFO = {
    BasicDatasetsEnum.COCO.value: DatasetInfo(class_names=CLASS_NAMES_COCO, label_map=COCO_LABEL_MAP),
    BasicDatasetsEnum.IDS3CLS_CUSTOM.value: DatasetInfo(class_names=class_names_ids3cls, label_map=IDS3CLS_LABEL_MAP),
}

:white_check_mark: Registered Dataset in parse_coco.py

Also in parse_detection_record():

@DATASET_FACTORY.register(name="ids3cls_custom")
def parse_detection_record(serialized_example):
    ...

:white_check_mark: TFRecord Generation

Converted my YOLO-style annotations to COCO-style .json, then to TFRecord using:

python3 hailo_model_zoo/hailo_model_zoo/datasets/create_coco_tfrecord.py calib2017 --img /path/to/images --det /path/to/calib2017.json
python3 hailo_model_zoo/hailo_model_zoo/datasets/create_coco_tfrecord.py val2017 --img /path/to/images --det /path/to/val2017.json

Command Flow I’m Using

hailomz parse --hw-arch hailo8l --ckpt ids3cls.onnx ids3cls 

hailomz optimize --hw-arch hailo8l --har ./ids3cls.har ids3cls 

hailomz compile ids3cls --hw-arch hailo8l --har ./ids3cls.har


Final Clarification Needed

I’ve done all the steps above, but one last doubt remains:

Where should the custom_postprocess.json file be placed exactly to ensure it’s embedded in the compiled .hef and used during hardware inference? and also i’ve there are lots and lots of .yaml base like id3cls.yaml → (in base folder)yolov8.yaml → yolo.yaml → coco.yaml → base.yaml. Do i need to really mention or follow all these base? And kindly clear me from where the coco default labels are comming in the dataset_info.py i made a simple hack changes like in the COCO_CLASS_NAME in edited and hardcoded my custom class names at respective index position but still that also it does’nt take those names so i regiserted the name as ids3cls_custom and made a new field as i mention above and created a Enum that to did’nt work i need to know from where this label mapping is happening that to tell me

Thanks a lot for the continued support!
Really appreciate the clarity and depth of responses here and kindly guide me through out this process. These are all the things i done and this is my current setup and let me know what to change and where? and explain me also?

Best regards,
Ragul M

Let me clear up those three points for you:

1. Where to put the custom_postprocess.json file:
Just drop it in the same directory as your ids3cls.yaml file - I usually create a postprocess_config/ subfolder inside cfg/networks/ids3cls/ to keep things organized. Then reference it in your YAML like this:

postprocessing:
  postprocess_config_file: postprocess_config/custom_labels.json

This way it gets baked right into your compiled .hef file.

2. About all those YAML base files:
Don’t worry - you don’t need to manually specify every single one! Just reference the top-level base (like base/yolov8.yaml) in your network YAML. The compiler is smart enough to follow the chain automatically: yolov8.yaml → yolo.yaml → coco.yaml → base.yaml. It handles all that inheritance for you.

3. The COCO labels mystery:
Ah, this is where it gets tricky! There are actually two places where labels come from:

  • At compile time: The system uses a built-in post-processing JSON with the 80 COCO class names unless you override it with your postprocess_config_file
  • At evaluation time: If your evaluation.dataset_name is still pointing to COCO TFRecords (or you haven’t properly registered your ids3cls_custom in datasets_info.py), it’ll keep pulling the COCO class list

The fix is to make sure both your data_set and calib_set point to your own TFRecords AND properly register that ids3cls_custom in datasets_info.py. That should get your custom labels showing up in evaluation.

Hope that helps! Let me know if you need me to walk through any of these steps in more detail.

Hi @omria and Hailo Team,

Thank you again for your previous response and the detailed clarification.

Unfortunately, I’ve followed all the steps as recommended, but I’m still seeing the COCO labels (“person”, “bicycle”, “car”) during evaluation and visualization, instead of my custom labels (“Gun”, “Person_with_Mask”, “Person”).

Steps I’ve Re-Applied (as per your suggestions)

:white_check_mark: 1. Created the custom_postprocess.json file:

{
  "labels": ["Gun", "Person_with_Mask", "Person"]
}

:white_check_mark: 2. Updated my ids3cls.yaml:

base:

- base/yolov8.yaml

postprocessing:

  postprocess_config_file: /home/graphin/Hailoexecutablefile/hailo_model_zoo/hailo_model_zoo/cfg/networks/postprocess/custom_labels.json

  device_pre_post_layers:

    nms: true

  hpp: true

  classes: 3

  class_names:

    -Gun

    -Person

    -Person_with_mask
 
network:

  network_name: ids3cls

paths:

  network_path:

  - /home/graphin/Hailoexecutablefile/idsv3.onnx

  alls_script: ids3cls.alls

parser:

  nodes:

  - null

  - - /model.23/cv2.0/cv2.0.2/Conv

    - /model.23/cv3.0/cv3.0.2/Conv

    - /model.23/cv2.1/cv2.1.2/Conv

    - /model.23/cv3.1/cv3.1.2/Conv

    - /model.23/cv2.2/cv2.2.2/Conv

    - /model.23/cv3.2/cv3.2.2/Conv

evaluation:

  dataset_name: ids3cls_custom

  labels_offset: 0

  classes: 3

  data_set: /home/graphin/.hailomz/data/models_files/ids3cls/2021-06-18/coco_calib2017.tfrecord

quantization:

  calib_set:

  - /home/graphin/.hailomz/data/models_files/ids3cls/2021-06-18/coco_val2017.tfrecord
 
info:

  task: object detection

  input_shape: 640x640x3

  output_shape: 80x5x100

  operations: 6.55G

  parameters: 2.6M

  framework: pytorch

  training_data: /home/graphin/Documents/Dataset/train/

  validation_data: /home/graphin/Documents/Dataset/valid/


:white_check_mark: 3. Registered Dataset in datasets_info.py:

from enum import Enum
 
CLASS_NAMES_COCO = (

    "person",

    "bicycle",

    "car",

    "motorcycle",

    "airplane",

    "bus",

    "train",

    "truck",

    "boat",

    "traffic light",

    "fire hydrant",

    "stop sign",

    "parking meter",

    "bench",

    "bird",

    "cat",

    "dog",

    "horse",

    "sheep",

    "cow",

    "elephant",

    "bear",

    "zebra",

    "giraffe",

    "backpack",

    "umbrella",

    "handbag",

    "tie",

    "suitcase",

    "frisbee",

    "skis",

    "snowboard",

    "sports ball",

    "kite",

    "baseball bat",

    "baseball glove",

    "skateboard",

    "surfboard",

    "tennis racket",

    "bottle",

    "wine glass",

    "cup",

    "fork",

    "knife",

    "spoon",

    "bowl",

    "banana",

    "apple",

    "sandwich",

    "orange",

    "broccoli",

    "carrot",

    "hot dog",

    "pizza",

    "donut",

    "cake",

    "chair",

    "couch",

    "potted plant",

    "bed",

    "dining table",

    "toilet",

    "tv",

    "laptop",

    "mouse",

    "remote",

    "keyboard",

    "cell phone",

    "microwave",

    "oven",

    "toaster",

    "sink",

    "refrigerator",

    "book",

    "clock",

    "vase",

    "scissors",

    "teddy bear",

    "hair drier",

    "toothbrush",

)
 
COCO_LABEL_MAP = {

    1: 1,

    2: 2,

    3: 3,

    4: 4,

    5: 5,

    6: 6,

    7: 7,

    8: 8,

    9: 9,

    10: 10,

    11: 11,

    13: 12,

    14: 13,

    15: 14,

    16: 15,

    17: 16,

    18: 17,

    19: 18,

    20: 19,

    21: 20,

    22: 21,

    23: 22,

    24: 23,

    25: 24,

    27: 25,

    28: 26,

    31: 27,

    32: 28,

    33: 29,

    34: 30,

    35: 31,

    36: 32,

    37: 33,

    38: 34,

    39: 35,

    40: 36,

    41: 37,

    42: 38,

    43: 39,

    44: 40,

    46: 41,

    47: 42,

    48: 43,

    49: 44,

    50: 45,

    51: 46,

    52: 47,

    53: 48,

    54: 49,

    55: 50,

    56: 51,

    57: 52,

    58: 53,

    59: 54,

    60: 55,

    61: 56,

    62: 57,

    63: 58,

    64: 59,

    65: 60,

    67: 61,

    70: 62,

    72: 63,

    73: 64,

    74: 65,

    75: 66,

    76: 67,

    77: 68,

    78: 69,

    79: 70,

    80: 71,

    81: 72,

    82: 73,

    84: 74,

    85: 75,

    86: 76,

    87: 77,

    88: 78,

    89: 79,

    90: 80,

}
 
CLASS_NAMES_D2S = (

    "adelholzener_alpenquelle_classic_075",

    "adelholzener_alpenquelle_naturell_075",

    "adelholzener_classic_bio_apfelschorle_02",

    "adelholzener_classic_naturell_02",

    "adelholzener_gourmet_mineralwasser_02",

    "augustiner_lagerbraeu_hell_05",

    "augustiner_weissbier_05",

    "coca_cola_05",

    "coca_cola_light_05",

    "suntory_gokuri_lemonade",

    "tegernseer_hell_03",

    "corny_nussvoll",

    "corny_nussvoll_single",

    "corny_schoko_banane",

    "corny_schoko_banane_single",

    "dr_oetker_vitalis_knuspermuesli_klassisch",

    "koelln_muesli_fruechte",

    "koelln_muesli_schoko",

    "caona_cocoa",

    "cocoba_cocoa",

    "cafe_wunderbar_espresso",

    "douwe_egberts_professional_ground_coffee",

    "gepa_bio_caffe_crema",

    "gepa_italienischer_bio_espresso",

    "apple_braeburn_bundle",

    "apple_golden_delicious",

    "apple_granny_smith",

    "apple_red_boskoop",

    "avocado",

    "banana_bundle",

    "banana_single",

    "grapes_green_sugraone_seedless",

    "grapes_sweet_celebration_seedless",

    "kiwi",

    "orange_single",

    "oranges",

    "pear",

    "clementine",

    "clementine_single",

    "pasta_reggia_elicoidali",

    "pasta_reggia_fusilli",

    "pasta_reggia_spaghetti",

    "franken_tafelreiniger",

    "pelikan_tintenpatrone_canon",

    "ethiquable_gruener_tee_ceylon",

    "gepa_bio_und_fair_fencheltee",

    "gepa_bio_und_fair_kamillentee",

    "gepa_bio_und_fair_kraeuterteemischung",

    "gepa_bio_und_fair_pfefferminztee",

    "gepa_bio_und_fair_rooibostee",

    "kilimanjaro_tea_earl_grey",

    "cucumber",

    "carrot",

    "corn_salad",

    "lettuce",

    "vine_tomatoes",

    "roma_vine_tomatoes",

    "rocket",

    "salad_iceberg",

    "zucchini",

)
 
D2S_LABEL_MAP = {i + 1: i + 1 for i in range(len(CLASS_NAMES_D2S))}
 
CLASS_NAMES_D2S_FRUITS = (

    "apple",

    "avocado",

    "banana_single",

    "clementine_single",

    "kiwi",

    "orange_single",

    "pear",

    "cucumber",

    "carrot",

)
 
D2S_FRUITS_LABEL_MAP = {i + 1: i + 1 for i in range(len(CLASS_NAMES_D2S_FRUITS))}
 
CLASS_NAMES_NUSCENES = (

    "car",

    "truck",

    "construction_vehicle",

    "bus",

    "trailer",

    "barrier",

    "motorcycle",

    "bicycle",

    "pedestrian",

    "traffic_cone",

)
 
NUSCENES_LABEL_MAP = {i: i for i in range(len(CLASS_NAMES_NUSCENES))}
 
CLASS_NAMES_IDS3CLS = (

    "GUN",

    "Person_with_Mask",

    "person"

)
 
IDS3CLS_LABEL_MAP = {i: i for i in range(len(CLASS_NAMES_IDS3CLS))}
 
 
class DatasetInfo(object):

    def __init__(self, class_names, label_map):

        self._class_names = class_names

        self._label_map = label_map
 
    @property

    def class_names(self):

        return self._class_names
 
    @property

    def label_map(self):

        return self._label_map
 
 
class BasicDatasetsEnum(Enum):

    COCO = "coco_detection"

    D2S = "d2s_detection"

    D2S_FRUITS = "d2s_fruits_detection"

    NUSCENES = "nuscenes"

    IDS3CLS = "ids3cls_custom"
 
 
DATASETS_INFO = {

    BasicDatasetsEnum.COCO.value: DatasetInfo(class_names=CLASS_NAMES_COCO, label_map=COCO_LABEL_MAP),

    BasicDatasetsEnum.D2S.value: DatasetInfo(class_names=CLASS_NAMES_D2S, label_map=D2S_LABEL_MAP),

    BasicDatasetsEnum.D2S_FRUITS.value: DatasetInfo(class_names=CLASS_NAMES_D2S_FRUITS, label_map=D2S_FRUITS_LABEL_MAP),

    BasicDatasetsEnum.NUSCENES.value: DatasetInfo(class_names=CLASS_NAMES_NUSCENES, label_map=NUSCENES_LABEL_MAP),

    BasicDatasetsEnum.IDS3CLS.value: DatasetInfo(class_names=CLASS_NAMES_IDS3CLS, label_map=IDS3CLS_LABEL_MAP),

}
 
 
def get_dataset_info(dataset_name):

    print(f"🧪 Using dataset_name: {dataset_name}")

    if dataset_name not in DATASETS_INFO:

        raise ValueError("ERROR unknown network_selection {}".format(dataset_name))

    return DATASETS_INFO[dataset_name]
 

:white_check_mark: 4. Registered ids3cls_custom in parse_coco.py:

import tensorflow as tf
 
from hailo_model_zoo.core.factory import DATASET_FACTORY
 
 
@DATASET_FACTORY.register(name="cocopose_single_person")

def parse_single_person_pose_estimation_record(serialized_example):

    """Parse serialized example of TfRecord and extract dictionary of all the information"""

    features = tf.io.parse_single_example(

        serialized_example,

        features={

            "height": tf.io.FixedLenFeature([], tf.int64),

            "width": tf.io.FixedLenFeature([], tf.int64),

            "xmin": tf.io.VarLenFeature(tf.float32),

            "xmax": tf.io.VarLenFeature(tf.float32),

            "ymin": tf.io.VarLenFeature(tf.float32),

            "ymax": tf.io.VarLenFeature(tf.float32),

            "image_id": tf.io.FixedLenFeature([], tf.int64),

            "image_name": tf.io.FixedLenFeature([], tf.string),

            "image_jpeg": tf.io.FixedLenFeature([], tf.string),

        },

    )

    height = tf.cast(features["height"], tf.int32)

    width = tf.cast(features["width"], tf.int32)
 
    xmin = tf.sparse.to_dense(features["xmin"], default_value=0)

    xmax = tf.sparse.to_dense(features["xmax"], default_value=0)

    ymin = tf.sparse.to_dense(features["ymin"], default_value=0)

    ymax = tf.sparse.to_dense(features["ymax"], default_value=0)

    bbox = tf.transpose(tf.stack([xmin, xmax, ymin, ymax]))
 
    image_id = tf.cast(features["image_id"], tf.int32)

    image_name = tf.cast(features["image_name"], tf.string)

    image = tf.image.decode_jpeg(features["image_jpeg"], channels=3)

    image_shape = tf.stack([height, width, 3])

    image = tf.cast(tf.reshape(image, image_shape), tf.uint8)

    image_info = {"image_id": image_id, "image_name": image_name, "bbox": bbox}
 
    return [image, image_info]
 
 
@DATASET_FACTORY.register(name="cocopose")

def parse_pose_estimation_record(serialized_example):

    """Parse serialized example of TfRecord and extract dictionary of all the information"""

    features = tf.io.parse_single_example(

        serialized_example,

        features={

            "height": tf.io.FixedLenFeature([], tf.int64),

            "width": tf.io.FixedLenFeature([], tf.int64),

            "image_id": tf.io.FixedLenFeature([], tf.int64),

            "image_name": tf.io.FixedLenFeature([], tf.string),

            "image_jpeg": tf.io.FixedLenFeature([], tf.string),

        },

    )

    height = tf.cast(features["height"], tf.int32)

    width = tf.cast(features["width"], tf.int32)

    image_id = tf.cast(features["image_id"], tf.int32)

    image_name = tf.cast(features["image_name"], tf.string)

    image = tf.image.decode_jpeg(features["image_jpeg"], channels=3)

    image_shape = tf.stack([height, width, 3])

    image = tf.cast(tf.reshape(image, image_shape), tf.uint8)

    image_info = {"image_id": image_id, "image_name": image_name}
 
    return [image, image_info]
 
 
@DATASET_FACTORY.register(name="coco_segmentation")

@DATASET_FACTORY.register(name="cityscapes")

@DATASET_FACTORY.register(name="oxford_pet")

def parse_segmentation_record(serialized_example):

    """Parse serialized example of TfRecord and extract dictionary of all the information"""

    features = tf.io.parse_single_example(

        serialized_example,

        features={

            "height": tf.io.FixedLenFeature([], tf.int64),

            "width": tf.io.FixedLenFeature([], tf.int64),

            "xmin": tf.io.VarLenFeature(tf.float32),

            "xmax": tf.io.VarLenFeature(tf.float32),

            "ymin": tf.io.VarLenFeature(tf.float32),

            "ymax": tf.io.VarLenFeature(tf.float32),

            "category_id": tf.io.VarLenFeature(tf.int64),

            "image_name": tf.io.FixedLenFeature([], tf.string),

            "mask": tf.io.FixedLenFeature([], tf.string),

            "image_jpeg": tf.io.FixedLenFeature([], tf.string),

        },

    )

    height = tf.cast(features["height"], tf.int32)

    width = tf.cast(features["width"], tf.int32)

    image_name = tf.cast(features["image_name"], tf.string)

    image = tf.image.decode_jpeg(features["image_jpeg"], channels=3)

    mask = tf.io.decode_raw(features["mask"], tf.uint8)

    image_shape = tf.stack([height, width, 3])

    mask_shape = tf.stack([height, width, 1])

    image = tf.cast(tf.reshape(image, image_shape), tf.uint8)

    mask = tf.cast(tf.reshape(mask, mask_shape), tf.uint8)

    image_info = {"image_name": image_name, "mask": mask}
 
    return [image, image_info]
 
 
@DATASET_FACTORY.register(name="coco_detection")

@DATASET_FACTORY.register(name="ids3cls_custom")

@DATASET_FACTORY.register(name="open_images")

@DATASET_FACTORY.register(name="visdrone_detection")

@DATASET_FACTORY.register(name="d2s_detection")

@DATASET_FACTORY.register(name="d2s_fruits_detection")

@DATASET_FACTORY.register(name="coco_2017_detection")

@DATASET_FACTORY.register(name="vehicle_detection")

@DATASET_FACTORY.register(name="license_plates")

@DATASET_FACTORY.register(name="personface_detection")

def parse_detection_record(serialized_example):

    """Parse serialized example of TfRecord and extract dictionary of all the information"""

    features = tf.io.parse_single_example(

        serialized_example,

        features={

            "height": tf.io.FixedLenFeature([], tf.int64),

            "width": tf.io.FixedLenFeature([], tf.int64),

            "image_id": tf.io.FixedLenFeature([], tf.int64),

            "xmin": tf.io.VarLenFeature(tf.float32),

            "xmax": tf.io.VarLenFeature(tf.float32),

            "ymin": tf.io.VarLenFeature(tf.float32),

            "ymax": tf.io.VarLenFeature(tf.float32),

            "area": tf.io.VarLenFeature(tf.float32),

            "category_id": tf.io.VarLenFeature(tf.int64),

            "is_crowd": tf.io.VarLenFeature(tf.int64),

            "num_boxes": tf.io.FixedLenFeature([], tf.int64),

            "image_name": tf.io.FixedLenFeature([], tf.string),

            "image_jpeg": tf.io.FixedLenFeature([], tf.string),

        },

    )

    height = tf.cast(features["height"], tf.int32)

    width = tf.cast(features["width"], tf.int32)

    image_id = tf.cast(features["image_id"], tf.int32)

    image_name = tf.cast(features["image_name"], tf.string)

    image = tf.image.decode_jpeg(features["image_jpeg"], channels=3)

    image_shape = tf.stack([height, width, 3])

    image = tf.cast(tf.reshape(image, image_shape), tf.uint8)

    image_info = {"image_name": image_name}

    image_info["height"] = height

    image_info["width"] = width

    image_info["image_id"] = image_id
 
    image_info["num_boxes"] = tf.cast(features["num_boxes"], tf.int32)

    image_info["is_crowd"] = tf.sparse.to_dense(features["is_crowd"], default_value=0)
 
    image_info["xmin"] = tf.sparse.to_dense(features["xmin"], default_value=0)

    image_info["xmax"] = tf.sparse.to_dense(features["xmax"], default_value=0)

    image_info["ymin"] = tf.sparse.to_dense(features["ymin"], default_value=0)

    image_info["ymax"] = tf.sparse.to_dense(features["ymax"], default_value=0)

    image_info["area"] = tf.sparse.to_dense(features["area"], default_value=0)

    image_info["category_id"] = tf.sparse.to_dense(features["category_id"], default_value=0)
 
    return [image, image_info]
 

:white_check_mark: 5. Reran Full Pipeline (from scratch):

hailomz parse --hw-arch hailo8l --ckpt ids3cls.onnx ids3cls
hailomz optimize --hw-arch hailo8l --har ./ids3cls.har ids3cls
hailomz compile ids3cls --hw-arch hailo8l --har ./ids3cls.har

Still Not Working

Even after all these steps, the label mapping during inference and evaluation still defaults to COCO. I confirmed that the detections (bounding boxes) are correct, but the label names are wrong.

My Question Now:

Is there another hardcoded fallback (e.g., inside compiled .hef, or within HailoRT API defaults) that could still override postprocess_config_file?

Also, do I need to include class_names: under postprocessing in YAML in addition to using the postprocess config?

Kindly help me through out this process could you verify the .yaml and the .json files used and kindly let me know what exactly i done wrong

Let me know if you’d like me to share folder structure, full YAML config, or video evidence.

Thanks again for the ongoing support!

Best regards,
Ragul M

Hey @Ragul_M,

How are you running it? Are you using the Hailo-rpi5-examples? If so, make sure you’re passing the correct labels JSON file in the detection part.

If its some other way , make sure to pass the labels json to the app.

Hi @omria,

I’m not running in the Hailo-rpi-examples. I’ve created my own script with the libyolo_hailortapp_post.so

and my script has 3 rtsp feed for detection

3rtsp.py



import gi

gi.require_version('Gst', '1.0')

gi.require_version('GObject', '2.0')

from gi.repository import Gst, GObject
 
 
Gst.init(None)
 
# Replace with your paths

HEF_PATH = "/home/pi/Desktop/yolov11n_h8l.hef"

POSTPROCESS_SO = "/home/pi/Desktop/libyolo_hailortpp_post.so"
 
# Replace with your RTSP URLs

RTSP_URLS = [

    "rtsp://admin:admin@192.168.1.1/Streaming/Channels/101",

    "rtsp://admin:admin@192.168.1.2/Streaming/Channels/101",

    "rtsp://admin:admin@192.168.1.3/Streaming/Channels/101"

]
 
# Generate source branches for 3 RTSP streams

rtsp_sources = ""

streamrouter_input_streams = ""

for i, url in enumerate(RTSP_URLS):

    rtsp_sources += f"""

    rtspsrc location={url} name=source_{i} message-forward=true ! rtph264depay ! 

    queue name=hailo_preprocess_q_{i} leaky=no max-size-buffers=5 ! 

    decodebin ! queue leaky=downstream max-size-buffers=5 ! videoscale n-threads=8 ! 

    video/x-raw,pixel-aspect-ratio=1/1 ! videoconvert n-threads=4 ! video/x-raw,pixel-aspect-ratio=1/1 ! 

    fun.sink_{i} sid.src_{i} ! 

    queue name=comp_q_{i} leaky=downstream max-size-buffers=5 ! 

    comp.sink_{i}

    """

    streamrouter_input_streams += f"src_{i}::input-streams=\"<sink_{i}>\" "
 
# Final GStreamer pipeline string

pipeline_str = f"""

hailoroundrobin mode=0 name=fun ! 

queue name=hailo_pre_infer_q_0 leaky=downstream max-size-buffers=5 ! 

hailonet hef-path={HEF_PATH} nms-score-threshold=0.3 nms-iou-threshold=0.45 output-format-type=HAILO_FORMAT_TYPE_FLOAT32 ! 

queue name=hailo_postprocess0 leaky=no max-size-buffers=30 ! 

hailofilter so-path={POSTPROCESS_SO} qos=false ! 

queue name=hailo_draw0 leaky=no max-size-buffers=30 ! 

hailooverlay ! hailostreamrouter name=sid {streamrouter_input_streams} 

compositor name=comp start-time-selection=0 

    sink_0::xpos=0 sink_0::ypos=0 

    sink_1::xpos=640 sink_1::ypos=0 

    sink_2::xpos=1280 sink_2::ypos=0 ! 

videoscale n-threads=4 ! 

video/x-raw,width=1920,height=640 ! 

fpsdisplaysink video-sink=ximagesink name=hailo_display sync=false text-overlay=false 

{rtsp_sources}

"""
 
print("✅ Launching GStreamer pipeline")

pipeline = Gst.parse_launch(pipeline_str)

pipeline.set_state(Gst.State.PLAYING)
 
loop = GObject.MainLoop()

try:

    loop.run()

except KeyboardInterrupt:

    pipeline.set_state(Gst.State.NULL)

    print("⏹️ Pipeline stopped.")
 

im using this script for running my model can you help me out.

@omria can you help me out in this? Still i’m not able to find from where these coco dataset class names are Mapping even though my .Pt has valid label names after converting to .hef with modelzoo my labels are mapped to the coco class names why?

Hey @Ragul_M ,

Did you try to overwrite them in the pipeline ?

Like we do in here for the --labels-json : hailo-apps-infra/hailo_apps/hailo_app_python/apps/detection/detection_pipeline.py at main · hailo-ai/hailo-apps-infra · GitHub

@omria yes its working but in my case i cant give like that. is there any way to bake the lables inside the hef itself?

This is how to do it :

self.labels_json = self.options_menu.labels_json
        detection_pipeline = INFERENCE_PIPELINE(
            hef_path=self.hef_path,
            post_process_so=self.post_process_so,
            post_function_name=self.post_function_name,
            batch_size=self.batch_size,
            config_json=self.labels_json,
            additional_params=self.thresholds_str)

def INFERENCE_PIPELINE(
    hef_path,
    post_process_so=None,
    batch_size=1,
    config_json=None,
    post_function_name=None,
    additional_params='',
    name='inference',
    # Extra hailonet parameters
    scheduler_timeout_ms=None,
    scheduler_priority=None,
    vdevice_group_id=1,
    multi_process_service=None
):
    """
    Creates a GStreamer pipeline string for inference and post-processing using a user-provided shared object file.
    This pipeline includes videoscale and videoconvert elements to convert the video frame to the required format.
    The format and resolution are automatically negotiated based on the HEF file requirements.

    Args:
        hef_path (str): Path to the HEF file.
        post_process_so (str or None): Path to the post-processing .so file. If None, post-processing is skipped.
        batch_size (int): Batch size for hailonet (default=1).
        config_json (str or None): Config JSON for post-processing (e.g., label mapping).
        post_function_name (str or None): Function name in the .so postprocess.
        additional_params (str): Additional parameters appended to hailonet.
        name (str): Prefix name for pipeline elements (default='inference').

        # Extra hailonet parameters
        Run `gst-inspect-1.0 hailonet` for more information.
        vdevice_group_id (int): hailonet vdevice-group-id. Default=1.
        scheduler_timeout_ms (int or None): hailonet scheduler-timeout-ms. Default=None.
        scheduler_priority (int or None): hailonet scheduler-priority. Default=None.
        multi_process_service (bool or None): hailonet multi-process-service. Default=None.

    Returns:
        str: A string representing the GStreamer pipeline for inference.
    """
    # config & function strings
    config_str = f' config-path={config_json} ' if config_json else ''
    function_name_str = f' function-name={post_function_name} ' if post_function_name else ''
    vdevice_group_id_str = f' vdevice-group-id={vdevice_group_id} '
    multi_process_service_str = f' multi-process-service={str(multi_process_service).lower()} ' if multi_process_service is not None else ''
    scheduler_timeout_ms_str = f' scheduler-timeout-ms={scheduler_timeout_ms} ' if scheduler_timeout_ms is not None else ''
    scheduler_priority_str = f' scheduler-priority={scheduler_priority} ' if scheduler_priority is not None else ''

    hailonet_str = (
        f'hailonet name={name}_hailonet '
        f'hef-path={hef_path} '
        f'batch-size={batch_size} '
        f'{vdevice_group_id_str}'
        f'{multi_process_service_str}'
        f'{scheduler_timeout_ms_str}'
        f'{scheduler_priority_str}'
        f'{additional_params} '
        f'force-writable=true '
    )

    inference_pipeline = (
        f'{QUEUE(name=f"{name}_scale_q")} ! '
        f'videoscale name={name}_videoscale n-threads=2 qos=false ! '
        f'{QUEUE(name=f"{name}_convert_q")} ! '
        f'video/x-raw, pixel-aspect-ratio=1/1 ! '
        f'videoconvert name={name}_videoconvert n-threads=2 ! '
        f'{QUEUE(name=f"{name}_hailonet_q")} ! '
        f'{hailonet_str} ! '
    )

    if post_process_so:
        inference_pipeline += (
            f'{QUEUE(name=f"{name}_hailofilter_q")} ! '
            f'hailofilter name={name}_hailofilter so-path={post_process_so} {config_str} {function_name_str} qos=false ! '
        )

    inference_pipeline += f'{QUEUE(name=f"{name}_output_q")} '

    return inference_pipeline