Skip to content

Commit 5f069d5

Browse files
authored
Merge pull request #936 from mprib/feature/presenter-layer-cleanup
refactor(presenter): cleanup naming, domain logic, and indirection
2 parents 805efd2 + 3491627 commit 5f069d5

File tree

7 files changed

+107
-346
lines changed

7 files changed

+107
-346
lines changed

src/caliscope/core/point_data_bundle.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
apply_similarity_transform,
2929
SimilarityTransform,
3030
)
31+
from caliscope.core.scale_accuracy import (
32+
compute_scale_accuracy as compute_scale_accuracy_impl,
33+
ScaleAccuracyData,
34+
)
3135

3236
import pandas as pd
3337

@@ -512,6 +516,65 @@ def filter_by_percentile_error(
512516

513517
return self._filter_by_reprojection_thresholds(thresholds, min_per_camera)
514518

519+
def compute_scale_accuracy(self, sync_index: int) -> ScaleAccuracyData:
520+
"""Compute scale accuracy comparing triangulated points to known object geometry.
521+
522+
Compares triangulated world points at the given sync_index to their
523+
corresponding ground truth object positions (from obj_loc columns) to
524+
assess reconstruction accuracy. Uses ALL pairwise distances between
525+
detected corners for robust statistical measurement.
526+
527+
Args:
528+
sync_index: Frame index to compute accuracy at
529+
530+
Returns:
531+
ScaleAccuracyData with distance RMSE and relative error
532+
533+
Raises:
534+
ValueError: If insufficient matched points at sync_index (< 2)
535+
"""
536+
# Extract data at sync_index
537+
img_df = self.image_points.df
538+
world_df = self.world_points.df
539+
540+
img_subset = img_df[img_df["sync_index"] == sync_index]
541+
world_subset = world_df[world_df["sync_index"] == sync_index]
542+
543+
if img_subset.empty:
544+
raise ValueError(f"No image observations at sync_index {sync_index}")
545+
if world_subset.empty:
546+
raise ValueError(f"No world points at sync_index {sync_index}")
547+
548+
# Get image points with object locations at reference frame
549+
# Use drop_duplicates on img_subset since multiple cameras may see same point_id
550+
obj_points_df = img_subset[["point_id", "obj_loc_x", "obj_loc_y", "obj_loc_z"]].drop_duplicates(
551+
subset=["point_id"]
552+
)
553+
554+
# Merge world points with object locations by point_id
555+
merged = world_subset.merge(obj_points_df, on="point_id", how="inner")
556+
557+
if len(merged) < 2:
558+
raise ValueError(f"Insufficient matched points for scale accuracy: {len(merged)} (need at least 2)")
559+
560+
# Handle planar objects (z=0 or NaN)
561+
if merged["obj_loc_z"].isna().all():
562+
merged = merged.copy()
563+
merged["obj_loc_z"] = 0.0
564+
565+
# Filter out any remaining NaN values
566+
valid_mask = ~merged[["obj_loc_x", "obj_loc_y", "obj_loc_z"]].isna().any(axis=1)
567+
merged = merged[valid_mask]
568+
569+
if len(merged) < 2:
570+
raise ValueError("Insufficient valid points after NaN filtering (need at least 2)")
571+
572+
# Extract arrays for scale accuracy computation
573+
world_points = merged[["x_coord", "y_coord", "z_coord"]].to_numpy()
574+
object_points = merged[["obj_loc_x", "obj_loc_y", "obj_loc_z"]].to_numpy()
575+
576+
return compute_scale_accuracy_impl(world_points, object_points, sync_index)
577+
515578
def align_to_object(self, sync_index: int) -> "PointDataBundle":
516579
"""
517580
Align the bundle to real-world units using object point correspondences.

src/caliscope/gui/camera_undistort_view.py

Lines changed: 0 additions & 205 deletions
This file was deleted.

src/caliscope/gui/extrinsic_calibration_tab.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from caliscope.gui.views.extrinsic_calibration_view import ExtrinsicCalibrationView
1818

1919
if TYPE_CHECKING:
20-
from caliscope.core.point_data_bundle import PointDataBundle
2120
from caliscope.workspace_coordinator import WorkspaceCoordinator
2221

2322
logger = logging.getLogger(__name__)
@@ -66,19 +65,9 @@ def _connect_signals(self) -> None:
6665
if self._presenter is None:
6766
return
6867

69-
# Calibration completion - persist bundle via coordinator
70-
self._presenter.calibration_complete.connect(self._on_calibration_complete)
71-
7268
# Charuco changes invalidate the presenter - need to recreate
7369
self._coordinator.charuco_changed.connect(self._on_charuco_changed)
7470

75-
def _on_calibration_complete(self, bundle: PointDataBundle) -> None:
76-
"""Handle calibration completion - persist bundle via coordinator."""
77-
logger.info(f"Extrinsic calibration complete: RMSE={bundle.reprojection_report.overall_rmse:.3f}px")
78-
79-
# Persist via coordinator (handles bundle_updated signal, camera array save, etc.)
80-
self._coordinator.update_bundle(bundle)
81-
8271
def _on_charuco_changed(self) -> None:
8372
"""Update charuco reference when config changes.
8473

src/caliscope/gui/multi_camera_processing_tab.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
from caliscope.gui.views.multi_camera_processing_widget import MultiCameraProcessingWidget
1818

1919
if TYPE_CHECKING:
20-
from caliscope.core.coverage_analysis import ExtrinsicCoverageReport
21-
from caliscope.core.point_data import ImagePoints
22-
from caliscope.tracker import Tracker
2320
from caliscope.workspace_coordinator import WorkspaceCoordinator
2421

2522
logger = logging.getLogger(__name__)
@@ -74,24 +71,9 @@ def _connect_signals(self) -> None:
7471
# Rotation persistence
7572
self._presenter.rotation_changed.connect(self.coordinator.persist_camera_rotation)
7673

77-
# Processing completion - persist results and notify coordinator
78-
self._presenter.processing_complete.connect(self._on_processing_complete)
79-
8074
# Charuco changes invalidate the tracker - need to recreate presenter
8175
self.coordinator.charuco_changed.connect(self._on_charuco_changed)
8276

83-
def _on_processing_complete(
84-
self,
85-
image_points: ImagePoints,
86-
coverage_report: ExtrinsicCoverageReport,
87-
tracker: Tracker,
88-
) -> None:
89-
"""Handle processing completion - persist results and signal coordinator."""
90-
logger.info(f"Multi-camera processing complete: {len(image_points.df)} observations")
91-
92-
# Persist ImagePoints to extrinsic directory (triggers status_changed)
93-
self.coordinator.persist_extrinsic_image_points(image_points, tracker.name)
94-
9577
def _on_charuco_changed(self) -> None:
9678
"""Update tracker when charuco config changes.
9779

0 commit comments

Comments
 (0)