Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wip/get location from visual data ba #83

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion parallax/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import os

__version__ = "0.37.25"
__version__ = "0.37.26"

# allow multiple OpenMP instances
os.environ["KMP_DUPLICATE_LIB_OK"] = "True"
2 changes: 2 additions & 0 deletions parallax/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,8 @@ def __init__(self):
)
self._next_frame = 0
self.device_color_type = None
self.width = 4000
self.height = 3000

def name(self, sn_only=False):
"""Get the name of the mock camera"""
Expand Down
23 changes: 15 additions & 8 deletions parallax/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
The Model class is the core component for managing cameras, stages, and calibration data.
"""
from collections import OrderedDict
from PyQt5.QtCore import QObject, pyqtSignal
from .camera import MockCamera, PySpinCamera, close_cameras, list_cameras
from .stage_listener import Stage, StageInfo
Expand Down Expand Up @@ -47,8 +48,8 @@ def __init__(self, version="V1", bundle_adjustment=False):
self.pos_x = {}
self.camera_intrinsic = {}
self.camera_extrinsic = {}
self.stereo_instance = None
self.best_camera_pair = None
self.stereo_calib_instance = {}
self.calibration = None
self.calibrations = {}
self.coords_debug = {}
Expand All @@ -60,7 +61,7 @@ def __init__(self, version="V1", bundle_adjustment=False):
self.reticle_metadata = {}

# clicked pts
self.clicked_pts = {}
self.clicked_pts = OrderedDict()

def add_calibration(self, cal):
"""Add a calibration."""
Expand Down Expand Up @@ -147,7 +148,10 @@ def reset_stage_calib_info(self):
self.stages_calib = {}

def add_pts(self, camera_name, pts):
"""Add points."""
"""Add points. If a new camera is added and the size exceeds 2, remove the oldest."""
if len(self.clicked_pts) == 2 and camera_name not in self.clicked_pts:
# Remove the oldest entry (first added item)
self.clicked_pts.popitem(last=False)
self.clicked_pts[camera_name] = pts

def get_pts(self, camera_name):
Expand All @@ -160,7 +164,7 @@ def get_cameras_detected_pts(self):

def reset_pts(self):
"""Reset points."""
self.clicked_pts = {}
self.clicked_pts = OrderedDict()

def add_transform(self, stage_sn, transform, scale):
"""Add transformation matrix between local to global coordinates."""
Expand Down Expand Up @@ -233,11 +237,14 @@ def get_camera_intrinsic(self, camera_name):
"""Get camera intrinsic parameters."""
return self.camera_intrinsic.get(camera_name)

def add_stereo_instance(self, instance):
self.stereo_instance = instance
def add_stereo_calib_instance(self, sorted_key, instance):
self.stereo_calib_instance[sorted_key] = instance

def reset_stereo_instance(self):
self.stereo_instance = None
def get_stereo_calib_instance(self, sorted_key):
return self.stereo_calib_instance.get(sorted_key)

def reset_stereo_calib_instance(self):
self.stereo_calib_instance = {}

def add_camera_extrinsic(self, name1, name2, retVal, R, T, E, F):
"""Add camera extrinsic parameters."""
Expand Down
8 changes: 3 additions & 5 deletions parallax/probe_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,7 @@ def update(self, stage, debug_info=None):
self._update_local_global_point(debug_info) # Do no update if it is duplicates

filtered_df = self._filter_df_by_sn(self.stage.sn)
self.transM_LR = self._get_transM(filtered_df, noise_threshold=100) # TODO original
#self.transM_LR = self._get_transM(filtered_df, remove_noise=False) # Test
self.transM_LR = self._get_transM(filtered_df, noise_threshold=100)
if self.transM_LR is None:
return

Expand All @@ -620,9 +619,8 @@ def complete_calibration(self, filtered_df):
# save the filtered points to a new file
print("ProbeCalibration: complete_calibration")
self.file_name = f"points_{self.stage.sn}.csv"
self.transM_LR = self._get_transM(filtered_df, save_to_csv=True, file_name=self.file_name, noise_threshold=20) # TODO original
#self.transM_LR = self._get_transM(filtered_df, save_to_csv=True, file_name=self.file_name, remove_noise=False) # Test

self.transM_LR = self._get_transM(filtered_df, save_to_csv=True, file_name=self.file_name, noise_threshold=20)

if self.transM_LR is None:
return

Expand Down
58 changes: 54 additions & 4 deletions parallax/screen_coords_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ def __init__(self, model, screen_widgets, reticle_selector, x, y, z):
def _clicked_position(self, camera_name, pos):
"""Get clicked position."""
self._register_pt(camera_name, pos)
global_coords = self._get_global_coords(camera_name, pos)
if not self.model.bundle_adjustment:
global_coords = self._get_global_coords_stereo(camera_name, pos)
else:
global_coords = self._get_global_coords_BA(camera_name, pos)
if global_coords is None:
return

Expand Down Expand Up @@ -73,9 +76,9 @@ def _register_pt(self, camera_name, pos):
if pos is not None and camera_name is not None:
self.model.add_pts(camera_name, pos)

def _get_global_coords(self, camera_name, pos):
def _get_global_coords_stereo(self, camera_name, pos):
"""Calculate global coordinates based on the best camera pair."""
if self.model.stereo_instance is None:
if self.model.stereo_calib_instance is None:
logger.debug("Stereo instance is None")
return None

Expand Down Expand Up @@ -105,9 +108,56 @@ def _get_global_coords(self, camera_name, pos):
tip_coordsB = pos

# Calculate global coordinates using stereo instance
global_coords = self.model.stereo_instance.get_global_coords(
stereo_instance = self._get_calibration_instance(camA_best, camB_best)
if stereo_instance is None:
logger.debug(f"Stereo calibration instance not found for cameras: {camA_best}, {camB_best}")
return None

global_coords = stereo_instance.get_global_coords(
camA_best, tip_coordsA, camB_best, tip_coordsB
)

return global_coords[0]

def _get_global_coords_BA(self, camera_name, pos):
"""Calculate global coordinates based on the best camera pair."""
if self.model.stereo_calib_instance is None:
logger.debug("Stereo instance is None")
return None

# Get detected points from cameras
cameras_detected_pts = self.model.get_cameras_detected_pts()
if len(cameras_detected_pts) < 2:
logger.debug("Not enough detected points to calculate global coordinates")
return None

camA, camB = None, None
tip_coordsA, tip_coordsB = None, None
for camera, pts in cameras_detected_pts.items():
if camera_name == camera:
camA = camera
tip_coordsA = pos
else:
camB = camera
tip_coordsB = pts

if not camA or not camB or tip_coordsA is None or tip_coordsB is None:
logger.debug("Insufficient camera data to compute global coordinates")
return None

# Calculate global coordinates using stereo instance
stereo_instance = self._get_calibration_instance(camA, camB)
if stereo_instance is None:
logger.debug(f"Stereo calibration instance not found for cameras: {camA}, {camB}")
return None

# Calculate global coordinates using the stereo instance
global_coords = stereo_instance.get_global_coords(
camA, tip_coordsA, camB, tip_coordsB
)

return global_coords[0]

def _get_calibration_instance(self, camA, camB):
sorted_key = tuple(sorted((camA, camB)))
return self.model.get_stereo_calib_instance(sorted_key)
29 changes: 8 additions & 21 deletions parallax/stage_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def reticle_detect_default_status(self):
# Disable probe calibration
self.probe_detect_default_status()
self.model.reset_stage_calib_info()
self.model.reset_stereo_instance()
self.model.reset_stereo_calib_instance()
self.model.reset_camera_extrinsic()
self.probeCalibration.clear()

Expand Down Expand Up @@ -501,9 +501,11 @@ def calibrate_stereo(self, cam_names, intrinsics, img_coords):
self.camA_best, self.camB_best = camA, camB
coordsA_best, coordsB_best = coordsA, coordsB
itmxA_best, itmxB_best = itmxA, itmxB


# Update the model with the calibration results
self.model.add_stereo_instance(self.calibrationStereo)
sorted_key = tuple(sorted((self.camA_best, self.camB_best)))
self.model.add_stereo_calib_instance(sorted_key, self.calibrationStereo)
self.model.add_camera_extrinsic(
self.camA_best, self.camB_best, min_err, R_AB_best, T_AB_best, E_AB_best, F_AB_best
)
Expand All @@ -522,9 +524,6 @@ def calibrate_all_cameras(self, cam_names, intrinsics, img_coords):
# Stereo Camera Calibration
calibrationStereo = None

# Dictionary to store instances with sorted camera names as keys
self.calibrationStereoInstances = {}

# Perform calibration between pairs of cameras
print(cam_names)

Expand All @@ -540,20 +539,13 @@ def calibrate_all_cameras(self, cam_names, intrinsics, img_coords):
camA, coordsA, itmxA, camB, coordsB, itmxB
)
print("\n--------------------------------------------------------")
print(f"camsera pair: {camA}-{camB}, err: {np.round(err, 2) * 1000} µm³")
logger.debug(f"=== camera pair: {camA}-{camB}, err: {np.round(err, 2) * 1000} µm³ ===")
print(f"camsera pair: {camA}-{camB}")
logger.debug(f"=== camera pair: {camA}-{camB} ===")
logger.debug(f"R: \n{R_AB}\nT: \n{T_AB}")

# Store the instance with a sorted tuple key
sorted_key = tuple(sorted((camA, camB)))
self.calibrationStereoInstances[sorted_key] = {
"instance": calibrationStereo,
"error": err,
"R_AB": R_AB,
"T_AB": T_AB,
"E_AB": E_AB,
"F_AB": F_AB,
}
self.model.add_stereo_calib_instance(sorted_key, calibrationStereo)

#calibrationStereo.print_calibrate_stereo_results(camA, camB)
err = calibrationStereo.test_performance(camA, coordsA, camB, coordsB, print_results=True)
Expand All @@ -565,7 +557,7 @@ def calibrate_all_cameras(self, cam_names, intrinsics, img_coords):
# Example of how to retrieve the instance with either (camA, camB) or (camB, camA)
def get_calibration_instance(self, camA, camB):
sorted_key = tuple(sorted((camA, camB)))
return self.calibrationStereoInstances.get(sorted_key)
return self.model.get_stereo_calib_instance(sorted_key)

def calibrate_cameras(self):
"""
Expand Down Expand Up @@ -687,10 +679,6 @@ def probe_detect_on_screens(self, camA, timestampA, snA, stage_info, tip_coordsA
if (camA is None) or (timestampA is None) or (snA is None) or (tip_coordsA is None):
return

if self.calibrationStereoInstances is None:
logger.debug(f"Camera calibration has not done. {camA}")
return

for screen in self.screen_widgets:
camB = screen.get_camera_name()
if camA == camB:
Expand All @@ -710,7 +698,6 @@ def probe_detect_on_screens(self, camA, timestampA, snA, stage_info, tip_coordsA
logger.debug(f"Camera calibration has not done {camA}, {camB}")
continue

calibrationStereoInstance = calibrationStereoInstance["instance"]
global_coords = calibrationStereoInstance.get_global_coords(
camA, tip_coordsA, camB, tip_coordsB
)
Expand Down
Loading