-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from UCL/34-calibration-checker
34 calibration checker
- Loading branch information
Showing
9 changed files
with
296 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
import sys | ||
|
||
from sksurgerycalibration.ui.video_calibration_checker_command_line import main | ||
|
||
if __name__ == "__main__": | ||
sys.exit(main(sys.argv[1:])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
sksurgerycalibration/ui/video_calibration_checker_app.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# coding=utf-8 | ||
|
||
""" Application to detect chessboards, and assess calibration accuracy. """ | ||
|
||
import numpy as np | ||
import cv2 | ||
import sksurgeryimage.calibration.chessboard_point_detector as cpd | ||
from sksurgerycalibration.video.video_calibration_params import \ | ||
MonoCalibrationParams | ||
|
||
# pylint: disable=too-many-branches | ||
|
||
def run_video_calibration_checker(configuration = None, | ||
calib_dir = './', prefix = None): | ||
""" | ||
Application that detects a calibration pattern, runs | ||
solvePnP, and prints information out to enable you to | ||
check how accurate a calibration actually is. | ||
:param config_file: location of configuration file. | ||
:param calib_dir: the location of the calibration directory you want to | ||
check | ||
:param prefix: the file prefix for the calibration data you want to check | ||
:raises ValueError: if no configuration provided. | ||
:raises RuntimeError: if can't open source. | ||
""" | ||
if configuration is None: | ||
raise ValueError("Calibration Checker requires a config file") | ||
|
||
source = configuration.get("source", 0) | ||
corners = configuration.get("corners", [14, 10]) | ||
corners = (corners[0], corners[1]) | ||
size = configuration.get("square size in mm", 3) | ||
window_size = configuration.get("window size", None) | ||
keypress_delay = configuration.get("keypress delay", 10) | ||
interactive = configuration.get("interactive", True) | ||
sample_frequency = configuration.get("sample frequency", 1) | ||
|
||
existing_calibration = MonoCalibrationParams() | ||
existing_calibration.load_data(calib_dir, prefix, halt_on_ioerror = False) | ||
intrinsics = existing_calibration.camera_matrix | ||
distortion = existing_calibration.dist_coeffs | ||
|
||
cap = cv2.VideoCapture(source) | ||
if not cap.isOpened(): | ||
raise RuntimeError("Failed to open camera:" + str(source)) | ||
|
||
if window_size is not None: | ||
cap.set(cv2.CAP_PROP_FRAME_WIDTH, window_size[0]) | ||
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, window_size[1]) | ||
print("Video feed set to (" | ||
+ str(window_size[0]) + " x " + str(window_size[1]) + ")") | ||
else: | ||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | ||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | ||
print("Video feed defaults to (" | ||
+ str(width) + " x " + str(height) + ")") | ||
|
||
# For detecting the chessboard points | ||
detector = cpd.ChessboardPointDetector(corners, size) | ||
num_pts = corners[0] * corners[1] | ||
captured_positions = np.zeros((0, 3)) | ||
frames_sampled = 0 | ||
while True: | ||
frame_ok, frame = cap.read() | ||
|
||
key = None | ||
frames_sampled += 1 | ||
|
||
if not frame_ok: | ||
print("Reached end of video source or read failure.") | ||
key = ord('q') | ||
else: | ||
undistorted = cv2.undistort(frame, intrinsics, distortion) | ||
if interactive: | ||
cv2.imshow("live image", undistorted) | ||
key = cv2.waitKey(keypress_delay) | ||
else: | ||
if frames_sampled % sample_frequency == 0: | ||
key = ord('a') | ||
|
||
if key == ord('q'): | ||
break | ||
|
||
image_points = np.array([]) | ||
if key in [ord('c'), ord('m'), ord('t'), ord('a')]: | ||
_, object_points, image_points = \ | ||
detector.get_points(undistorted) | ||
|
||
pnp_ok = False | ||
img = None | ||
tvec = None | ||
if image_points.shape[0] > 0: | ||
img = cv2.drawChessboardCorners(undistorted, corners, | ||
image_points, | ||
num_pts) | ||
if interactive: | ||
cv2.imshow("detected points", img) | ||
|
||
pnp_ok, _, tvec = cv2.solvePnP(object_points, | ||
image_points, | ||
intrinsics, | ||
None) | ||
if pnp_ok: | ||
captured_positions = np.append(captured_positions, | ||
np.transpose(tvec), | ||
axis=0) | ||
|
||
if key in [ord('t'), ord('a')] and captured_positions.shape[0] > 1: | ||
print(str(captured_positions[-1][0] | ||
- captured_positions[-2][0]) + " " | ||
+ str(captured_positions[-1][1] | ||
- captured_positions[-2][1]) + " " | ||
+ str(captured_positions[-1][2] | ||
- captured_positions[-2][2]) + " ") | ||
if key in [ord('m'), ord('a')] and \ | ||
captured_positions.shape[0] > 1: | ||
print("Mean:" | ||
+ str(np.mean(captured_positions, axis=0))) | ||
print("StdDev:" | ||
+ str(np.std(captured_positions, axis=0))) | ||
|
||
if key in [ord('c'), ord('a')] and pnp_ok: | ||
print("Pose" + str(tvec[0][0]) + " " | ||
+ str(tvec[1][0]) + " " | ||
+ str(tvec[2][0])) | ||
|
||
if not pnp_ok and image_points.shape[0] > 0: | ||
print("Failed to solve PnP") | ||
|
||
if image_points.shape[0] == 0: | ||
print("Failed to detect points") | ||
|
||
cap.release() | ||
cv2.destroyAllWindows() |
49 changes: 49 additions & 0 deletions
49
sksurgerycalibration/ui/video_calibration_checker_command_line.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# coding=utf-8 | ||
|
||
""" CLI for sksurgerycalibration video calibration checker. """ | ||
|
||
import argparse | ||
|
||
from sksurgerycore.configuration.configuration_manager import \ | ||
ConfigurationManager | ||
from sksurgerycalibration import __version__ | ||
from sksurgerycalibration.ui.video_calibration_checker_app \ | ||
import run_video_calibration_checker | ||
|
||
|
||
def main(args=None): | ||
|
||
""" Entry point for bardVideoCalibrationChecker application. """ | ||
|
||
parser = argparse.ArgumentParser( | ||
description='SciKit-Surgery Calibration ' | ||
'Video Calibration Checker') | ||
|
||
parser.add_argument("-c", "--config", | ||
required=True, | ||
type=str, | ||
help="Configuration file containing the parameters.") | ||
|
||
parser.add_argument("-d", "--calib_dir", | ||
required=True, | ||
type=str, | ||
help="Directory containing calibration data.") | ||
|
||
parser.add_argument("-p", "--prefix", | ||
required=True, | ||
type=str, | ||
help="Prefix for calibration data.") | ||
|
||
version_string = __version__ | ||
friendly_version_string = version_string if version_string else 'unknown' | ||
parser.add_argument( | ||
"--version", | ||
action='version', | ||
version='scikit-surgerycalibration version ' + friendly_version_string) | ||
|
||
args = parser.parse_args(args) | ||
|
||
configurer = ConfigurationManager(args.config) | ||
configuration = configurer.get_copy() | ||
|
||
run_video_calibration_checker(configuration, args.calib_dir, args.prefix) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"""Tests for command line application """ | ||
import pytest | ||
from sksurgerycalibration.ui.video_calibration_checker_command_line import main | ||
|
||
def test_cl_no_config(): | ||
""" Run command line app with no config file. The parser should | ||
raise SystemExit due to missing required argument""" | ||
with pytest.raises(SystemExit) as pytest_wrapped_e: | ||
main([]) | ||
|
||
#I'm not sure how useful the next 2 asserts are. We already know it's | ||
#a SystemExit, if the code value specific to the parser? | ||
assert pytest_wrapped_e.type == SystemExit | ||
assert pytest_wrapped_e.value.code == 2 | ||
|
||
|
||
def test_cl_with_config(): | ||
""" Run command line app with config """ | ||
main(['-c', 'config/recorded_chessboard.json', | ||
'-d', 'tests/data/laparoscope_calibration/cbh-viking/', | ||
'-p', 'calib.left']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""Tests for command line application """ | ||
import copy | ||
import pytest | ||
from sksurgerycalibration.ui.video_calibration_checker_app import \ | ||
run_video_calibration_checker | ||
|
||
config = { "method": "chessboard", | ||
"source": "tests/data/laparoscope_calibration/left/left.ogv", | ||
"corners": [14, 10], | ||
"square size in mm": 6, | ||
"minimum number of views": 5, | ||
"keypress delay": 0, | ||
"interactive" : False, | ||
"sample frequency" : 2 | ||
} | ||
|
||
def test_with_no_config(): | ||
"""It shouldn't run with no configuration file""" | ||
with pytest.raises(ValueError): | ||
run_video_calibration_checker(None, | ||
calib_dir = 'tests/data/laparoscope_calibration/cbh-viking', | ||
prefix = "calib.right") | ||
|
||
|
||
def test_with_prefix(): | ||
""" Run command line app with an existing calibration""" | ||
run_video_calibration_checker(config, | ||
calib_dir = 'tests/data/laparoscope_calibration/cbh-viking', | ||
prefix = "calib.right") | ||
|
||
|
||
def test_with_invalid_capture(): | ||
"""Should throw a runtime error if we can't open video capture""" | ||
duff_config = copy.deepcopy(config) | ||
duff_config['source'] = 'bad source' | ||
with pytest.raises(RuntimeError): | ||
run_video_calibration_checker(duff_config, | ||
calib_dir = 'tests/data/laparoscope_calibration/cbh-viking', | ||
prefix = "calib.right") | ||
|
||
|
||
def test_with_custome_window_size(): | ||
"""We should be able to set the window size in config""" | ||
ok_config = copy.deepcopy(config) | ||
ok_config['window size'] = [640, 480] | ||
run_video_calibration_checker(ok_config, | ||
calib_dir = 'tests/data/laparoscope_calibration/cbh-viking', | ||
prefix = "calib.right") |