Hi @Teig_Levingston, Thanks for pointing that out. I think I wasnt really clear in the tutoiral. Yes, there should be a src/main.py file. I provided an example at the very end of the tutorial. But it is just an example. The tutorial was aminly focused on getting the environment set-up because that was for me the biggest hurddle (I also but my Hailo8 on the shelf for a few months because of that frustration
).
The main.py file i am using now looks like this:
# src/main.py
import hydra
from omegaconf import DictConfig
from parse_onnx import parse_onnx
from compile_hef import compile_hef
from make_alls import make_alls
@hydra.main(version_base=None, config_path="conf", config_name="config")
def main(cfg: DictConfig):
onnx_path = cfg.get("onnx_path")
calib_folder = cfg.get("calibration_images")
net_name = cfg.get("net_name")
build_dir = cfg.get("hef_build_dir")
hw_arch = cfg.get("hw_arch")
alls_script = make_alls(cfg)
end_node_names = cfg.get("end_node_names")
har_filename = cfg.get("har_filename")
parse_onnx(onnx_path, calib_folder, net_name, hw_arch, end_node_names, alls_script=alls_script)
compile_hef(har_filename, build_dir, hw_arch)
if __name__ == "__main__":
main()```
This uses hydra-core for the config. The config file is like this:
```yaml
# src/conf/config.yaml
end_node_names:
- "/model.22/cv2.0/cv2.0.2/Conv"
- "/model.22/cv3.0/cv3.0.2/Conv"
- "/model.22/cv2.1/cv2.1.2/Conv"
- "/model.22/cv3.1/cv3.1.2/Conv"
- "/model.22/cv2.2/cv2.2.2/Conv"
- "/model.22/cv3.2/cv3.2.2/Conv"
onnx_path: "./models/best.onnx"
calibration_images: "./calibration_images"
net_name: "yolov8n"
hef_build_dir: "./models/build"
har_filename: "yolov8n_quantized_model.har"
hw_arch: "hailo8l"
image_size:
- 640
- 640
alls:
normalization:
min:
- 0.0
- 0.0
- 0.0
max:
- 255.0
- 255.0
- 255.0
nms:
meta_arch: yolov8
nms_scores_th: 0.25
nms_iou_th: 0.45
engine: cpu
Then the imported scripts.
There is this one for making an alls script.
# src/make_alls.py
from textwrap import dedent
from pathlib import Path
def make_alls(cfg):
img_size = cfg.get("image_size")
alls_cfg = cfg.get("alls")
norm_min = alls_cfg.get("normalization", {}).get("min", [0,0,0])
norm_max = alls_cfg.get("normalization", {}).get("max", [255,255,255])
nms_cfg = alls_cfg.get("nms", {})
alls_content = dedent(f"""
normalization1 = normalization({norm_min}, {norm_max})
resize_input1 = resize(resize_shapes={img_size})
nms_postprocess(meta_arch={nms_cfg.get("meta_arch")}, engine={nms_cfg.get("engine")}, nms_scores_th={nms_cfg.get("nms_scores_th")}, nms_iou_th={nms_cfg.get("nms_iou_th")})
model_optimization_flavor(optimization_level=2, compression_level=1, batch_size=8)
""")
out_path = "./script.alls"
Path(out_path).write_text(alls_content.strip())
return out_path
This one for parsing the onnx to har
# src/parse_onnx.py
import os
import onnx
from onnxsim import simplify
import numpy as np
from hailo_sdk_client import ClientRunner
from PIL import Image
def load_calibration_dataset(calib_folder, output_har_filename, target_size=(640, 640)):
"""
Loads and preprocesses images from the specified folder.
Args:
calib_folder (str): Path to the folder with calibration images.
target_size (tuple): Desired image size as (width, height).
Returns:
np.ndarray: A numpy array of shape (num_images, height, width, 3) in float32.
"""
image_files = [
os.path.join(calib_folder, f)
for f in os.listdir(calib_folder)
if f.lower().endswith(('.jpg', '.png', '.jpeg'))
]
if not image_files:
raise ValueError("No calibration images found in the folder.")
images = []
for img_file in sorted(image_files):
img = Image.open(img_file).convert("RGB")
# Resize using bilinear interpolation
img = img.resize(target_size, Image.BILINEAR)
img_np = np.array(img).astype(np.float32)
images.append(img_np)
calib_dataset = np.stack(images, axis=0)
print(f"Loaded {calib_dataset.shape[0]} calibration images of size {target_size}.")
return calib_dataset
def parse_onnx(onnx_path: str, calib_folder: str, net_name: str, hw_arch: str, end_node_names, target_size: tuple = (640, 640), alls_script: str = "./script.alls"):
# -------------------------------
# Step 1. Simplify the ONNX model
# -------------------------------
# Load the ONNX model
model = onnx.load(onnx_path)
# Simplify the model
model_simp, check = simplify(model)
if not check:
raise RuntimeError("Simplified ONNX model validation failed.")
# Save the simplified model to a new file
simplified_onnx_path = os.path.splitext(onnx_path)[0] + "_simplified.onnx"
onnx.save(model_simp, simplified_onnx_path)
print(f"Simplified ONNX model saved to: {simplified_onnx_path}")
# -----------------------------------------------------
# Step 2. Translate the simplified ONNX model to Hailo format
# -----------------------------------------------------
# Create a ClientRunner instance.
# The hw_arch parameter should match your target Hailo device (e.g., "hailo8")
runner = ClientRunner(hw_arch=hw_arch, har=None)
# Translate the ONNX model. Optionally, you can supply start and end node names if needed.
hn, params = runner.translate_onnx_model(simplified_onnx_path, net_name, end_node_names=end_node_names)
print("Model translation to Hailo format completed.")
# -----------------------------------------------------
# Step 3. Quantize the model using a calibration dataset
# -----------------------------------------------------
# For quantization, you need a calibration dataset.
# Here we create a dummy calibration dataset.
# Adjust the shape (batch, height, width, channels) as required by your model.
# For many YOLO models the expected input is 640x640 with 3 channels.
calib_dataset = load_calibration_dataset(calib_folder, target_size)
print("Calibration dataset created.")
# Run optimization (quantization). This process uses the calibration dataset to
# convert floating-point parameters into their quantized (integer) counterparts.
runner.load_model_script(alls_script)
runner.optimize(calib_dataset)
print("Model quantization complete.")
# -----------------------------------------------------
# Step 4. Save the quantized model as an HAR file
# -----------------------------------------------------
har_file = f"{net_name}_quantized_model.har"
runner.save_har(har_file)
print(f"HAR file saved to: {har_file}")
return har_file
and then this one for compiling the hef
# src/compile_hef.py
from hailo_sdk_client import ClientRunner
import os
def compile_hef(har_file, build_dir, hw_arch="hailo8l",):
os.makedirs(build_dir, exist_ok=True)
runner = ClientRunner(har=har_file, hw_arch=hw_arch)
hef_data = runner.compile()
hef_file = os.path.join(build_dir, os.path.basename(har_file).split(".")[0] + ".hef")
with open(hef_file, "wb") as f:
f.write(hef_data)
print(f"HEF file saved to: {hef_file}")