# 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]])