import io import json import os import struct from pathlib import Path import pytest _DETECT_SLOW_TIMEOUT = 120 _MEDIA = os.environ.get("MEDIA_DIR", "/media") _EPS = 1e-6 _WEATHER_CLASS_STRIDE = 20 def _image_width_height(data): if len(data) >= 24 and data[:8] == b"\x89PNG\r\n\x1a\n": w, h = struct.unpack(">II", data[16:24]) return w, h if len(data) >= 2 and data[:2] == b"\xff\xd8": i = 2 while i + 1 < len(data): if data[i] != 0xFF: i += 1 continue i += 1 while i < len(data) and data[i] == 0xFF: i += 1 if i >= len(data): break m = data[i] i += 1 if m in (0xD8, 0xD9): continue if i + 3 > len(data): break seg_len = (data[i] << 8) | data[i + 1] i += 2 if m in (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7): if i + 5 > len(data): return None h = (data[i + 1] << 8) | data[i + 2] w = (data[i + 3] << 8) | data[i + 4] return w, h i += max(0, seg_len - 2) return None def _overlap_to_min_area_ratio(a, b): ox = 0.5 * (a["width"] + b["width"]) - abs(a["centerX"] - b["centerX"]) oy = 0.5 * (a["height"] + b["height"]) - abs(a["centerY"] - b["centerY"]) overlap_area = max(0.0, ox) * max(0.0, oy) aa = a["width"] * a["height"] ab = b["width"] * b["height"] m = min(aa, ab) if m <= 0: return 0.0 return overlap_area / m def _load_classes_media(): p = Path(_MEDIA) / "classes.json" if not p.is_file(): pytest.skip(f"missing {p}") raw = json.loads(p.read_text()) by_id = {} names = [] for row in raw: cid = row["Id"] by_id[cid] = float(row["MaxSizeM"]) names.append(row["Name"]) return by_id, names def _weather_label_ok(label, base_names): for n in base_names: if label == n: return True if label == n + "(Wint)" or label == n + "(Night)": return True return False @pytest.mark.slow def test_ft_p_03_detection_response_structure_ac1(image_detect, image_small, warm_engine): detections, _ = image_detect(image_small, "img.jpg") assert isinstance(detections, list) for d in detections: assert isinstance(d["centerX"], (int, float)) assert isinstance(d["centerY"], (int, float)) assert isinstance(d["width"], (int, float)) assert isinstance(d["height"], (int, float)) assert 0.0 <= float(d["centerX"]) <= 1.0 assert 0.0 <= float(d["centerY"]) <= 1.0 assert 0.0 <= float(d["width"]) <= 1.0 assert 0.0 <= float(d["height"]) <= 1.0 assert isinstance(d["classNum"], int) assert isinstance(d["label"], str) assert isinstance(d["confidence"], (int, float)) assert 0.0 <= float(d["confidence"]) <= 1.0 @pytest.mark.slow def test_ft_p_05_confidence_filtering_ac2(image_detect, image_small, warm_engine): hi, _ = image_detect(image_small, "img.jpg", config=json.dumps({"probability_threshold": 0.8})) assert isinstance(hi, list) for d in hi: assert float(d["confidence"]) + _EPS >= 0.8 lo, _ = image_detect(image_small, "img.jpg", config=json.dumps({"probability_threshold": 0.1})) assert isinstance(lo, list) assert len(lo) >= len(hi) @pytest.mark.slow def test_ft_p_06_overlap_deduplication_ac3(image_detect, image_dense, warm_engine): dets, _ = image_detect( image_dense, "img.jpg", config=json.dumps({"tracking_intersection_threshold": 0.6}), timeout=_DETECT_SLOW_TIMEOUT, ) assert isinstance(dets, list) by_label = {} for d in dets: by_label.setdefault(d["label"], []).append(d) for label, group in by_label.items(): for i in range(len(group)): for j in range(i + 1, len(group)): ratio = _overlap_to_min_area_ratio(group[i], group[j]) assert ratio <= 0.6 + _EPS, (label, ratio) strict, _ = image_detect( image_dense, "img.jpg", config=json.dumps({"tracking_intersection_threshold": 0.01}), timeout=_DETECT_SLOW_TIMEOUT, ) assert isinstance(strict, list) assert len(strict) <= len(dets) @pytest.mark.slow def test_ft_p_07_physical_size_filtering_ac4(image_detect, image_small, warm_engine): by_id, _ = _load_classes_media() wh = _image_width_height(image_small) assert wh is not None image_width_px, _ = wh altitude = 400.0 focal_length = 24.0 sensor_width = 23.5 gsd = (sensor_width * altitude) / (focal_length * image_width_px) cfg = json.dumps( { "altitude": altitude, "focal_length": focal_length, "sensor_width": sensor_width, } ) body, _ = image_detect(image_small, "img.jpg", config=cfg, timeout=_DETECT_SLOW_TIMEOUT) assert isinstance(body, list) for d in body: base_id = d["classNum"] % _WEATHER_CLASS_STRIDE assert base_id in by_id physical_width = float(d["width"]) * image_width_px * gsd assert physical_width <= by_id[base_id] + _EPS @pytest.mark.slow def test_ft_p_13_weather_mode_class_variants_ac5( image_detect, image_different_types, warm_engine ): _, base_names = _load_classes_media() body, _ = image_detect(image_different_types, "img.jpg", timeout=_DETECT_SLOW_TIMEOUT) assert isinstance(body, list) for d in body: label = d["label"] assert isinstance(label, str) assert len(label) > 0 assert _weather_label_ok(label, base_names)