| | |
| | | validation = next(item for item in runs if item["id"] == "validation-normal-20260821-v2") |
| | | self.assertTrue(validation["artifactRoot"].startswith("shared/outputs/05-3d-pointcloud/")) |
| | | |
| | | def test_trained_model_inference_is_discovered_as_semantic_case_only(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | inference = root / "shared" / "outputs" / "05-3d-pointcloud" / "model-inference-runs" / "semantic-inference-test" |
| | | automatic = root / "shared" / "outputs" / "05-3d-pointcloud" / "auto-annotation-runs" / "automatic-test" |
| | | for directory in (inference, automatic): |
| | | directory.mkdir(parents=True) |
| | | for name in ("predicted-semantic-preview.ply", "predicted-semantic-classified.las", "prediction-summary.json", "class-counts.csv"): |
| | | (directory / name).write_bytes(b"artifact") |
| | | (directory / "run_metadata.json").write_text(json.dumps({ |
| | | "capability": "05-3d-pointcloud", "created_at": "2026-08-31T00:00:00+00:00", |
| | | "input": {"mesh_file": "new-scene.ply"}, "model": {"path": "training-runs/model/model.pt"}, |
| | | "prediction": {"class_counts": {"5": 10}}, |
| | | "artifacts": {"preview": "predicted-semantic-preview.ply", "classified_las": "predicted-semantic-classified.las", "summary": "prediction-summary.json", "class_counts": "class-counts.csv"}, |
| | | }), encoding="utf-8") |
| | | runs = MODULE.pointcloud_runs(root) |
| | | self.assertEqual([item["id"] for item in runs], ["semantic-inference-test"]) |
| | | self.assertEqual(runs[0]["kind"], "trained-model-inference") |
| | | |
| | | def test_trained_model_semantic_case_has_a_limited_removal_plan(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | run_id = "semantic-inference-test" |
| | | output = root / "shared" / "outputs" / "05-3d-pointcloud" / "model-inference-runs" / run_id |
| | | raw = root / "shared" / "data" / "raw" / "05-3d-pointcloud" / "model-inference-runs" / run_id |
| | | processed = root / "shared" / "data" / "processed" / "05-3d-pointcloud" / "model-inference-runs" / run_id |
| | | for directory in (output, raw, processed): |
| | | directory.mkdir(parents=True) |
| | | for name in ("predicted-semantic-preview.ply", "predicted-semantic-classified.las", "prediction-summary.json", "class-counts.csv"): |
| | | (output / name).write_bytes(b"artifact") |
| | | (output / "run_metadata.json").write_text(json.dumps({ |
| | | "capability": "05-3d-pointcloud", "input": {"path": "new-scene.las"}, |
| | | "model": {"path": "training-runs/model/model.pt"}, "prediction": {"class_counts": {"5": 10}}, |
| | | "artifacts": {"preview": "predicted-semantic-preview.ply", "classified_las": "predicted-semantic-classified.las", "summary": "prediction-summary.json", "class_counts": "class-counts.csv"}, |
| | | }), encoding="utf-8") |
| | | external = root / "baseData" / "new-scene.las"; external.parent.mkdir(); external.write_bytes(b"keep") |
| | | plan = MODULE.run_deletion_plan(root, "05-3d-pointcloud", run_id) |
| | | self.assertTrue(plan["removable"]) |
| | | self.assertEqual(plan["outputDirectories"], [f"shared/outputs/05-3d-pointcloud/model-inference-runs/{run_id}"]) |
| | | self.assertEqual(plan["rawDirectories"], [f"shared/data/raw/05-3d-pointcloud/model-inference-runs/{run_id}"]) |
| | | self.assertEqual(plan["processedDirectories"], [f"shared/data/processed/05-3d-pointcloud/model-inference-runs/{run_id}"]) |
| | | MODULE.delete_console_run(root, "05-3d-pointcloud", run_id) |
| | | self.assertFalse(output.exists()); self.assertFalse(raw.exists()); self.assertFalse(processed.exists()) |
| | | self.assertEqual(external.read_bytes(), b"keep") |
| | | |
| | | def test_trained_model_human_review_ply_is_discoverable_for_human_review(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | run_id = "semantic-inference-review" |
| | | artifact = root / "shared" / "outputs" / "05-3d-pointcloud" / "model-inference-runs" / run_id |
| | | artifact.mkdir(parents=True) |
| | | classified = artifact / "predicted-semantic-classified.las" |
| | | classified.write_bytes(b"classified-with-observed-rgb") |
| | | review = artifact / "human-review-source.ply" |
| | | review.write_text( |
| | | "ply\nformat ascii 1.0\nelement vertex 42\nproperty float x\nproperty float y\nproperty float z\nend_header\n", |
| | | encoding="ascii", |
| | | ) |
| | | (artifact / "run_metadata.json").write_text(json.dumps({ |
| | | "capability": "05-3d-pointcloud", "input": {"path": "new-scene.las"}, |
| | | "prediction": {"input_points": 42}, "artifacts": {"classified_las": classified.name, "human_review_source": review.name}, |
| | | "human_review_source": {"file": review.name, "sha256": MODULE.file_sha256(review), "point_count": 42}, |
| | | }), encoding="utf-8") |
| | | sources = MODULE.model_inference_annotation_sources(root) |
| | | self.assertEqual([item["id"] for item in sources], [f"{run_id}:{review.name}"]) |
| | | self.assertEqual(sources[0]["pointCount"], 42) |
| | | self.assertTrue(sources[0]["sourceHasRgb"]) |
| | | |
| | | def test_multiview_annotation_source_requires_complete_ordered_contract(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | |
| | | dataset.unlink() |
| | | self.assertEqual(MODULE.pointcloud_annotation_sources(root), []) |
| | | |
| | | def test_pointcloud_concentrated_training_accepts_one_revision_per_rgb_source(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | annotation_root = root / "shared" / "outputs" / "05-3d-pointcloud" / "annotations" |
| | | records = [] |
| | | for index in range(2): |
| | | annotation_id = f"annotation-source-{index}" |
| | | location = annotation_root / annotation_id |
| | | location.mkdir(parents=True) |
| | | source_id = f"source-{index}" |
| | | (location / "annotation.json").write_text(json.dumps({"schema_version": 1, "id": annotation_id, "source_id": source_id}), encoding="utf-8") |
| | | records.append({"id": source_id, "artifactRoot": f"shared/outputs/05-3d-pointcloud/runs/source-{index}", "sourceHasRgb": True}) |
| | | handler = object.__new__(MODULE.WorkbenchConsoleHandler) |
| | | handler.directory = str(root) |
| | | execution = {"python": "fixed-python", "device": "cpu", "environment": "05-3d-pointcloud", "torchVersion": "2.13.0+cpu", "requestedDevice": "cpu", "fallbackUsed": False, "fallbackReason": None} |
| | | with mock.patch.object(MODULE, "pointcloud_annotation_sources", return_value=records), mock.patch.object(MODULE, "pointcloud_execution_environment", return_value=execution), mock.patch.object(MODULE.threading, "Thread") as thread: |
| | | thread.return_value.start.return_value = None |
| | | job = handler.create_pointcloud_training_run({"annotationIds": ["annotation-source-0", "annotation-source-1"], "device": "cpu"}) |
| | | self.assertEqual(job["trainer"], "rgb_xyz_baseline") |
| | | self.assertEqual(job["sourceCount"], 2) |
| | | self.assertEqual(job["annotationIds"], ["annotation-source-0", "annotation-source-1"]) |
| | | self.assertEqual(len(thread.call_args.kwargs["args"][2]), 2) |
| | | with self.assertRaisesRegex(MODULE.ApiError, "each annotation revision only once"): |
| | | handler.create_pointcloud_training_run({"annotationIds": ["annotation-source-0", "annotation-source-0"], "device": "cpu"}) |
| | | |
| | | def test_pointcloud_request_requires_allowlisted_files(self) -> None: |
| | | handler = self.make_handler() |
| | | with self.assertRaisesRegex(MODULE.ApiError, "PLY, PCD, XYZ, LAS, or LAZ"): |
| | |
| | | handler.create_pointcloud_annotation_class({"key": "Transformer", "label": "变压器", "color": [30, 144, 255]}) |
| | | with self.assertRaisesRegex(MODULE.ApiError, "RGB"): |
| | | handler.create_pointcloud_annotation_class({"key": "transformer", "label": "变压器", "color": [999, 1, 1]}) |
| | | |
| | | def test_annotation_taxonomy_color_update_preserves_stable_class_metadata(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | handler = object.__new__(MODULE.WorkbenchConsoleHandler) |
| | | handler.directory = str(root) |
| | | before = next(item for item in MODULE.annotation_classes(root) if item["code"] == 15) |
| | | annotation = root / "shared" / "outputs" / "05-3d-pointcloud" / "annotations" / "annotation-used" |
| | | annotation.mkdir(parents=True) |
| | | record = {"labels": [[0, 15]], "class_schema": {"15": before}} |
| | | path = annotation / "annotation.json" |
| | | path.write_text(json.dumps(record), encoding="utf-8") |
| | | |
| | | updated = handler.update_pointcloud_annotation_class_color(15, {"color": [12, 34, 56]}) |
| | | |
| | | self.assertEqual(updated["color"], [12, 34, 56]) |
| | | self.assertEqual({key: updated[key] for key in ("code", "key", "label", "builtIn")}, {key: before[key] for key in ("code", "key", "label", "builtIn")}) |
| | | self.assertEqual(json.loads(path.read_text(encoding="utf-8")), record) |
| | | with self.assertRaisesRegex(MODULE.ApiError, "RGB"): |
| | | handler.update_pointcloud_annotation_class_color(15, {"color": [256, 0, 0]}) |
| | | |
| | | def test_preview_only_annotation_source_is_discovered_without_geometry_artifacts(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | |
| | | sources = MODULE.pointcloud_annotation_sources(root) |
| | | self.assertEqual([item["id"] for item in sources], [f"{run_id}:{preview.name}"]) |
| | | self.assertTrue(sources[0]["sourceHasRgb"]) |
| | | |
| | | def test_textured_mesh_header_requires_declared_uv_texture_tiles(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | mesh = Path(temp_dir) / "BlockBA.ply" |
| | | mesh.write_bytes( |
| | | b"ply\nformat binary_little_endian 1.0\ncomment TextureFile BlockBA_0_0.jpg\n" |
| | | b"element vertex 3\nproperty float x\nproperty float y\nproperty float z\n" |
| | | b"element face 1\nproperty list uchar uint vertex_indices\n" |
| | | b"property list uchar float texcoord\nproperty int texnumber\nend_header\n" |
| | | ) |
| | | self.assertEqual(MODULE.textured_mesh_texture_names(mesh), ["BlockBA_0_0.jpg"]) |
| | | mesh.write_bytes(b"ply\nformat binary_little_endian 1.0\nelement vertex 3\nend_header\n") |
| | | with self.assertRaisesRegex(MODULE.ApiError, "TextureFile"): |
| | | MODULE.textured_mesh_texture_names(mesh) |
| | | |
| | | def test_textured_model_inference_requires_exact_declared_texture_tiles(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | model = root / "shared" / "outputs" / "05-3d-pointcloud" / "training-runs" / "semantic-good" |
| | | model.mkdir(parents=True) |
| | | (model / "model.pt").write_bytes(b"weights") |
| | | (model / "metrics.json").write_text(json.dumps({ |
| | | "capability": "05-3d-pointcloud", "classification": "B", "classes": { |
| | | "5": {"key": "vegetation", "label": "Vegetation", "color": [1, 2, 3]}, |
| | | "16": {"key": "power_line", "label": "Power line", "color": [4, 5, 6]}, |
| | | }, |
| | | }), encoding="utf-8") |
| | | mesh_upload = root / "shared" / "data" / "raw" / "05-3d-pointcloud" / "uploads" / ("a" * 32) |
| | | texture_upload = root / "shared" / "data" / "raw" / "05-3d-pointcloud" / "uploads" / ("b" * 32) |
| | | mesh_upload.mkdir(parents=True); texture_upload.mkdir(parents=True) |
| | | (mesh_upload / "pointcloud.ply").write_bytes( |
| | | b"ply\nformat binary_little_endian 1.0\ncomment TextureFile expected.jpg\n" |
| | | b"element vertex 3\nproperty float x\nproperty float y\nproperty float z\n" |
| | | b"element face 1\nproperty list uchar uint vertex_indices\n" |
| | | b"property list uchar float texcoord\nproperty int texnumber\nend_header\n" |
| | | ) |
| | | (mesh_upload / "pointcloud.json").write_text(json.dumps({"role": "pointcloud", "name": "mesh.ply"}), encoding="utf-8") |
| | | (texture_upload / "texture.jpg").write_bytes(b"jpeg") |
| | | (texture_upload / "texture.json").write_text(json.dumps({"role": "texture", "name": "unexpected.jpg"}), encoding="utf-8") |
| | | handler = object.__new__(MODULE.WorkbenchConsoleHandler) |
| | | handler.directory = str(root) |
| | | with self.assertRaisesRegex(MODULE.ApiError, "must exactly match"): |
| | | handler.create_textured_mesh_model_inference_run({ |
| | | "modelId": "semantic-good", "pointCloud": {"uploadId": "a" * 32}, |
| | | "textures": [{"uploadId": "b" * 32}], |
| | | }) |
| | | |
| | | def test_annotation_source_removal_deletes_only_its_complete_local_chain(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | |
| | | self.assertFalse(revision.exists()) |
| | | self.assertEqual(source_sentinel.read_bytes(), b"source-must-remain") |
| | | |
| | | def test_pointcloud_task_registry_marks_unfinished_work_interrupted_after_restart(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | registry = MODULE.pointcloud_task_registry(root) |
| | | registry.write("training", {"id": "task-1", "runId": "semantic-model-1", "status": "running", "stage": "training", "createdAt": "2026-09-01T00:00:00+00:00"}) |
| | | self.assertEqual(registry.reconcile_startup(), 1) |
| | | task = registry.get("task-1") |
| | | self.assertEqual(task["status"], "interrupted") |
| | | self.assertIn("restarted", task["error"]) |
| | | |
| | | def test_pointcloud_artifact_health_reports_missing_and_valid_artifacts(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | valid = root / "shared" / "outputs" / "05-3d-pointcloud" / "training-runs" / "valid-model" |
| | | invalid = valid.parent / "broken-model" |
| | | valid.mkdir(parents=True); invalid.mkdir(parents=True) |
| | | (valid / "metrics.json").write_text(json.dumps({"capability": "05-3d-pointcloud"}), encoding="utf-8") |
| | | (valid / "model.pt").write_bytes(b"weights") |
| | | (valid / "predicted-semantic-preview.ply").write_bytes(b"preview") |
| | | (invalid / "metrics.json").write_text("{bad", encoding="utf-8") |
| | | health = MODULE.pointcloud_artifact_health(root) |
| | | records = {(item["kind"], item["id"]): item for item in health["records"]} |
| | | self.assertEqual(records[("training_model", "valid-model")]["status"], "healthy") |
| | | self.assertEqual(records[("training_model", "broken-model")]["status"], "invalid") |
| | | self.assertIn("model.pt", records[("training_model", "broken-model")]["missing"]) |
| | | |
| | | def test_pointcloud_auto_annotation_merge_preview_counts_review_and_human_overrides(self) -> None: |
| | | with tempfile.TemporaryDirectory() as temp_dir: |
| | | root = Path(temp_dir) |
| | | run_id, source_id = "auto-test", "source:preview.ply" |
| | | artifact = root / "shared" / "outputs" / "05-3d-pointcloud" / "auto-annotation-runs" / run_id |
| | | artifact.mkdir(parents=True) |
| | | (artifact / "automatic-annotation-candidates.json").write_text(json.dumps({"schema_version": 1, "source_id": source_id, "source_sha256": "source-hash", "labels": [[0, 5, 0.99], [1, 15, 0.99], [2, 16, 0.99]]}), encoding="utf-8") |
| | | (artifact / "review-corrections.json").write_text(json.dumps({"schema_version": 1, "run_id": run_id, "source_id": source_id, "source_sha256": "source-hash", "corrections": [[0, 0], [1, 16], [3, 5]]}), encoding="utf-8") |
| | | base = root / "shared" / "outputs" / "05-3d-pointcloud" / "annotations" / "human-1" |
| | | base.mkdir(parents=True) |
| | | (base / "annotation.json").write_text(json.dumps({"source_id": source_id, "labels": [[1, 15], [2, 5]]}), encoding="utf-8") |
| | | source = {"id": source_id, "sha256": "source-hash", "pointCount": 4} |
| | | classes = [{"code": 5}, {"code": 15}, {"code": 16}] |
| | | with mock.patch.object(MODULE, "pointcloud_annotation_source", return_value=source), mock.patch.object(MODULE, "annotation_classes", return_value=classes): |
| | | preview = MODULE.pointcloud_auto_annotation_merge_preview(root, run_id, source_id, "human-1") |
| | | self.assertEqual(preview["highConfidenceCandidateCount"], 3) |
| | | self.assertEqual(preview["rejectedCandidateCount"], 1) |
| | | self.assertEqual(preview["reclassifiedCandidateCount"], 2) |
| | | self.assertEqual(preview["baseHumanOverrideCount"], 2) |
| | | self.assertEqual(preview["classCounts"], {"5": 2, "15": 1}) |
| | | |
| | | def test_risk_rule_validation_run_is_discovered(self) -> None: |
| | | runs = MODULE.risk_rule_runs(ROOT) |
| | | validation = next(item for item in runs if item["id"] == "validation-normal-20260820") |