Skip to content

Commit

Permalink
Merge pull request #585 from mprib/583-issues-with-null-values-in-con…
Browse files Browse the repository at this point in the history
…figtoml

583 issues with null values in configtoml
  • Loading branch information
mprib authored Dec 10, 2023
2 parents 5a1d3ae + c3d7790 commit 1af9c62
Show file tree
Hide file tree
Showing 19 changed files with 374 additions and 129 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*.png
.vscode*
.idea*
*.mp4
*.bz2
.venv*
*.pkl
Expand All @@ -13,8 +12,8 @@
dist/*
*.epgz
*.pdf
tests/sessions/*
tests/sessions_copy_delete/*
dev/sessions_copy_delete/*
dev/sample_sessions*
*.nbi
*.nbc
Expand Down
67 changes: 32 additions & 35 deletions pyxy3d/calibration/stereocalibrator.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
#%%
import sys
from pathlib import Path

import pyxy3d.logger

import cv2
import pandas as pd
from pyxy3d import __root__

import time

import numpy as np
import rtoml
from itertools import combinations

logger = pyxy3d.logger.get(__name__)


class StereoCalibrator:
def __init__(
self,
Expand All @@ -23,21 +18,20 @@ def __init__(
):
self.config_path = config_path
self.config = rtoml.load(config_path)
self.ports = []

self.ports = []
# set ports keeping in mind that something may be flagged for ignore
for key, value in self.config.items():
if key[0:4] == "cam_":
#it's a camera so check if it should not be ignored
if not self.config[key]["ignore"]:
self.ports.append(int(key[4:]))
# NOTE: Commenting out this line as "ignore" was only a property needed when managing webcams but does not make sense here
# if not self.config[key]["ignore"]:
self.ports.append(int(key[4:]))

# import point data, adding coverage regions to each port
raw_point_data = pd.read_csv(point_data_path)
self.all_point_data = self.points_with_coverage_region(raw_point_data)
self.all_boards = self.get_boards_with_coverage()


# self.ports = [int(key[4:]) for key in self.config.keys() if key[0:3] == "cam"]
self.pairs = [(i, j) for i, j in combinations(self.ports, 2) if i < j]

Expand Down Expand Up @@ -128,11 +122,11 @@ def get_boards_with_coverage(self):

return all_boards


def get_stereopair_data(self, pair: tuple, boards_sampled: int, random_state=1)-> pd.DataFrame or None:

def get_stereopair_data(
self, pair: tuple, boards_sampled: int, random_state=1
) -> pd.DataFrame or None:
# convenience function to get the points that are in the overlap regions of the pairs
def in_pair(row:int, pair: tuple):
def in_pair(row: int, pair: tuple):
"""
Uses the coverage_region string generated previously to flag points that are in
a shared region of the pair
Expand Down Expand Up @@ -179,11 +173,13 @@ def in_pair(row:int, pair: tuple):
n=sample_size, weights=sample_weight, random_state=random_state
)

selected_pair_points = pair_points.merge(selected_boards, "right", "sync_index")
selected_pair_points = pair_points.merge(
selected_boards, "right", "sync_index"
)
else:
logger.info(f"For pair {pair} there are no shared boards")
selected_pair_points = None

return selected_pair_points

def stereo_calibrate_all(self, boards_sampled=10):
Expand All @@ -203,34 +199,37 @@ def stereo_calibrate_all(self, boards_sampled=10):
if error is not None:
# only store data if there was sufficient stereopair coverage to get
# a good calibration

# toml dumps arrays as strings, so needs to be converted to list
rotation = rotation.tolist()
translation = translation.tolist()

config_key = "stereo_" + str(pair[0]) + "_" + str(pair[1])
self.config[config_key] = {}
self.config[config_key]["rotation"] = rotation
self.config[config_key]["translation"] = translation
self.config[config_key]["RMSE"] = error

logger.info(f"Direct stereocalibration complete for all pairs for which data is available")
logger.info(
"Direct stereocalibration complete for all pairs for which data is available"
)
logger.info(f"Saving stereo-pair extrinsic data to {self.config_path}")
with open(self.config_path, "w") as f:
rtoml.dump(self.config, f)

def stereo_calibrate(self, pair, boards_sampled=10):

stereocalibration_flags = cv2.CALIB_FIX_INTRINSIC
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 40, 0.000001)

paired_point_data = self.get_stereopair_data(
pair, boards_sampled
)

paired_point_data = self.get_stereopair_data(pair, boards_sampled)

if paired_point_data is not None:
img_locs_A, obj_locs_A = self.get_stereocal_inputs(pair[0], paired_point_data)
img_locs_B, obj_locs_B = self.get_stereocal_inputs(pair[1], paired_point_data)
img_locs_A, obj_locs_A = self.get_stereocal_inputs(
pair[0], paired_point_data
)
img_locs_B, obj_locs_B = self.get_stereocal_inputs(
pair[1], paired_point_data
)

camera_matrix_A = self.config["cam_" + str(pair[0])]["matrix"]
camera_matrix_B = self.config["cam_" + str(pair[1])]["matrix"]
Expand Down Expand Up @@ -267,16 +266,15 @@ def stereo_calibrate(self, pair, boards_sampled=10):

logger.info(f"RMSE of reprojection for pair {pair} is {ret}")

else:
else:
logger.info(f"No stereocalibration produced for pair {pair}")
ret = None
rotation = None
translation = None

return ret, rotation, translation

def get_stereocal_inputs(self, port, point_data):

port_point_data = point_data.query(f"port == {port}")

sync_indices = port_point_data["sync_index"].to_numpy().round().astype(int)
Expand Down Expand Up @@ -316,8 +314,7 @@ def get_stereocal_inputs(self, port, point_data):
config_path,
point_data_path,
)

# %%

stereocal.stereo_calibrate_all(boards_sampled=15)
# %%

stereocal.stereo_calibrate_all(boards_sampled=15)
8 changes: 5 additions & 3 deletions pyxy3d/cameras/camera_array_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ def _get_scored_anchored_array(self, anchor_port: int) -> tuple:
total_error_score = 0

for key, data in self.config.items():
if key.startswith("cam_") and not self.config[key]["ignore"]:
# NOTE: commenting out second conditional check below. If you come back to this in a month and
# things haven't been breaking, then just delete all these comments.
if key.startswith("cam_"): # and not self.config[key]["ignore"]:
port = data["port"]
size = data["size"]
rotation_count = data["rotation_count"]
Expand All @@ -239,7 +241,7 @@ def _get_scored_anchored_array(self, anchor_port: int) -> tuple:
distortions = np.array(data["distortions"], dtype=np.float64)
# exposure = data["exposure"]
grid_count = data["grid_count"]
ignore = data["ignore"]
# ignore = data["ignore"]
# verified_resolutions = data["verified_resolutions"]

# update with extrinsics, though place anchor camera at origin
Expand All @@ -263,7 +265,7 @@ def _get_scored_anchored_array(self, anchor_port: int) -> tuple:
distortions=distortions,
# exposure,
grid_count=grid_count,
ignore=ignore,
# ignore=ignore,
# verified_resolutions,
translation=translation,
rotation=rotation,
Expand Down
38 changes: 21 additions & 17 deletions pyxy3d/configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ class Configurator:
def __init__(self, workspace_path: Path) -> None:
self.workspace_path = workspace_path
self.config_toml_path = Path(self.workspace_path, "config.toml")
self.point_estimates_toml_path = Path(self.workspace_path, "point_estimates.toml")
self.point_estimates_toml_path = Path(
self.workspace_path, "point_estimates.toml"
)

if exists(self.config_toml_path):
self.refresh_config_from_toml()
# this check only included for interfacing with historical tests...
# if underlying tests data updated, this should be removed
# if underlying tests data updated, this should be removed
if "camera_count" not in self.dict.keys():
self.dict["camera_count"] = 0
else:
Expand All @@ -52,16 +54,16 @@ def __init__(self, workspace_path: Path) -> None:
self.save_charuco(charuco)

# if exists(self.point_estimates_toml_path):
# self.refresh_point_estimates_from_toml()
# self.refresh_point_estimates_from_toml()

def save_camera_count(self, count):
self.camera_count = count
self.dict["camera_count"] = count
self.update_config_toml()

def get_camera_count(self):
return self.dict["camera_count"]

def get_intrinsic_wait_time(self):
return self.dict["intrinsic_wait_time"]

Expand Down Expand Up @@ -112,15 +114,17 @@ def save_capture_volume(self, capture_volume: CaptureVolume):
] = capture_volume.origin_sync_index
self.update_config_toml()



def get_configured_camera_data(self) -> dict[CameraData]:
all_camera_data = {}
for key, params in self.dict.items():
if key.startswith("cam_"):
port = params["port"]

if "error" in params.keys() and params["error"] is not None: # intrinsics have been calculated
if (
"error" in params.keys()
and params["error"] is not None
and params["error"] != "null"
): # intrinsics have been calculated
error = params["error"]
matrix = np.array(params["matrix"])
distortions = np.array(params["distortions"])
Expand All @@ -132,14 +136,14 @@ def get_configured_camera_data(self) -> dict[CameraData]:
grid_count = None

if (
"translation" in params.keys() and params["translation"] is not None
"translation" in params.keys()
and params["translation"] is not None
and params["translation"] != "null"
): # Extrinsics have been calculated
translation = np.array(params["translation"])
rotation = np.array(params["rotation"])

if rotation.shape == (
3,
): # camera rotation is stored as a matrix
if rotation.shape == (3,): # camera rotation is stored as a matrix
rotation = cv2.Rodrigues(rotation)[0]

else:
Expand Down Expand Up @@ -220,12 +224,12 @@ def save_camera(self, camera: Camera | CameraData):
def none_or_list(value):
# required to make sensible numeric format
# otherwise toml formats as text
if value is None:
if value is None or value == "null":
return None
else:
return value.tolist()

if camera.rotation is not None:
if camera.rotation is not None and camera.rotation != "null":
# store rotation as 3 parameter rodrigues
rotation_for_config = cv2.Rodrigues(camera.rotation)[0][:, 0]
rotation_for_config = rotation_for_config.tolist()
Expand All @@ -243,8 +247,8 @@ def none_or_list(value):
"rotation": rotation_for_config,
"exposure": camera.exposure,
"grid_count": camera.grid_count,
"ignore": camera.ignore,
"verified_resolutions": camera.verified_resolutions,
# "ignore": camera.ignore,
# "verified_resolutions": camera.verified_resolutions,
}

self.dict["cam_" + str(camera.port)] = params
Expand All @@ -255,7 +259,7 @@ def save_camera_array(self, camera_array: CameraArray):
for port, camera_data in camera_array.cameras.items():
self.save_camera(camera_data)

# Mac: leave this reference code in here for a potential splitting out of the recording functionality.
# Mac: leave this reference code in here for a potential splitting out of the recording functionality.
# def get_cameras(self) -> dict[Camera]:
# cameras = {}

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 1af9c62

Please sign in to comment.