Skip to content

Commit

Permalink
Merge pull request #538 from mprib/537-initiate-extrinsic-calibration…
Browse files Browse the repository at this point in the history
…-from-gui

537 initiate extrinsic calibration from gui
  • Loading branch information
mprib authored Nov 28, 2023
2 parents 87ad09d + 7c489c7 commit ddc235d
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 333 deletions.
2 changes: 1 addition & 1 deletion dev/demo/demo_process_prerecorded.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import pyxy3d.logger

logger = pyxy3d.logger.get(__name__)
from time import sleep

import sys
Expand All @@ -17,6 +16,7 @@
PlaybackTriangulationWidget,
)
from pyxy3d.trackers.tracker_enum import TrackerEnum
logger = pyxy3d.logger.get(__name__)

# session_path = Path(__root__, "dev", "sample_sessions", "293")
# recording_path = Path(session_path, "recording_1")
Expand Down
33 changes: 16 additions & 17 deletions pyxy3d/cameras/camera_array_initializer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# %%
import pyxy3d.logger

logger = pyxy3d.logger.get(__name__)


from pathlib import Path
Expand All @@ -17,6 +14,8 @@
import numpy as np
from dataclasses import dataclass, asdict
import toml
import pyxy3d.logger
logger = pyxy3d.logger.get(__name__)


@dataclass
Expand Down Expand Up @@ -238,10 +237,10 @@ def _get_scored_anchored_array(self, anchor_port: int) -> tuple:
error = data["error"]
matrix = np.array(data["matrix"], dtype=np.float64)
distortions = np.array(data["distortions"], dtype=np.float64)
exposure = data["exposure"]
# exposure = data["exposure"]
grid_count = data["grid_count"]
ignore = data["ignore"]
verified_resolutions = data["verified_resolutions"]
# verified_resolutions = data["verified_resolutions"]

# update with extrinsics, though place anchor camera at origin
if port == anchor_port:
Expand All @@ -256,18 +255,18 @@ def _get_scored_anchored_array(self, anchor_port: int) -> tuple:
total_error_score += anchored_stereopair.error_score

cam_data = CameraData(
port,
size,
rotation_count,
error,
matrix,
distortions,
exposure,
grid_count,
ignore,
verified_resolutions,
translation,
rotation,
port=port,
size=size,
rotation_count=rotation_count,
error=error,
matrix=matrix,
distortions=distortions,
# exposure,
grid_count=grid_count,
ignore=ignore,
# verified_resolutions,
translation=translation,
rotation=rotation,
)

cameras[port] = cam_data
Expand Down
85 changes: 50 additions & 35 deletions pyxy3d/controller.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtCore import QObject, Signal, Slot, QThread

from pathlib import Path

from PySide6.QtGui import QPixmap

import pyxy3d.logger
from time import sleep
from pyxy3d.calibration.charuco import Charuco
from pyxy3d.recording.recorded_stream import RecordedStream
from pyxy3d.configurator import Configurator
Expand All @@ -24,6 +23,7 @@
from pyxy3d.synchronized_stream_manager import SynchronizedStreamManager
from collections import OrderedDict

import pyxy3d.logger
logger = pyxy3d.logger.get(__name__)


Expand All @@ -39,6 +39,7 @@ class Controller(QObject):
IndexUpdate = Signal(int, int) # port, frame_index
ExtrinsicImageUpdate = Signal(dict)
ExtrinsicCalibrationComplete = Signal()
extrinsic_2D_complete = Signal()

def __init__(self, workspace_dir: Path):
super().__init__()
Expand All @@ -47,6 +48,7 @@ def __init__(self, workspace_dir: Path):

# streams will be used to play back recorded video with tracked markers to select frames
self.intrinsic_streams = {}
self.all_camera_data = {}
self.frame_emitters = {}
self.intrinsic_calibrators = {}
self.charuco = self.config.get_charuco()
Expand Down Expand Up @@ -91,13 +93,24 @@ def update_charuco(self, charuco: Charuco):

def process_extrinsic_streams(self, fps_target = None):

def worker():
self.sync_stream_manager = SynchronizedStreamManager(
recording_dir=self.extrinsic_source_directory,
all_camera_data=self.all_camera_data,
tracker=self.charuco_tracker,
)
self.sync_stream_manager.process_streams(fps_target=fps_target)

output_path = Path(self.extrinsic_source_directory,"CHARUCO", "xy_CHARUCO.csv")
while not output_path.exists():
sleep(.5)
logger.info(f"Waiting for 2D tracked points to populate at {output_path}")

self.extrinsic_process_thread = QThread()
self.extrinsic_process_thread.run = worker
self.extrinsic_process_thread.finished.connect(self.extrinsic_2D_complete.emit)
self.extrinsic_process_thread.start()

self.sync_stream_manager = SynchronizedStreamManager(
recording_dir=self.extrinsic_source_directory,
all_camera_data=self.all_camera_data,
tracker=self.charuco_tracker,
)
self.sync_stream_manager.process_streams(fps_target=fps_target)

def load_intrinsic_streams(self):
for port, camera_data in self.all_camera_data.items():
Expand Down Expand Up @@ -205,8 +218,6 @@ def calibrate_camera(self, port):
self.push_camera_data(port)
camera_data = self.all_camera_data[port]
self.config.save_camera(camera_data)
# camera_display_data = self.all_camera_data[port].get_display_data()
# self.CameraDataUpdate.emit(port,camera_display_data)

def push_camera_data(self, port):
camera_display_data = self.all_camera_data[port].get_display_data()
Expand Down Expand Up @@ -263,35 +274,39 @@ def estimate_extrinsics(self):
here, but they are all necessary steps of the process so I didn't want to
try to encapsulate any further
"""
self.extrinsic_calibration_xy = Path(
self.workspace, "calibration", "extrinsic", "CHARUCO", "xy_CHARUCO.csv"
)
def worker():
self.extrinsic_calibration_xy = Path(
self.workspace, "calibration", "extrinsic", "CHARUCO", "xy_CHARUCO.csv"
)

stereocalibrator = StereoCalibrator(
self.config.config_toml_path, self.extrinsic_calibration_xy
)
stereocalibrator.stereo_calibrate_all(boards_sampled=10)
stereocalibrator = StereoCalibrator(
self.config.config_toml_path, self.extrinsic_calibration_xy
)
stereocalibrator.stereo_calibrate_all(boards_sampled=10)

self.camera_array: CameraArray = CameraArrayInitializer(
self.config.config_toml_path
).get_best_camera_array()
self.camera_array: CameraArray = CameraArrayInitializer(
self.config.config_toml_path
).get_best_camera_array()

self.point_estimates: PointEstimates = get_point_estimates(
self.camera_array, self.extrinsic_calibration_xy
)
self.point_estimates: PointEstimates = get_point_estimates(
self.camera_array, self.extrinsic_calibration_xy
)

self.capture_volume = CaptureVolume(self.camera_array, self.point_estimates)
self.capture_volume.optimize()
self.capture_volume = CaptureVolume(self.camera_array, self.point_estimates)
self.capture_volume.optimize()

self.quality_controller = QualityController(self.capture_volume, self.charuco)
self.quality_controller = QualityController(self.capture_volume, self.charuco)

logger.info(
f"Removing the worst fitting {FILTERED_FRACTION*100} percent of points from the model"
)
self.quality_controller.filter_point_estimates(FILTERED_FRACTION)
self.capture_volume.optimize()
logger.info(
f"Removing the worst fitting {FILTERED_FRACTION*100} percent of points from the model"
)
self.quality_controller.filter_point_estimates(FILTERED_FRACTION)
self.capture_volume.optimize()

# saves both point estimates and camera array
self.config.save_capture_volume(self.capture_volume)
# saves both point estimates and camera array
self.config.save_capture_volume(self.capture_volume)

self.ExtrinsicCalibrationComplete.emit()
self.extrinsicCalibrationThread = QThread()
self.extrinsicCalibrationThread.run = worker
self.extrinsicCalibrationThread.finished.connect(self.ExtrinsicCalibrationComplete.emit)
self.extrinsicCalibrationThread.start()
17 changes: 5 additions & 12 deletions pyxy3d/gui/prerecorded_main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
import pyxy3d.logger
from pathlib import Path


from PySide6.QtWidgets import QMainWindow, QFileDialog
from threading import Thread
import sys
from PySide6.QtWidgets import (
QApplication,
QFileDialog,
QMainWindow,
QWidget,
QTabWidget,
Expand All @@ -18,18 +16,10 @@
from PySide6.QtGui import QIcon, QAction
from PySide6.QtCore import Qt
from pyxy3d import __root__, __settings_path__
from pyxy3d.session.session import LiveSession, SessionMode
from pyxy3d.gui.log_widget import LogWidget
from pyxy3d.configurator import Configurator
from pyxy3d.gui.charuco_widget import CharucoWidget
from pyxy3d.gui.live_camera_config.intrinsic_calibration_widget import (
IntrinsicCalibrationWidget,
)
from pyxy3d.gui.recording_widget import RecordingWidget
from pyxy3d.gui.post_processing_widget import PostProcessingWidget
from pyxy3d.gui.extrinsic_calibration_widget import ExtrinsicCalibrationWidget
from pyxy3d.gui.workspace_widget import WorkspaceSummaryWidget
from pyxy3d.gui.prerecorded_intrinsic_calibration.multiplayback_widget import MultiIntrinsicPlaybackWidget
from pyxy3d.gui.vizualize.calibration.capture_volume_widget import CaptureVolumeWidget
from pyxy3d.controller import Controller

logger = pyxy3d.logger.get(__name__)
Expand Down Expand Up @@ -81,6 +71,9 @@ def build_central_tabs(self):

self.central_tab = QTabWidget()
self.setCentralWidget(self.central_tab)
self.workspace_summary = WorkspaceSummaryWidget(self.controller)
self.central_tab.addTab(self.workspace_summary, "Workspace")

self.charuco_widget = CharucoWidget(self.controller)
self.central_tab.addTab(self.charuco_widget,"Charuco")
self.intrinsic_cal_widget = MultiIntrinsicPlaybackWidget(self.controller)
Expand Down
60 changes: 60 additions & 0 deletions pyxy3d/gui/workspace_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os
import sys
import subprocess
from pathlib import Path
from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton
from PySide6.QtCore import Slot
import pyxy3d.logger
logger = pyxy3d.logger.get(__name__)

class WorkspaceSummaryWidget(QWidget):
def __init__(self, controller):
super().__init__()

self.controller = controller

self.open_workspace_folder_btn = QPushButton("Open Workspace Directory", self)
self.process_extrinsics_btn = QPushButton("Process Extrinsics", self)
self.calibrate_btn = QPushButton("Calibrate Extrinsics", self)


# Set the layout for the widget
self.place_widgets()
self.connect_widgets()


def place_widgets(self):
# Layout
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.layout.addWidget(self.open_workspace_folder_btn)
self.layout.addWidget(self.process_extrinsics_btn)
self.layout.addWidget(self.calibrate_btn)



def connect_widgets(self):
self.open_workspace_folder_btn.clicked.connect(self.open_workspace)
self.calibrate_btn.clicked.connect(self.on_calibrate_btn_clicked)
self.process_extrinsics_btn.clicked.connect(self.on_process_extrinsics_clicked)


@Slot()
def on_calibrate_btn_clicked(self):
# Call the extrinsic calibration method in the controller
self.controller.estimate_extrinsics()

@Slot()
def on_process_extrinsics_clicked(self):
logger.info("Calling controller to process extrinsic streams into 2D data")
self.controller.process_extrinsic_streams(fps_target=100)


def open_workspace(self):
logger.info(f"Opening workspace within File Explorer... located at {self.controller.workspace}")
if sys.platform == 'win32':
os.startfile(self.controller.workspace)
elif sys.platform == 'darwin':
subprocess.run(["open", self.controller.workspace])
else: # Linux and Unix-like systems
subprocess.run(["xdg-open", self.controller.workspace])
72 changes: 0 additions & 72 deletions tests/sessions/post_monocal/calibration/extrinsic/config.toml

This file was deleted.

Loading

0 comments on commit ddc235d

Please sign in to comment.