"""Tests for CsvSource CSV loader.""" from __future__ import annotations import tempfile import unittest from pathlib import Path import numpy as np import sys sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from wafer_edge.Calibration import BaselineCalibration from wafer_edge.CsvSource import find_session_files, load_camera_records_from_csv, parse_csv_filename BASELINE = BaselineCalibration() MASTER_SD = Path(__file__).resolve().parents[1] / "MASTER_SD" DATA_DIR = Path(__file__).resolve().parents[1] / "DATA" / "0610" class CsvSourceTests(unittest.TestCase): """Test CSV loader correctness and robustness.""" def test_point_count_matches_header(self) -> None: """Test 1: load S10519P0.CSV, assert parser reads all data rows.""" # Read raw point count from file header (before filtering) with open(MASTER_SD / "S10519P0.CSV") as f: for line in f: if line.startswith("# points,"): raw_count = int(line.split(",")[1].strip()) break # After filter_ring_outliers, non-wafer edges are stripped # but a significant fraction should remain for a good capture paths = {"cam-1": MASTER_SD / "S10519P0.CSV"} records = load_camera_records_from_csv(paths, BASELINE) self.assertEqual(len(records), 1) self.assertEqual(records[0].camera_id, "cam-1") filtered = len(records[0].points) self.assertGreater(filtered, 0, "No points loaded from CSV") self.assertGreaterEqual(raw_count, 100, f"Expected >= 100 raw points, got {raw_count}") # Filtered should be a reasonable fraction (at least 5% of raw) self.assertGreaterEqual(filtered, raw_count * 0.05, f"Filtered {filtered} << raw {raw_count}") def test_mm_values_in_reasonable_range(self) -> None: """Test 2: assert all mm values are within ±100 mm (physically reasonable).""" paths = {"cam-1": MASTER_SD / "S10519P0.CSV"} records = load_camera_records_from_csv(paths, BASELINE) pts = np.array(records[0].points) # All coordinates should be within ±100 mm for a 75mm radius wafer self.assertTrue(np.all(np.abs(pts) < 100.0), f"Out-of-range values: min={pts.min():.1f}, max={pts.max():.1f}") def test_four_cameras_returns_four_records(self) -> None: """Test 3: 4 paths → 4 records with correct camera IDs.""" paths = { "cam-1": MASTER_SD / "S10540P0.CSV", "cam-2": MASTER_SD / "S20540P0.CSV", "cam-3": MASTER_SD / "R10540P0.CSV", "cam-4": MASTER_SD / "R20539P0.CSV", } records = load_camera_records_from_csv(paths, BASELINE) self.assertEqual(len(records), 4) record_ids = {r.camera_id for r in records} self.assertEqual(record_ids, {"cam-1", "cam-2", "cam-3", "cam-4"}) # All should have points and ok status for r in records: self.assertGreater(len(r.points), 0, f"{r.camera_id} has no points") self.assertEqual(r.status, "ok") # --------------------------------------------------------------------------- # find_session_files tests # --------------------------------------------------------------------------- def test_find_session_files_complete(self) -> None: """All 4 cameras present → dict with 4 entries, correct labels.""" result = find_session_files(DATA_DIR) self.assertEqual(len(result), 4) self.assertIn("cam-1 (S1)", result) self.assertIn("cam-2 (S2)", result) self.assertIn("cam-3 (R1)", result) self.assertIn("cam-4 (R2)", result) for label, path in result.items(): self.assertTrue(path.exists(), f"{label} → {path}") def test_find_session_files_partial(self) -> None: """Only 2 cameras present → returns dict with 2 entries, no error.""" with tempfile.TemporaryDirectory() as tmp: tmpdir = Path(tmp) for name in ("S10610P0.CSV", "R10610P0.CSV"): (tmpdir / name).write_text( (DATA_DIR / name).read_text() ) result = find_session_files(tmpdir) self.assertEqual(len(result), 2) self.assertIn("cam-1 (S1)", result) self.assertIn("cam-3 (R1)", result) self.assertNotIn("cam-2 (S2)", result) self.assertNotIn("cam-4 (R2)", result) def test_find_session_files_empty_folder(self) -> None: """No CSVs at all → empty dict, no exception.""" with tempfile.TemporaryDirectory() as tmp: result = find_session_files(Path(tmp)) self.assertEqual(result, {}) def test_find_session_files_bad_filename(self) -> None: """Non-NanoBerry file is silently skipped.""" with tempfile.TemporaryDirectory() as tmp: tmpdir = Path(tmp) (tmpdir / "README.TXT").write_text("hello") (tmpdir / "S10610P0.CSV").write_text( (DATA_DIR / "S10610P0.CSV").read_text() ) result = find_session_files(tmpdir) self.assertEqual(len(result), 1) self.assertIn("cam-1 (S1)", result) def test_find_session_files_new_format(self) -> None: """Underscore-style filenames (R1_0540_P0.CSV) are also discovered.""" with tempfile.TemporaryDirectory() as tmp: tmpdir = Path(tmp) (tmpdir / "S1_0610_P0.CSV").write_text( (DATA_DIR / "S10610P0.CSV").read_text() ) result = find_session_files(tmpdir) self.assertEqual(len(result), 1) self.assertIn("cam-1 (S1)", result) if __name__ == "__main__": unittest.main()