Files
yolov26_3d/tests/test_engine.py

297 lines
11 KiB
Python
Raw Normal View History

2026-06-24 09:35:46 +08:00
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
import sys
from unittest import mock
import numpy as np
import torch
from tests import MODEL, SOURCE
from ultralytics import YOLO
from ultralytics.cfg import get_cfg
from ultralytics.engine.exporter import Exporter
from ultralytics.models.yolo import classify, detect, segment
from ultralytics.utils import ASSETS, DEFAULT_CFG, WEIGHTS_DIR
def test_func(*args, **kwargs):
"""Test function used as a callback stub to verify callback registration."""
print("callback test passed")
def test_export():
"""Test model exporting functionality by adding a callback and verifying its execution."""
exporter = Exporter()
exporter.add_callback("on_export_start", test_func)
assert test_func in exporter.callbacks["on_export_start"], "callback test failed"
f = exporter(model=YOLO("yolo26n.yaml").model)
YOLO(f)(SOURCE) # exported model inference
def test_detect():
"""Test YOLO object detection training, validation, and prediction functionality."""
overrides = {"data": "coco8.yaml", "model": "yolo26n.yaml", "imgsz": 32, "epochs": 1, "save": False}
cfg = get_cfg(DEFAULT_CFG)
cfg.data = "coco8.yaml"
cfg.imgsz = 32
# Trainer
trainer = detect.DetectionTrainer(overrides=overrides)
trainer.add_callback("on_train_start", test_func)
assert test_func in trainer.callbacks["on_train_start"], "callback test failed"
trainer.train()
# Validator
val = detect.DetectionValidator(args=cfg)
val.add_callback("on_val_start", test_func)
assert test_func in val.callbacks["on_val_start"], "callback test failed"
val(model=trainer.best) # validate best.pt
# Predictor
pred = detect.DetectionPredictor(overrides={"imgsz": [64, 64]})
pred.add_callback("on_predict_start", test_func)
assert test_func in pred.callbacks["on_predict_start"], "callback test failed"
# Confirm there is no issue with sys.argv being empty
with mock.patch.object(sys, "argv", []):
result = pred(source=ASSETS, model=MODEL)
assert len(result), "predictor test failed"
# Test resume functionality
overrides["resume"] = trainer.last
trainer = detect.DetectionTrainer(overrides=overrides)
try:
trainer.train()
except Exception as e:
print(f"Expected exception caught: {e}")
return
raise Exception("Resume test failed!")
def test_segment():
"""Test image segmentation training, validation, and prediction pipelines using YOLO models."""
overrides = {
"data": "coco8-seg.yaml",
"model": "yolo26n-seg.yaml",
"imgsz": 32,
"epochs": 1,
"save": False,
"mask_ratio": 1,
"overlap_mask": False,
}
cfg = get_cfg(DEFAULT_CFG)
cfg.data = "coco8-seg.yaml"
cfg.imgsz = 32
# Trainer
trainer = segment.SegmentationTrainer(overrides=overrides)
trainer.add_callback("on_train_start", test_func)
assert test_func in trainer.callbacks["on_train_start"], "callback test failed"
trainer.train()
# Validator
val = segment.SegmentationValidator(args=cfg)
val.add_callback("on_val_start", test_func)
assert test_func in val.callbacks["on_val_start"], "callback test failed"
val(model=trainer.best) # validate best.pt
# Predictor
pred = segment.SegmentationPredictor(overrides={"imgsz": [64, 64]})
pred.add_callback("on_predict_start", test_func)
assert test_func in pred.callbacks["on_predict_start"], "callback test failed"
result = pred(source=ASSETS, model=WEIGHTS_DIR / "yolo26n-seg.pt")
assert len(result), "predictor test failed"
# Test resume functionality
overrides["resume"] = trainer.last
trainer = segment.SegmentationTrainer(overrides=overrides)
try:
trainer.train()
except Exception as e:
print(f"Expected exception caught: {e}")
return
raise Exception("Resume test failed!")
def test_classify():
"""Test image classification including training, validation, and prediction phases."""
overrides = {"data": "imagenet10", "model": "yolo26n-cls.yaml", "imgsz": 32, "epochs": 1, "save": False}
cfg = get_cfg(DEFAULT_CFG)
cfg.data = "imagenet10"
cfg.imgsz = 32
# Trainer
trainer = classify.ClassificationTrainer(overrides=overrides)
trainer.add_callback("on_train_start", test_func)
assert test_func in trainer.callbacks["on_train_start"], "callback test failed"
trainer.train()
# Validator
val = classify.ClassificationValidator(args=cfg)
val.add_callback("on_val_start", test_func)
assert test_func in val.callbacks["on_val_start"], "callback test failed"
val(model=trainer.best)
# Predictor
pred = classify.ClassificationPredictor(overrides={"imgsz": [64, 64]})
pred.add_callback("on_predict_start", test_func)
assert test_func in pred.callbacks["on_predict_start"], "callback test failed"
result = pred(source=ASSETS, model=trainer.best)
assert len(result), "predictor test failed"
def test_nan_recovery():
"""Test NaN loss detection and recovery during training."""
nan_injected = [False]
def inject_nan(trainer):
"""Inject NaN into loss during batch processing to test recovery mechanism."""
if trainer.epoch == 1 and trainer.tloss is not None and not nan_injected[0]:
trainer.tloss *= torch.tensor(float("nan"))
nan_injected[0] = True
overrides = {"data": "coco8.yaml", "model": "yolo26n.yaml", "imgsz": 32, "epochs": 3}
trainer = detect.DetectionTrainer(overrides=overrides)
trainer.add_callback("on_train_batch_end", inject_nan)
trainer.train()
assert nan_injected[0], "NaN injection failed"
def test_ground3d_validator_prints_combined_metrics(caplog):
"""Test Ground3DDetectionValidator prints whole and face 3D metrics."""
from ultralytics.models.yolo.detect.train import Ground3DDetectionValidator
validator = Ground3DDetectionValidator(args=get_cfg(DEFAULT_CFG))
validator.names = {0: "car"}
validator.args.task = "detect"
validator.args.verbose = False
validator.training = True
validator.seen = 3531
validator.nc = 1
validator.stats = []
validator.metrics.nt_per_class = torch.tensor([59691])
validator.metrics.mean_results = lambda: [0.73, 0.585, 0.657, 0.499]
validator.metrics.keys = ["metrics/precision(B)", "metrics/recall(B)", "metrics/mAP50(B)", "metrics/mAP50-95(B)"]
validator.metrics_3d_results = {
"whole": {
"depth_abs": 48.72,
"depth_rel": 1.915,
"depth_rmse": 54.22,
"center": 52.01,
"uv": 17.35,
"orient": 85.47,
"size": 2.011,
"matched": 5738,
},
"face": {
"depth_abs": 12.31,
"depth_rel": 0.441,
"depth_rmse": 18.22,
"center": 14.07,
"uv": 9.56,
"matched": 2814,
},
}
with caplog.at_level("INFO", logger="ultralytics"):
validator.print_results()
all_lines = [record.message for record in caplog.records if "all" in record.message]
assert len(all_lines) == 1
all_line = all_lines[0]
assert "0.73" in all_line
assert "48.7" in all_line
assert "1.92" in all_line
assert "54.2" in all_line
assert "52" in all_line
assert "17.4" in all_line
assert "85.5" in all_line
assert "2.01" in all_line
assert "5738" in all_line
face_lines = [record.message for record in caplog.records if "3d-face" in record.message]
assert len(face_lines) == 1
face_line = face_lines[0]
assert "12.3" in face_line
assert "0.441" in face_line
assert "18.2" in face_line
assert "14.1" in face_line
assert "9.56" in face_line
assert "2814" in face_line
def test_ground3d_validator_prints_invalid_visible_yaw_as_dash(caplog):
"""Test Ground3DDetectionValidator renders invalid visible-yaw metrics as '-'."""
from ultralytics.models.yolo.detect.train import Ground3DDetectionValidator
validator = Ground3DDetectionValidator(args=get_cfg(DEFAULT_CFG))
validator.names = {0: "car"}
validator.args.task = "detect"
validator.args.verbose = False
validator.training = True
validator.seen = 1
validator.nc = 1
validator.stats = []
validator.metrics.nt_per_class = torch.tensor([1])
validator.metrics.mean_results = lambda: [0.73, 0.585, 0.657, 0.499]
validator.metrics.keys = ["metrics/precision(B)", "metrics/recall(B)", "metrics/mAP50(B)", "metrics/mAP50-95(B)"]
validator.metrics_3d_results = {
"whole": {
"depth_abs": 1.0,
"depth_rel": 0.1,
"depth_rmse": 1.5,
"center": 2.0,
"uv": 3.0,
"orient": 4.0,
"size": 0.5,
"matched": 1,
},
"face": {
"depth_abs": 2.0,
"depth_rel": 0.2,
"depth_rmse": 2.5,
"center": 4.0,
"uv": 5.0,
"size": 0.6,
"direct_orient_visible": np.nan,
"edge_orient_visible": np.nan,
"matched": 1,
},
}
with caplog.at_level("INFO", logger="ultralytics"):
validator.print_results()
all_lines = [record.message for record in caplog.records if "all-3d" in record.message]
assert len(all_lines) == 1
assert " nan" not in all_lines[0]
assert all_lines[0].rstrip().endswith("-")
def test_detect3d_select_topk_3d_metadata_keeps_batch_alignment():
"""Test Detect3D keeps selected 3D predictions, anchors, and strides aligned per sample."""
from ultralytics.nn.modules.head import Detect3D
detect3d = Detect3D(nc=2, reg_max=1, end2end=True, ch=(16,))
detect3d.anchors = torch.tensor([[0.5, 1.5, 2.5, 3.5], [10.5, 11.5, 12.5, 13.5]], dtype=torch.float32)
detect3d.strides = torch.tensor([[8.0, 16.0, 32.0, 64.0]], dtype=torch.float32)
preds_3d = torch.arange(2 * detect3d.no_3d * 4, dtype=torch.float32).reshape(2, detect3d.no_3d, 4)
idx = torch.tensor([[[0], [2]], [[1], [3]]], dtype=torch.long)
preds_sel, anchors_sel, strides_sel = detect3d._select_topk_3d_metadata(preds_3d, idx)
assert preds_sel.shape == (2, 2, detect3d.no_3d)
assert anchors_sel.shape == (2, 2, 2)
assert strides_sel.shape == (2, 2)
torch.testing.assert_close(preds_sel[0, 0], preds_3d[0, :, 0])
torch.testing.assert_close(preds_sel[0, 1], preds_3d[0, :, 2])
torch.testing.assert_close(preds_sel[1, 0], preds_3d[1, :, 1])
torch.testing.assert_close(preds_sel[1, 1], preds_3d[1, :, 3])
torch.testing.assert_close(anchors_sel[0], detect3d.anchors[:, [0, 2]])
torch.testing.assert_close(anchors_sel[1], detect3d.anchors[:, [1, 3]])
torch.testing.assert_close(strides_sel[0], detect3d.strides[0, [0, 2]])
torch.testing.assert_close(strides_sel[1], detect3d.strides[0, [1, 3]])