"""YOLO 标注格式的解析与写出。""" from pathlib import Path import pytest pytest.importorskip("numpy") import numpy as np # noqa: E402 from datasets.yolo import ( # noqa: E402 denormalize, format_label_lines, label_path_for, list_images, load_classes, parse_label_file, write_data_yaml, ) def test_label_path_swaps_images_for_labels(): img_dir = Path("/data/film/images/train") assert label_path_for(img_dir / "0001.png", img_dir) == Path("/data/film/labels/train/0001.txt") def test_label_path_keeps_subdirs_and_honours_explicit_dir(): img_dir = Path("/data/film/images/train") assert label_path_for(img_dir / "cam1/0001.png", img_dir) == Path( "/data/film/labels/train/cam1/0001.txt" ) assert label_path_for(img_dir / "0001.png", img_dir, Path("/tmp/lab")) == Path("/tmp/lab/0001.txt") def test_parse_center_format(tmp_path): path = tmp_path / "a.txt" path.write_text("0 0.5 0.5 0.4 0.2\n# 注释\n\n1 0.25 0.25 0.1 0.1\n", encoding="utf-8") boxes, labels = parse_label_file(path) assert labels.tolist() == [0, 1] assert boxes[0].tolist() == pytest.approx([0.3, 0.4, 0.7, 0.6]) assert denormalize(boxes, 100, 200)[0].tolist() == pytest.approx([30.0, 80.0, 70.0, 120.0]) def test_missing_file_means_no_defect(tmp_path): boxes, labels = parse_label_file(tmp_path / "nope.txt") assert boxes.shape == (0, 4) and labels.shape == (0,) def test_polygon_rows_fall_back_to_bounding_box(tmp_path): path = tmp_path / "a.txt" path.write_text("2 0.1 0.1 0.4 0.2 0.3 0.5\n", encoding="utf-8") boxes, labels = parse_label_file(path) assert labels.tolist() == [2] assert boxes[0].tolist() == pytest.approx([0.1, 0.1, 0.4, 0.5]) def test_coordinates_are_clamped_and_degenerate_rows_dropped(tmp_path): path = tmp_path / "a.txt" path.write_text("0 0.5 0.5 2.0 2.0\n0 0.5 0.5 0.0 0.0\n", encoding="utf-8") boxes, labels = parse_label_file(path) assert len(labels) == 1 assert boxes[0].tolist() == pytest.approx([0.0, 0.0, 1.0, 1.0]) def test_malformed_rows_report_file_and_line(tmp_path): path = tmp_path / "a.txt" path.write_text("0 0.5 0.5 0.4\n", encoding="utf-8") with pytest.raises(ValueError, match=r"a\.txt:1"): parse_label_file(path) path.write_text("scratch 0.5 0.5 0.4 0.2\n", encoding="utf-8") with pytest.raises(ValueError, match="不是合法的 YOLO 标注行"): parse_label_file(path) def test_class_id_range_is_checked(tmp_path): path = tmp_path / "a.txt" path.write_text("3 0.5 0.5 0.4 0.2\n", encoding="utf-8") with pytest.raises(ValueError, match="越界"): parse_label_file(path, num_classes=2) def test_format_lines_roundtrips_pixel_boxes(tmp_path): boxes = np.array([[10.0, 20.0, 50.0, 60.0]]) lines = format_label_lines(boxes, [1], width=100, height=200) assert lines == ["1 0.300000 0.200000 0.400000 0.200000"] path = tmp_path / "a.txt" path.write_text("\n".join(lines), encoding="utf-8") parsed, labels = parse_label_file(path) assert labels.tolist() == [1] assert denormalize(parsed, 100, 200)[0].tolist() == pytest.approx([10.0, 20.0, 50.0, 60.0]) def test_data_yaml_roundtrip(tmp_path): pytest.importorskip("yaml") path = write_data_yaml(tmp_path / "data.yaml", ["scratch", "bubble"], root=tmp_path) assert load_classes(path) == ["scratch", "bubble"] assert load_classes(["a", "b"]) == ["a", "b"] def test_list_images_supports_dir_and_list_file(tmp_path): images = tmp_path / "images" images.mkdir() for name in ("b.png", "a.jpg", "notes.txt"): (images / name).write_bytes(b"") assert [p.name for p in list_images(images)] == ["a.jpg", "b.png"] list_file = tmp_path / "train.txt" list_file.write_text("images/b.png\nimages/a.jpg\n", encoding="utf-8") assert [p.name for p in list_images(images, list_file)] == ["b.png", "a.jpg"]