diff --git a/tests/algorithms/__init__.py b/tests/algorithms/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/algorithms/test_pivot.py b/tests/algorithms/test_pivot.py deleted file mode 100644 index 5d1dc77..0000000 --- a/tests/algorithms/test_pivot.py +++ /dev/null @@ -1,243 +0,0 @@ -# -*- coding: utf-8 -*- -"""Tests for sksrurgerycalibration pivot calibration""" -from glob import glob -from random import seed -import numpy as np -import pytest -import sksurgerycalibration.algorithms.pivot as p - - -def test_empty_matrices(): - """Throws a type error if empty matrices are None""" - - with pytest.raises(TypeError): - p.pivot_calibration(None) - - -def test_rank_lt_six(): - """Throw a value error if matrix rank is less than 6?""" - with pytest.raises(ValueError): - file_names = glob('tests/data/PivotCalibration/1378476417807806000.txt') - arrays = [np.loadtxt(f) for f in file_names] - matrices = np.concatenate(arrays) - number_of_matrices = int(matrices.size/16) - matrices = matrices.reshape((number_of_matrices, 4, 4)) - p.pivot_calibration_aos(matrices) - - -def test_unkown_method(): - """Throw value error if config set for unknown method""" - config = {"method" : "not implemented"} - matrix = (np.arange(2, 18, dtype=float).reshape((1, 4, 4))) - with pytest.raises(ValueError): - p.pivot_calibration(matrix, config) - -def test_four_columns_matrices4x4(): - """Throw a value error if matrix is not 4 column""" - - with pytest.raises(ValueError): - p.pivot_calibration(np.arange(2, 14, dtype=float).reshape((1, 4, 3))) - - -def test_four_rows_matrices4x4(): - """Throw a value error if matrix is not 4 rows""" - - with pytest.raises(ValueError): - p.pivot_calibration(np.arange(2, 14, dtype=float).reshape((1, 3, 4))) - - -def test_return_value(): - """A regression test using some recorded data""" - - file_names = glob('tests/data/PivotCalibration/*') - arrays = [np.loadtxt(f) for f in file_names] - matrices = np.concatenate(arrays) - number_of_matrices = int(matrices.size/16) - matrices = matrices.reshape((number_of_matrices, 4, 4)) - config = {"method" : "aos"} - pointer_offset, pivot_point, residual_error = \ - p.pivot_calibration(matrices, config) - assert round(residual_error, 3) == 1.761 - assert round(pointer_offset[0, 0], 3) == -14.473 - assert round(pointer_offset[1, 0], 3) == 394.634 - assert round(pointer_offset[2, 0], 3) == -7.407 - assert round(pivot_point[0, 0], 3) == -804.742 - assert round(pivot_point[1, 0], 3) == -85.474 - assert round(pivot_point[2, 0], 3) == -2112.131 - - -def test_pivot_with_ransac(): - """Tests that pivot with ransac runs""" - #seed the random number generator. Seeding - #with 0 leads to one failed pivot calibration (rank < 6), so we - #hit lines 127-129 - seed(0) - - file_names = glob('tests/data/PivotCalibration/*') - arrays = [np.loadtxt(f) for f in file_names] - matrices = np.concatenate(arrays) - number_of_matrices = int(matrices.size/16) - matrices = matrices.reshape((number_of_matrices, 4, 4)) - _, _, residual_1 = p.pivot_calibration(matrices) - _, _, residual_2 = p.pivot_calibration_with_ransac(matrices, 10, 4, 0.25) - assert residual_2 < residual_1 - _, _, _ = p.pivot_calibration_with_ransac(matrices, - 10, 4, 0.25, - early_exit=True) - #tests for the value checkers at the start of RANSAC - with pytest.raises(ValueError): - _, _, _ = p.pivot_calibration_with_ransac(None, 0, None, None) - - with pytest.raises(ValueError): - _, _, _ = p.pivot_calibration_with_ransac(None, 2, -1.0, None) - - with pytest.raises(ValueError): - _, _, _ = p.pivot_calibration_with_ransac(None, 2, 1.0, 1.1) - - with pytest.raises(TypeError): - _, _, _ = p.pivot_calibration_with_ransac(None, 2, 1.0, 0.8) - - #with consensus threshold set to 1.0, we get a value error - #as no best model is found. - with pytest.raises(ValueError): - _, _, _ = p.pivot_calibration_with_ransac(matrices, - 10, 4, 1.0, - early_exit=True) - - -def test_pivot_with_sphere_fit(): - """Tests pivot calibration with sphere fitting""" - config = {"method" : "sphere_fitting"} - file_names = glob('tests/data/PivotCalibration/*') - arrays = [np.loadtxt(f) for f in file_names] - matrices = np.concatenate(arrays) - number_of_matrices = int(matrices.size/16) - matrices = matrices.reshape((number_of_matrices, 4, 4)) - _, _, residual_error = p.pivot_calibration(matrices, config) - - #do a regression test on the residual error - assert round(residual_error, 3) == 2.346 - - -def test_replace_small_values(): - """Tests for small values replacement""" - list_in = [0.2, 0.6, 0.0099, 0.56] - - rank = p._replace_small_values(list_in) #pylint: disable=protected-access - - assert rank == 3 - assert list_in[2] == 0 - - rank = p._replace_small_values( #pylint: disable=protected-access - list_in, - threshold=0.3, - replacement_value=-1.0) - - assert rank == 2 - assert list_in[0] == -1.0 - assert list_in[2] == -1.0 - - -def _rms(*values): - """ - Works out RMS error, so we can test against it. - """ - count = len(values) - square_sum = 0.0 - for value in values: - square_sum += value * value - - mean_square_sum = square_sum / count - return np.sqrt(mean_square_sum) - -def test_residual_error0(): - """ - Test that residual error returns a correct value - """ - - pivot_point = [0.0, 0.0, 0.0] - pointer_offset = [-100.0, 0.0, 0.0] - - tracker_mat = np.array([[[1.0, 0.0, 0.0, 110.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0]] - ]) - #error is 10.0 in one direction and zero in the others - expected_value = _rms(10.0, 0.0, 0.0) - residual_error = p._residual_error( #pylint: disable=protected-access - tracker_mat, - pointer_offset, pivot_point) - assert residual_error == expected_value - - -def test_residual_error1(): - """ - Test that residual error returns a correct value - """ - - pivot_point = [0.0, 0.0, 0.0] - pointer_offset = [-100.0, 0.0, 0.0] - - tracker_mat = np.array([[[1.0, 0.0, 0.0, 100.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0]] - ]) - - #error in all three directions is zero - expected_value = _rms(0.0, 0.0, 0.0) - residual_error = p._residual_error( #pylint: disable=protected-access - tracker_mat, - pointer_offset, pivot_point) - assert residual_error == expected_value - - -def test_residual_error2(): - """ - Test that residual error returns a correct value - """ - - pivot_point = [0.0, 0.0, 0.0] - pointer_offset = [-100.0, 0.0, 0.0] - - - tracker_mat = np.array([[[1.0, 0.0, 0.0, 110.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0]], - - [[1.0, 0.0, 0.0, 90.0], - [0.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0]], - ]) - - #error in all 2 of 6 directions is 10.0 - expected_value = _rms(10.0, 0.0, 0.0, -10.0, 0.0, 0.0) - residual_error = p._residual_error( #pylint: disable=protected-access - tracker_mat, - pointer_offset, pivot_point) - assert residual_error == expected_value - - -def test_residual_error3(): - """ - Test that residual error returns a correct value - """ - - pivot_point = [0.0, 0.0, 0.0] - pointer_offset = [-100.0, 0.0, 0.0] - - tracker_mat = np.array([[[1.0, 0.0, 0.0, 110.0], - [0.0, 1.0, 0.0, 10.0], - [0.0, 0.0, 1.0, -10.0], - [0.0, 0.0, 0.0, 1.0]] - ]) - - #error in all three directions is 10.0 - expected_value = _rms(10.0, 10.0, -10.0) - residual_error = p._residual_error( #pylint: disable=protected-access - tracker_mat, - pointer_offset, pivot_point) - assert residual_error == expected_value diff --git a/tests/algorithms/test_sphere_fitting.py b/tests/algorithms/test_sphere_fitting.py deleted file mode 100644 index 8e3c7bb..0000000 --- a/tests/algorithms/test_sphere_fitting.py +++ /dev/null @@ -1,42 +0,0 @@ -# coding=utf-8 - -"""scikit-surgerycalibration sphere-fitting tests""" - -import numpy -from sksurgerycalibration.algorithms import sphere_fitting - -def test_fit_sphere_least_squares(): - """Test that it fits a sphere with some gaussian noise""" - x_centre = 1.74839 - y_centre = 167.0899222 - z_centre = 200.738829 - - radius = 7.543589 - - #some arrays to fit data to - coordinates = numpy.ndarray(shape=(1000, 3), dtype=float) - - #fill the arrays with points uniformly spread on - #a sphere centred at x,y,z with radius radius - #first seed the random generator so we get consistent test behaviour - numpy.random.seed(seed=0) - for i in range(1000): - #make a random vector - x_vector = numpy.random.uniform(-1.0, 1.0) - y_vector = numpy.random.uniform(-1.0, 1.0) - z_vector = numpy.random.uniform(-1.0, 1.0) - - #scale it to length radius - length = numpy.sqrt((x_vector)**2 + (y_vector)**2 + (z_vector)**2) - factor = radius / length - - coordinates[i, 0] = x_vector*factor + x_centre - coordinates[i, 1] = y_vector*factor + y_centre - coordinates[i, 2] = z_vector*factor + z_centre - - parameters = [0.0, 0.0, 0.0, 0.0] - result = sphere_fitting.fit_sphere_least_squares(coordinates, parameters) - numpy.testing.assert_approx_equal(result.x[0], x_centre, significant=10) - numpy.testing.assert_approx_equal(result.x[1], y_centre, significant=10) - numpy.testing.assert_approx_equal(result.x[2], z_centre, significant=10) - numpy.testing.assert_approx_equal(result.x[3], radius, significant=10) diff --git a/tests/algorithms/test_triangulate.py b/tests/algorithms/test_triangulate.py deleted file mode 100644 index ba26f06..0000000 --- a/tests/algorithms/test_triangulate.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -"""Tests for sksrurgerycalibration triangulate""" - -import time - -import numpy as np - -import sksurgerycalibration.algorithms.triangulate as at - - -def load_chessboard_arrays(): - """ - Load array of data for "Chessboard Test" - """ - - points_in_2d = np.zeros((4, 4), dtype=np.double) - points_in_2d[0, 0] = 1100.16 - points_in_2d[0, 1] = 262.974 - points_in_2d[0, 2] = 1184.84 - points_in_2d[0, 3] = 241.915 - points_in_2d[1, 0] = 1757.74 - points_in_2d[1, 1] = 228.971 - points_in_2d[1, 2] = 1843.52 - points_in_2d[1, 3] = 204.083 - points_in_2d[2, 0] = 1065.44 - points_in_2d[2, 1] = 651.593 - points_in_2d[2, 2] = 1142.75 - points_in_2d[2, 3] = 632.817 - points_in_2d[3, 0] = 1788.22 - points_in_2d[3, 1] = 650.41 - points_in_2d[3, 2] = 1867.78 - points_in_2d[3, 3] = 632.59 - - left_undistorted = np.zeros((4, 2), dtype=np.double) - left_undistorted[0, 0] = points_in_2d[0, 0] - left_undistorted[0, 1] = points_in_2d[0, 1] - left_undistorted[1, 0] = points_in_2d[1, 0] - left_undistorted[1, 1] = points_in_2d[1, 1] - left_undistorted[2, 0] = points_in_2d[2, 0] - left_undistorted[2, 1] = points_in_2d[2, 1] - left_undistorted[3, 0] = points_in_2d[3, 0] - left_undistorted[3, 1] = points_in_2d[3, 1] - - right_undistorted = np.zeros((4, 2), dtype=np.double) - right_undistorted[0, 0] = points_in_2d[0, 2] - right_undistorted[0, 1] = points_in_2d[0, 3] - right_undistorted[1, 0] = points_in_2d[1, 2] - right_undistorted[1, 1] = points_in_2d[1, 3] - right_undistorted[2, 0] = points_in_2d[2, 2] - right_undistorted[2, 1] = points_in_2d[2, 3] - right_undistorted[3, 0] = points_in_2d[3, 2] - right_undistorted[3, 1] = points_in_2d[3, 3] - - left_intrinsic = np.eye(3, dtype=np.double) - left_intrinsic[0, 0] = 2012.186314 - left_intrinsic[1, 1] = 2017.966019 - left_intrinsic[0, 2] = 944.7173708 - left_intrinsic[1, 2] = 617.1093984 - - right_intrinsic = np.eye(3, dtype=np.double) - right_intrinsic[0, 0] = 2037.233928 - right_intrinsic[1, 1] = 2052.018948 - right_intrinsic[0, 2] = 1051.112809 - right_intrinsic[1, 2] = 548.0675962 - - left_to_right_rotation = np.eye(3, dtype=np.double) - left_to_right_rotation[0, 0] = 0.999678 - left_to_right_rotation[0, 1] = 0.000151 - left_to_right_rotation[0, 2] = 0.025398 - left_to_right_rotation[1, 0] = -0.000720 - left_to_right_rotation[1, 1] = 0.999749 - left_to_right_rotation[1, 2] = 0.022394 - left_to_right_rotation[2, 0] = -0.025388 - left_to_right_rotation[2, 1] = -0.022405 - left_to_right_rotation[2, 2] = 0.999426 - - left_to_right_translation = np.zeros((3, 1), dtype=np.double) - left_to_right_translation[0, 0] = -4.631472 - left_to_right_translation[1, 0] = 0.268695 - left_to_right_translation[2, 0] = 1.300256 - - model_points = np.zeros((4, 3), dtype=np.double) - model_points[0, 0] = 0 - model_points[0, 1] = 0 - model_points[0, 2] = 0 - model_points[1, 0] = 39 - model_points[1, 1] = 0 - model_points[1, 2] = 0 - model_points[2, 0] = 0 - model_points[2, 1] = 27 - model_points[2, 2] = 0 - model_points[3, 0] = 39 - model_points[3, 1] = 27 - model_points[3, 2] = 0 - - left_rotation = np.zeros((3, 3), dtype=np.double) - left_rotation[0, 0] = 0.966285949 - left_rotation[0, 1] = -0.1053020017 - left_rotation[0, 2] = 0.2349530874 - left_rotation[1, 0] = -0.005105986897 - left_rotation[1, 1] = 0.9045241988 - left_rotation[1, 2] = 0.4263917244 - left_rotation[2, 0] = -0.2574206552 - left_rotation[2, 1] = -0.4132159994 - left_rotation[2, 2] = 0.8734913532 - - left_translation = np.zeros((3, 1), dtype=np.double) - left_translation[0, 0] = 9.847672184 - left_translation[1, 0] = -22.45992103 - left_translation[2, 0] = 127.7836183 - - return points_in_2d, \ - left_undistorted, \ - right_undistorted, \ - left_intrinsic, \ - right_intrinsic, \ - left_to_right_rotation, \ - left_to_right_translation, \ - model_points, \ - left_rotation, \ - left_translation - - -def rms_between_points(a_array, b_array): - """ - Compute Root Mean Squater between_corresponding_points a, b. - """ - rms = 0 - diff = 0 - squared_diff = 0 - - if a_array.shape[0] != b_array.shape[0]: - print(f' a has {a_array.shape[0]} rows but b has {b_array.shape[0]} rows') - - if a_array.shape[1] != 3: - print(f'a does not have 3 columns but {a_array.shape[1]} columns') - - if b_array.shape[1] != 3: - print(f'b does not have 3 columns but {b_array.shape[1]} columns') - - for dummy_row_index in range(a_array.shape[0]): - for dummy_col_index in range(a_array.shape[1]): - diff = b_array[dummy_row_index, dummy_col_index] - a_array[dummy_row_index, dummy_col_index] - squared_diff = diff * diff - rms += squared_diff - - rms /= a_array.shape[0] - rms = np.sqrt(rms) - return rms - - -def test_triangulate_points_hartley(): - """ - Test triangulate points with hartley using "Chessboard Test" - """ - - points_in_2d, _left_undistorted, _right_undistorted, left_intrinsic, right_intrinsic, \ - left_to_right_rotation, left_to_right_translation, model_points, left_rotation, left_translation = load_chessboard_arrays() - - model_points_transposed = model_points.T - rotated_model_points = np.zeros((model_points_transposed.shape[0], model_points_transposed.shape[1]), - dtype=np.double) - # rotated_model_points = cv2.gemm(src1=left_rotation, src2=model_points_transposed, alpha=1.0, src3=None, - # beta=0.0) # flags=cv2.GEMM_2_T? - rotated_model_points = left_rotation.dot(model_points_transposed) - model_points_rotated_transposed = rotated_model_points.T - transformed_model_points = np.zeros( - (model_points_rotated_transposed.shape[0], model_points_rotated_transposed.shape[1]), dtype=np.double) - - for dummy_row_index in range(model_points_rotated_transposed.shape[0]): - for dummy_col_index in range(model_points_rotated_transposed.shape[1]): - transformed_model_points[dummy_row_index, dummy_col_index] = model_points_rotated_transposed[ - dummy_row_index, dummy_col_index] + \ - left_translation[dummy_col_index, 0] - - start = time.time_ns() - points_from_hartley = at.triangulate_points_hartley(points_in_2d, - left_intrinsic, - right_intrinsic, - left_to_right_rotation, - left_to_right_translation) - print(f'\n Elapsed time for at.triangulate_points_hartley(): {(time.time_ns() - start) / 1e6} millisecs ') - - start = time.time_ns() - points_from_hartley_opencv = at.triangulate_points_opencv(points_in_2d, - left_intrinsic, - right_intrinsic, - left_to_right_rotation, - left_to_right_translation) - print(f'\n Elapsed time for at.triangulate_points_opencv(): {(time.time_ns() - start) / 1e6} millisecs') - - rms_hartley = rms_between_points(transformed_model_points, points_from_hartley) - rms_hartley_opencv = rms_between_points(transformed_model_points, points_from_hartley_opencv) - - print(f'\n rms_hartley: {rms_hartley} and test (rms_hartley < 1.5)') - print(f'\n rms_hartley_opencv: {rms_hartley_opencv} and test (rms_hartley < 1.5) \n') - assert rms_hartley < 1.5 and rms_hartley_opencv < 1.5 diff --git a/tests/ui/__init__.py b/tests/ui/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/ui/test_pivot_calibration.py b/tests/ui/test_pivot_calibration.py deleted file mode 100644 index 91d60c2..0000000 --- a/tests/ui/test_pivot_calibration.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Tests for command line application """ -from sksurgerycalibration.ui.pivot_calibration_command_line import main - -def test_cl_no_config(): - """ Run command line app with no config file """ - main(['-i', 'tests/data/PivotCalibration/']) - - -def test_cl_ransac_config(): - """ Run command line app with ransac in config """ - main(['-i', 'tests/data/PivotCalibration/', '-c', - 'config/ransac_conf.json']) diff --git a/tests/video/test_charuco_plus_chessboard.py b/tests/video/test_charuco_plus_chessboard.py deleted file mode 100644 index 1cac9f5..0000000 --- a/tests/video/test_charuco_plus_chessboard.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- - -# pylint: disable=unused-import, superfluous-parens, line-too-long, missing-module-docstring, unused-variable, missing-function-docstring, invalid-name - -import glob -import pytest -import numpy as np -import cv2 -import sksurgeryimage.calibration.charuco as ch -import sksurgeryimage.calibration.charuco_plus_chessboard_point_detector as pd -import sksurgerycalibration.video.video_calibration_driver_stereo as sc - - -def test_stereo_davinci(): - - left_images = [] - files = glob.glob('tests/data/ChAruco_LR_frames_Steve_Axis_Tests/ExtractedFrames_L/*.jpg') - files.sort() - for file in files: - image = cv2.imread(file) - print("Loaded:" + str(file)) - left_images.append(image) - assert(len(left_images) == 59) - - right_images = [] - files = glob.glob('tests/data/ChAruco_LR_frames_Steve_Axis_Tests/ExtractedFrames_R/*.jpg') - files.sort() - for file in files: - image = cv2.imread(file) - print("Loaded:" + str(file)) - right_images.append(image) - assert (len(right_images) == 59) - - ref_img = cv2.imread('tests/data/2020_01_20_storz/pattern_4x4_19x26_5_4_with_inset_9x14.png') - - minimum_number_of_points_per_image = 50 - detector = pd.CharucoPlusChessboardPointDetector(ref_img, - error_if_no_chessboard=False) # Try to accept as many as possible. - calibrator = sc.StereoVideoCalibrationDriver(detector, detector, minimum_number_of_points_per_image) - for i, _ in enumerate(left_images): - try: - number_left, number_right = calibrator.grab_data(left_images[i], right_images[i]) - if number_left < minimum_number_of_points_per_image: - print("Image pair:" + str(i) + ", left image, SKIPPED, due to not enough points") - if number_right < minimum_number_of_points_per_image: - print("Image pair:" + str(i) + ", right image, SKIPPED, due to not enough points") - except ValueError as ve: - print("Image pair:" + str(i) + ", FAILED, due to:" + str(ve)) - except TypeError as te: - print("Image pair:" + str(i) + ", FAILED, due to:" + str(te)) - - reproj_err, recon_err, params = calibrator.calibrate() - print("Reproj:" + str(reproj_err)) - print("Recon:" + str(recon_err)) - assert reproj_err < 1.1 - assert recon_err < 6.4 - - # Now try iterative. - reference_image = ch.make_charuco_with_chessboard() - reference_ids, object_pts, reference_pts = detector.get_points(reference_image) - reproj_err, recon_err, params = calibrator.iterative_calibration(2, - reference_ids, - reference_pts, - (reference_image.shape[1], reference_image.shape[0])) - print("Reproj:" + str(reproj_err)) - print("Recon:" + str(recon_err)) - assert reproj_err < 0.9 - assert recon_err < 2.78 - calibrator.save_params('tests/output/ChAruco_LR_frames_Steve_Axis_Tests/params', '') - calibrator.save_data('tests/output/ChAruco_LR_frames_Steve_Axis_Tests/data', '') diff --git a/tests/video/test_chessboard_calibration.py b/tests/video/test_chessboard_calibration.py deleted file mode 100644 index ed7ba37..0000000 --- a/tests/video/test_chessboard_calibration.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- - -# pylint: disable=unused-import, superfluous-parens, line-too-long, missing-module-docstring, unused-variable, missing-function-docstring, invalid-name - -import glob -import pytest -import numpy as np -import cv2 -import sksurgeryimage.calibration.chessboard_point_detector as pd -import sksurgerycalibration.video.video_calibration_driver_mono as mc -import sksurgerycalibration.video.video_calibration_driver_stereo as sc -import sksurgerycalibration.video.video_calibration_utils as vu -import tests.video.video_testing_utils as vtu - - -def get_iterative_reference_data(): - number_of_points = 140 - x_size = 14 - y_size = 10 - pixels_per_square = 20 - reference_ids = np.zeros((number_of_points, 1)) - reference_points = np.zeros((number_of_points, 2)) - counter = 0 - for y_index in range(y_size): - for x_index in range(x_size): - reference_ids[counter][0] = counter - reference_points[counter][0] = (x_index + 2) * pixels_per_square - reference_points[counter][1] = (y_index + 2) * pixels_per_square - counter = counter + 1 - reference_image_size = ((x_size + 4) * pixels_per_square, (y_size + 4) * pixels_per_square) - return reference_ids, reference_points, reference_image_size - - -def test_chessboard_mono(): - - images = [] - - files = glob.glob('tests/data/laparoscope_calibration/left/*.png') - for file in files: - image = cv2.imread(file) - images.append(image) - - # This illustrates that the PointDetector sub-class holds the knowledge of the model. - chessboard_detector = \ - pd.ChessboardPointDetector((14, 10), # (Width, height), number of internal corners - 3, # Actual square size in mm - (1, 1) # Scale factors. Here, images are 1920x1080 so no scaling needed. - ) # If images were 1920x540, you'd pass in (1920, 540), - # and PointDetector base class would scale it up. - - # Pass a PointDetector sub-class to the calibration driver. - calibrator = \ - mc.MonoVideoCalibrationDriver(chessboard_detector, 140) - - # Repeatedly grab data, until you have enough. - for image in images: - successful = calibrator.grab_data(image, np.eye(4)) - assert successful > 0 - assert calibrator.is_device_tracked() - assert not calibrator.is_calibration_target_tracked() - - # Extra checking, as its a unit test - assert calibrator.get_number_of_views() == 9 - calibrator.pop() - assert calibrator.get_number_of_views() == 8 - successful = calibrator.grab_data(images[-1]) - assert successful - assert calibrator.get_number_of_views() == 9 - - # Then do calibration - reproj_err, params = calibrator.calibrate() - - # Just for a regression test, checking reprojection error. - # We do appear to get different performance on Linux/Mac - assert reproj_err < 0.6 - - # Test components of iterative calibration. - original_image = calibrator.video_data.images_array[0] - _, _, original_pts = chessboard_detector.get_points(original_image) - - # First ensure undistorting / redistorting points works. - undistorted_points = cv2.undistortPoints(original_pts, - calibrator.calibration_params.camera_matrix, - calibrator.calibration_params.dist_coeffs, - None, - calibrator.calibration_params.camera_matrix - ) - distorted_pts = vu.distort_points(undistorted_points.reshape((-1, 2)), - calibrator.calibration_params.camera_matrix, - calibrator.calibration_params.dist_coeffs) - assert np.allclose(original_pts, distorted_pts, rtol=1e-4, atol=1e-4) - - rectify_map_1, rectify_map_2 = cv2.initUndistortRectifyMap(calibrator.calibration_params.camera_matrix, - calibrator.calibration_params.dist_coeffs, - None, - calibrator.calibration_params.camera_matrix, - (original_image.shape[1], original_image.shape[0]), - cv2.CV_32FC1 - ) - #undistorted_image = cv2.undistort(original_image, calibrator.calibration_params.camera_matrix, calibrator.calibration_params.dist_coeffs, calibrator.calibration_params.camera_matrix) - undistorted_image = cv2.remap(original_image, rectify_map_1, rectify_map_2, cv2.INTER_LANCZOS4) - - _, _, undistorted_pts = chessboard_detector.get_points(undistorted_image) - distorted_pts = vu.distort_points(undistorted_pts, calibrator.calibration_params.camera_matrix, calibrator.calibration_params.dist_coeffs) - assert np.allclose(original_pts, distorted_pts, rtol=1e-1, atol=1e-1) - - # Test iterative calibration. - reference_ids, reference_points, reference_image_size = get_iterative_reference_data() - - reproj_err, params = calibrator.iterative_calibration(3, - reference_ids, - reference_points, - reference_image_size) - assert reproj_err < 0.7 - - -def test_chessboard_stereo(): - - left_images, right_images \ - = vtu.load_left_right_pngs('tests/data/laparoscope_calibration/', 9) - - chessboard_detector = \ - pd.ChessboardPointDetector((14, 10), - 3, - (1, 1) - ) - - calibrator = \ - sc.StereoVideoCalibrationDriver(chessboard_detector, chessboard_detector, 140) - - # Repeatedly grab data, until you have enough. - for i, _ in enumerate(left_images): - number_left, number_right = calibrator.grab_data(_, right_images[i]) - assert number_left > 0 - assert number_right > 0 - assert not calibrator.is_device_tracked() - assert not calibrator.is_calibration_target_tracked() - - # Then do calibration - reproj_err, recon_err, params = calibrator.calibrate() - - # Just for a regression test, checking reprojection error, and recon error. - print("\nStereo, default=" + str(reproj_err) + ", " + str(recon_err)) - assert reproj_err < 0.7 - assert recon_err < 1.7 - - # Test running with fixed intrinsics and fixed stereo, using existing - # calibration parameters, thereby re-optimising the camera poses. - reproj_err, recon_err, params = \ - calibrator.calibrate( - override_left_intrinsics=params.left_params.camera_matrix, - override_left_distortion=params.left_params.dist_coeffs, - override_right_intrinsics=params.right_params.camera_matrix, - override_right_distortion=params.right_params.dist_coeffs, - override_l2r_rmat=params.l2r_rmat, - override_l2r_tvec=params.l2r_tvec - ) - - # The above re-optimisation shouldn't make things worse, as its using same intrinsics and stereo. - print("Stereo, re-optimise=" + str(reproj_err) + ", " + str(recon_err)) - assert reproj_err < 0.7 - assert recon_err < 1.7 - - # Test iterative calibration. - reference_ids, reference_points, reference_image_size = get_iterative_reference_data() - - reproj_err, recon_err, params = calibrator.iterative_calibration(3, - reference_ids, - reference_points, - reference_image_size) - print("Stereo, iterative=" + str(reproj_err) + ", " + str(recon_err)) - assert reproj_err < 0.7 - assert recon_err < 1.6 - - # Now test re-optimising extrinsics, using a completely different set of calibration params. - ov_l_c = np.loadtxt('tests/data/laparoscope_calibration/cbh-viking/calib.left.intrinsics.txt') - ov_l_d = np.loadtxt('tests/data/laparoscope_calibration/cbh-viking/calib.left.distortion.txt') - ov_r_c = np.loadtxt('tests/data/laparoscope_calibration/cbh-viking/calib.right.intrinsics.txt') - ov_r_d = np.loadtxt('tests/data/laparoscope_calibration/cbh-viking/calib.right.distortion.txt') - - ov_l2r_t = np.zeros((3, 1)) - ov_l2r_t[0][0] = -4.5 - - reproj_err, recon_err, params = \ - calibrator.calibrate( - override_left_intrinsics=ov_l_c, - override_left_distortion=ov_l_d, - override_right_intrinsics=ov_r_c, - override_right_distortion=ov_r_d, - override_l2r_rmat=np.eye(3), - override_l2r_tvec=ov_l2r_t - ) - - # Must check that the overrides have actually been set on the output. - assert np.allclose(params.left_params.camera_matrix, ov_l_c) - assert np.allclose(params.left_params.dist_coeffs, ov_l_d) - assert np.allclose(params.right_params.camera_matrix, ov_r_c) - assert np.allclose(params.right_params.dist_coeffs, ov_r_d) - assert np.allclose(params.l2r_rmat, np.eye(3)) - assert np.allclose(params.l2r_tvec, ov_l2r_t) - - # Not expecting good results, as the camera parameters are completely wrong. - print("Stereo, override=" + str(reproj_err) + ", " + str(recon_err)) - assert reproj_err < 35 - assert recon_err < 109 diff --git a/tests/video/test_data_param_io.py b/tests/video/test_data_param_io.py deleted file mode 100644 index cd39f7e..0000000 --- a/tests/video/test_data_param_io.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- - -# pylint: disable=unused-import, superfluous-parens, line-too-long, missing-module-docstring, unused-variable, missing-function-docstring, invalid-name - -import glob -import pytest -import numpy as np -import cv2 -import sksurgeryimage.calibration.chessboard_point_detector as pd -import sksurgerycalibration.video.video_calibration_driver_mono as mc -import sksurgerycalibration.video.video_calibration_driver_stereo as sc -import tests.video.video_testing_utils as vtu - - -def test_chessboard_mono_io(): - - images = [] - - files = glob.glob('tests/data/laparoscope_calibration/left/*.png') - for file in files: - image = cv2.imread(file) - images.append(image) - - chessboard_detector = pd.ChessboardPointDetector((14, 10), 3, (1, 1)) - calibrator = mc.MonoVideoCalibrationDriver(chessboard_detector, 140) - for image in images: - successful = calibrator.grab_data(image, np.eye(4), np.eye(3)) - assert successful > 0 - - reproj_err_1, params_1 = calibrator.calibrate() - calibrator.save_data('tests/output/test_chessboard_mono_io', '') - calibrator.save_params('tests/output/test_chessboard_mono_io', '') - - # Now, load data back in, recalibrate, should get same results. - # Technically, you aren't running the 'grab' bit again. - # The calibration works off the already extracted points. - calibrator.load_data('tests/output/test_chessboard_mono_io', '') - reproj_err_2, params_2 = calibrator.calibrate() - assert (np.isclose(reproj_err_1, reproj_err_2)) - - calibrator.load_params('tests/output/test_chessboard_mono_io', '') - params_3 = calibrator.get_params() - assert np.allclose(params_2.camera_matrix, params_3.camera_matrix) - assert np.allclose(params_2.dist_coeffs, params_3.dist_coeffs) - for i, _ in enumerate(params_3.rvecs): - assert np.allclose(params_2.rvecs[i], params_3.rvecs[i]) - assert np.allclose(params_2.tvecs[i], params_3.tvecs[i]) - - -def test_chessboard_stereo_io(): - - left_images, right_images \ - = vtu.load_left_right_pngs('tests/data/laparoscope_calibration/', 9) - - chessboard_detector = \ - pd.ChessboardPointDetector((14, 10), - 3, - (1, 1) - ) - - calibrator = \ - sc.StereoVideoCalibrationDriver(chessboard_detector, chessboard_detector, 140) - - for i, _ in enumerate(left_images): - num_left, num_right = calibrator.grab_data(_, right_images[i], np.eye(4), np.eye(3)) - assert num_left > 0 - assert num_right > 0 - - # Then do calibration - reproj_err_1, recon_err_1, params_1 = calibrator.calibrate() - calibrator.save_data('tests/output/test_chessboard_stereo_io', '') - calibrator.save_params('tests/output/test_chessboard_stereo_io', '') - - # Load data, re-do calibration, check for same result. - calibrator.load_data('tests/output/test_chessboard_stereo_io', '') - reproj_err_2, recon_err_2, params_2 = calibrator.calibrate() - assert (np.isclose(reproj_err_1, reproj_err_2)) - assert (np.isclose(recon_err_1, recon_err_2)) - - # Now load parameters back in. - calibrator.load_params('tests/output/test_chessboard_stereo_io', '') - params_2 = calibrator.get_params() - - assert np.allclose(params_1.l2r_rmat, - params_2.l2r_rmat) - assert np.allclose(params_1.l2r_tvec, - params_2.l2r_tvec) - assert np.allclose(params_1.essential, - params_2.essential) - assert np.allclose(params_1.fundamental, - params_2.fundamental) diff --git a/tests/video/test_hand_eye.py b/tests/video/test_hand_eye.py deleted file mode 100644 index c9bd3ff..0000000 --- a/tests/video/test_hand_eye.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Tests for calibration_manager. -""" - -import glob -import cv2 -import pytest -import numpy as np -import sksurgeryimage.calibration.chessboard_point_detector as pd -import sksurgeryimage.calibration.charuco_plus_chessboard_point_detector as chpd -import sksurgerycalibration.video.video_calibration_driver_mono as mcd -import sksurgerycalibration.video.video_calibration_driver_stereo as scd - - -def load_images_from_glob(glob_pattern): - """ Load images from files based on a glob pattern. """ - images = [] - files = glob.glob(glob_pattern) - files.sort() - for file in files: - image = cv2.imread(file) - images.append(image) - - return images - - -def load_tracking_from_glob(glob_pattern): - """ Load tracking data from files based on a glob pattern. """ - tracking = [] - files = glob.glob(glob_pattern) - files.sort() - for file in files: - data = np.loadtxt(file) - tracking.append(data) - - return tracking - - -def test_handeye_calibration_mono(): - """ - Load mono data (only using left channel) and tracking, do video and - handeye calibration, compare results against expected values. - """ - images = load_images_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.left.*.png') - - device_tracking = load_tracking_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.device_tracking.*.txt') - - obj_tracking = load_tracking_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.calib_obj_tracking.*.txt') - - ref_img = cv2.imread( - 'tests/data/2020_01_20_storz/pattern_4x4_19x26_5_4_with_inset_9x14.png') - - assert len(images) == 10 - - assert len(device_tracking) == 10 - assert len(obj_tracking) == 10 - - min_number_of_points_per_image = 50 - detector = \ - chpd.CharucoPlusChessboardPointDetector(ref_img, - error_if_no_chessboard=False) - - calibrator = \ - mcd.MonoVideoCalibrationDriver(detector, min_number_of_points_per_image) - - # Grab data from images/tracking arrays - for image, device, calib_obj in zip(images, device_tracking, obj_tracking): - successful = calibrator.grab_data(image, device, calib_obj) - assert successful > 0 - - reproj_err_1, _ = calibrator.calibrate() - - assert reproj_err_1 == pytest.approx(1., rel=0.2) - - proj_err, _ = calibrator.handeye_calibration() - - print(f'Reproj err {proj_err}') - - # These values are taken from a previous successful run - # Not objective measures of correctness - expected_reproj_error = 10.412839 - - assert proj_err == pytest.approx(expected_reproj_error, rel=0.1) - - -def test_handeye_calibration_stereo(): - """ - Load Stereo data and tracking, do video and - handeye calibration, compare results against expected values. - """ - left_images = load_images_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.left.*.png') - - right_images = load_images_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.right.*.png') - - device_tracking = load_tracking_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.device_tracking.*.txt') - - obj_tracking = load_tracking_from_glob( - 'tests/data/2020_01_20_storz/12_50_30/calib.calib_obj_tracking.*.txt') - - ref_img = cv2.imread( - 'tests/data/2020_01_20_storz/pattern_4x4_19x26_5_4_with_inset_9x14.png') - - assert len(left_images) == 10 - assert len(right_images) == 10 - assert len(device_tracking) == 10 - assert len(obj_tracking) == 10 - - min_number_of_points_per_image = 50 - detector = \ - chpd.CharucoPlusChessboardPointDetector(ref_img, - charuco_filtering=True, - error_if_no_chessboard=False) - - calibrator = \ - scd.StereoVideoCalibrationDriver( - detector, detector, min_number_of_points_per_image) - - # Grab data from images/tracking arrays - for left, right, device, calib_obj in \ - zip(left_images, right_images, device_tracking, obj_tracking): - num_left, num_right = calibrator.grab_data(left, - right, - device, - calib_obj) - assert num_left > 0 - assert num_right > 0 - assert num_left < 480 - assert num_right < 480 - - reproj_err_1, recon_err_1, _ = calibrator.calibrate() - - assert reproj_err_1 == pytest.approx(0.6, rel=0.2) - assert recon_err_1 == pytest.approx(1., rel=0.2) - - proj_err, recon_err, _ = \ - calibrator.handeye_calibration(use_opencv=True, do_bundle_adjust=True) - - print(f'Reproj err {proj_err}') - print(f'Recon err {recon_err}') - - assert proj_err == pytest.approx(7.3, rel=1.7) - assert recon_err == pytest.approx(2.5, rel=0.3) - - # test save/load for hand-eye - calibrator.save_params('tests/output/test_handeye_calibration_stereo', '') - current_params = calibrator.get_params() - - calibrator.reinit() - calibrator.load_params('tests/output/test_handeye_calibration_stereo', '') - loaded_params = calibrator.get_params() - assert np.allclose(current_params.left_params.handeye_matrix, - loaded_params.left_params.handeye_matrix) - assert np.allclose(current_params.left_params.pattern2marker_matrix, - loaded_params.left_params.pattern2marker_matrix) - assert np.allclose(current_params.right_params.handeye_matrix, - loaded_params.right_params.handeye_matrix) - assert np.allclose(current_params.right_params.pattern2marker_matrix, - loaded_params.right_params.pattern2marker_matrix) - - -def test_load_data_stereo_calib(): - """ Load tracking and image data from test directory. """ - chessboard_detector = pd.ChessboardPointDetector((14, 10), 3, (1, 1)) - - stereo_calib = \ - scd.StereoVideoCalibrationDriver( - chessboard_detector, chessboard_detector, 140) - - tracking_data_dir = 'tests/data/2020_01_20_storz/12_50_30' - file_prefix = 'calib' - - stereo_calib.load_data(tracking_data_dir, file_prefix) - - assert len(stereo_calib.tracking_data.device_tracking_array) == 10 - assert len(stereo_calib.tracking_data.calibration_tracking_array) == 10 - assert len(stereo_calib.video_data.left_data.images_array) == 10 - assert len(stereo_calib.video_data.right_data.images_array) == 10 diff --git a/tests/video/test_handeye_dots.py b/tests/video/test_handeye_dots.py new file mode 100644 index 0000000..4bf25a5 --- /dev/null +++ b/tests/video/test_handeye_dots.py @@ -0,0 +1,327 @@ +# -*- coding: utf-8 -*- + +# pylint:disable=line-too-long + +""" Various tests to test the SmartLiver calibration using dotty pattern. """ + +import os +import numpy as np +import cv2 +import sksurgerycore.transforms.matrix as skcm +import sksurgerycore.algorithms.procrustes as pbr +import sksurgeryimage.calibration.dotty_grid_point_detector as dgpd +import sksurgerycalibration.video.video_calibration_driver_stereo as sc +import tests.video.test_load_calib_utils as lcu + + +def get_dotty_calib_driver(calib_dir: str): + """ + Utility function to setup stereo calibration driver and load data. + """ + minimum_points_per_frame = 36 + number_of_dots = [18, 25] + dot_separation = 5 + fiducial_indexes = [133, 141, 308, 316] + reference_image_size_in_pixels = [1900, 2600] + pixels_per_mm = 20 + + number_of_points = number_of_dots[0] * number_of_dots[1] + model_points = np.zeros((number_of_points, 6)) + counter = 0 + for y_index in range(number_of_dots[0]): + for x_index in range(number_of_dots[1]): + model_points[counter][0] = counter + model_points[counter][1] = (x_index + 1) * pixels_per_mm + model_points[counter][2] = (y_index + 1) * pixels_per_mm + model_points[counter][3] = x_index * dot_separation + model_points[counter][4] = y_index * dot_separation + model_points[counter][5] = 0 + counter = counter + 1 + + left_intrinsic_matrix = np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.left.intrinsics.txt") + left_distortion_matrix = np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.left.distortion.txt") + right_intrinsic_matrix = np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.right.intrinsics.txt") + right_distortion_matrix = np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.right.distortion.txt") + + threshold_offset = 20 + threshold_window_size = 151 + + left_pd = dgpd.DottyGridPointDetector(model_points, + list_of_indexes=fiducial_indexes, + camera_intrinsics=left_intrinsic_matrix, + distortion_coefficients=left_distortion_matrix, + reference_image_size=reference_image_size_in_pixels, + threshold_window_size=threshold_window_size, + threshold_offset=threshold_offset + ) + + right_pd = dgpd.DottyGridPointDetector(model_points, + list_of_indexes=fiducial_indexes, + camera_intrinsics=right_intrinsic_matrix, + distortion_coefficients=right_distortion_matrix, + reference_image_size=reference_image_size_in_pixels, + threshold_window_size=threshold_window_size, + threshold_offset=threshold_offset + ) + + calibration_driver = sc.StereoVideoCalibrationDriver(left_pd, + right_pd, + minimum_points_per_frame) + + for i in range(10): + l_img, r_img, chessboard, scope = lcu.get_calib_data(calib_dir, i) + num_points = calibration_driver.grab_data(l_img, r_img, scope, chessboard) + print("Grabbed " + str(num_points) + " points") + + return calibration_driver + + +def get_pattern_to_marker(): + """ + Creates a pattern 2 marker transform for the SmartLiver calibration pattern. + """ + plate_points = np.zeros((4, 3)) + plate_points[1][0] = 120 + plate_points[2][1] = 85 + plate_points[3][0] = 120 + plate_points[3][1] = 85 + + marker_points = np.ones((4, 3)) + marker_points[0][0] = -21.77 + marker_points[0][2] = -19.25 + marker_points[1][0] = -21.77 + marker_points[1][2] = 100.75 + marker_points[2][0] = -106.77 + marker_points[2][2] = -19.25 + marker_points[3][0] = -106.77 + marker_points[3][2] = 100.75 + + p2m_r, p2m_t, _ = pbr.orthogonal_procrustes(marker_points, plate_points) + + pattern2marker = skcm.construct_rigid_transformation(p2m_r, p2m_t) + return pattern2marker + + +def get_projection_errs_and_params(dir_name, + pattern2marker, + use_open_cv, + do_bundle_adjust, + save_data=False + ): + """ + Calibration driver. + """ + print("dir_name=" + dir_name) + + calib_driver = get_dotty_calib_driver(dir_name) + + reproj_err, recon_err, _ = calib_driver.calibrate() + + print("After initial calibration, reproj_err=" + str(reproj_err) + ", recon_err=" + str(recon_err)) + + tracked_reproj_err, tracked_recon_err, params_2 = \ + calib_driver.handeye_calibration(override_pattern2marker=pattern2marker, + use_opencv=use_open_cv, + do_bundle_adjust=do_bundle_adjust + ) + + print("After handeye calibration, tracked_reproj_err=" + str(tracked_reproj_err) + ", tracked_recon_err=" + str(tracked_recon_err)) + p2m = params_2.left_params.pattern2marker_matrix + if p2m is not None: + print("p2m=" + str(np.transpose(p2m[0:3, 3]))) + print("h2e=" + str(np.transpose(params_2.left_params.handeye_matrix[0:3, 3]))) + + if save_data: + output_calib_dir = os.path.join('tests', 'output', 'test_handeye_dots', dir_name) + calib_driver.save_data(output_calib_dir, 'calib') + calib_driver.save_params(output_calib_dir, 'calib') + + return reproj_err, recon_err, tracked_reproj_err, tracked_recon_err, params_2 + + +def get_dirs(is_paper=False): + """ + Just gets some data directories. + """ + dirs = [] + if is_paper: + dirs.append('tests/data/2022_02_11_calibration_viking_paper/18_36_09') + dirs.append('tests/data/2022_02_11_calibration_viking_paper/18_41_28') + dirs.append('tests/data/2022_02_11_calibration_viking_paper/18_44_06') + else: +# dirs.append('tests/data/2022_02_13_calibration_viking_metal/15_56_22') +# dirs.append('tests/data/2022_02_13_calibration_viking_metal/15_57_13') +# dirs.append('tests/data/2022_02_13_calibration_viking_metal/15_58_14') + +# dirs.append('tests/data/2022_02_13_calibration_viking_metal/16_13_39') +# dirs.append('tests/data/2022_02_13_calibration_viking_metal/16_20_03') +# dirs.append('tests/data/2022_02_13_calibration_viking_metal/16_24_24') + + dirs.append('tests/data/2022_02_28_calibration_viking_metal/calibration/14_58_31') + dirs.append('tests/data/2022_02_28_calibration_viking_metal/calibration/15_18_54') + dirs.append('tests/data/2022_02_28_calibration_viking_metal/calibration/15_22_44') + + return dirs + + +def get_fixed_pattern_dirs(): + """ + Just gets some data directories. + """ + dirs = [] + #dirs.append('tests/data/2022_02_28_fixed_position_calibs/calibration/14_58_31') + dirs.append('tests/data/2022_02_28_fixed_position_calibs/calibration/15_18_54') +# dirs.append('tests/data/2022_02_28_fixed_position_calibs/calibration/15_22_44') + return dirs + + +def get_calibrations(dirs, pattern2marker, use_opencv, use_bundle_adjust, save_data=False): + """ + Calibration driver. + """ + results = np.zeros((len(dirs), 8)) + counter = 0 + for dir_name in dirs: + _, _, tracked_reproj_err, tracked_recon_err, params_2 = \ + get_projection_errs_and_params(dir_name, pattern2marker, use_opencv, use_bundle_adjust, save_data) + rvec = np.zeros((3,1)) + cv2.Rodrigues(params_2.left_params.handeye_matrix[0:3, 0:3], rvec) + results[counter][0] = tracked_reproj_err + results[counter][1] = tracked_recon_err + results[counter][2] = params_2.left_params.handeye_matrix[0][3] + results[counter][3] = params_2.left_params.handeye_matrix[1][3] + results[counter][4] = params_2.left_params.handeye_matrix[2][3] + results[counter][5] = rvec[0][0] + results[counter][6] = rvec[1][0] + results[counter][7] = rvec[2][0] + counter = counter + 1 + return results + + +def test_gx_vs_cv(): + """ + Compares consistency of Guofang's method, with OpenCV. + """ + dirs = get_dirs() + results_gx = get_calibrations(dirs, None, False, False) + results_gx_m = np.mean(results_gx, axis=0) + results_gx_s = np.std(results_gx, axis=0) + results_opencv = get_calibrations(dirs, None, True, False) + results_opencv_m = np.mean(results_opencv, axis=0) + results_opencv_s = np.std(results_opencv, axis=0) + results_opencv_bundle = get_calibrations(dirs, None, True, True) + results_opencv_bundle_m = np.mean(results_opencv_bundle, axis=0) + results_opencv_bundle_s = np.std(results_opencv_bundle, axis=0) + + print("Guofang's mean=\n" + str(results_gx_m)) + print("OpenCV's mean=\n" + str(results_opencv_m)) + print("OpenCV's BA mean=\n" + str(results_opencv_bundle_m)) + + print("Guofang's stddev=\n" + str(results_gx_s)) + print("OpenCV's stddev=\n" + str(results_opencv_s)) + print("OpenCV's BA stddev=\n" + str(results_opencv_bundle_s)) + + # 1. Just looking at data, not much obvious difference. + # 2. Interestingly, looking at stddev of rotation and translation of + # hand-eye, most difference is translational. + # 3. Ultimately, chose OpenCV, with no bundle adjustment, as default, as then + # we can have more consistent implementation across different use-cases. + + # Just for regression testing: + + # Projection error < 4, should be ok for a stereo laparoscope + assert results_gx_m[0] < 4 + assert results_opencv_m[0] < 4 + assert results_opencv_bundle_m[0] < 4 + + # Reconstruction error < 1mm + assert results_gx_m[1] < 1 + assert results_opencv_m[1] < 1 + assert results_opencv_bundle_m[1] < 1 + + +def test_fixed_p2m(): + """ + Compares consistency of having a fixed pattern to marker. + """ + dirs = get_dirs() + p2m = get_pattern_to_marker() + results_no_p2m = get_calibrations(dirs, None, True, True) + results_no_p2m_m = np.mean(results_no_p2m, axis=0) + results_no_p2m_s = np.std(results_no_p2m, axis=0) + results_with_p2m = get_calibrations(dirs, p2m, True, True) + results_with_p2m_m = np.mean(results_with_p2m, axis=0) + results_with_p2m_s = np.std(results_with_p2m, axis=0) + + print("No p2m mean=\n" + str(results_no_p2m_m)) + print("With p2m mean=\n" + str(results_with_p2m_m)) + + print("No p2m stddev=\n" + str(results_no_p2m_s)) + print("With p2m stddev=\n" + str(results_with_p2m_s)) + + # Just for regression testing: + + # Projection error < 4, should be ok for a stereo laparoscope + assert results_no_p2m_m[0] < 4 + assert results_with_p2m_m[0] < 4 + + # Reconstruction error < 1mm + assert results_no_p2m_m[1] < 1 + assert results_with_p2m_m[1] < 1 + + +def test_tracked_vs_stationary(): + """ + Mainly to check code runs, as we don't yet have functional requirements. + Unfortunately, below, the stationary pattern calibrations fail. + I suspect there was not enough movement of the laparoscope. + + Needs more data. Not a complete unit test, as we aren't + testing anything other than whether the code runs without failing. + """ + dirs = get_fixed_pattern_dirs() + results_fixed = get_calibrations(dirs, None, True, False) + results_fixed_m = np.mean(results_fixed, axis=0) + results_fixed_s = np.std(results_fixed, axis=0) + dirs = get_dirs() + results_opencv = get_calibrations(dirs, None, True, False) + results_opencv_m = np.mean(results_opencv, axis=0) + results_opencv_s = np.std(results_opencv, axis=0) + + print("Fixed pattern mean=\n" + str(results_fixed_m)) + print("OpenCV's mean=\n" + str(results_opencv_m)) + + print("Fixed pattern stddev=\n" + str(results_fixed_s)) + print("OpenCV's stddev=\n" + str(results_opencv_s)) + + +def tests_for_smart_liver(): + """ + Additional tests for SmartLiver laparoscope system. + """ + output_dir = 'tests/output/SmartLiver/' + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + + p2m = get_pattern_to_marker() + np.savetxt(os.path.join(output_dir, 'p2m.txt'), p2m) + + # For documentation, this should re-run calibration, as SmartLiver has it, so should match the input folder. + results_sl = get_calibrations( + ['tests/data/2022_02_28_fixed_position_calibs/calibration/14_58_31'], + pattern2marker=None, + use_opencv=False, + use_bundle_adjust=False, + save_data=False) + + # This should save a new calibration in tests/output, + # as an example of using a fixed pattern2marker, and OpenCV methods. + results_2022_03_04 = get_calibrations( + ['tests/data/2022_02_28_fixed_position_calibs/calibration/14_58_31'], + pattern2marker=p2m, + use_opencv=True, + use_bundle_adjust=False, + save_data=True) + + print("SmartLiver, proj=" + str(results_sl[0][0]) + ", recon=" + str(results_sl[0][1])) + print("As of 2022-03-04, proj=" + str(results_2022_03_04[0][0]) + ", recon=" + str(results_2022_03_04[0][1])) diff --git a/tests/video/test_iterative.py b/tests/video/test_iterative.py deleted file mode 100644 index 5a33cc9..0000000 --- a/tests/video/test_iterative.py +++ /dev/null @@ -1,137 +0,0 @@ -# pylint: disable=line-too-long, missing-module-docstring, invalid-name, missing-function-docstring - -import os -from typing import Tuple -import numpy as np -import cv2 -import sksurgeryimage.calibration.dotty_grid_point_detector as dotty_pd -import sksurgerycalibration.video.video_calibration_driver_stereo as sc - - -def get_calib_data(directory: str, idx: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: - """ Generate filenames for calibration data in a given directory. """ - left_image = cv2.imread( - os.path.join(directory, f'calib.left.images.{idx}.png') - ) - - right_image = cv2.imread( - os.path.join(directory, f'calib.right.images.{idx}.png') - ) - - chessboard_tracking = np.loadtxt( - os.path.join(directory, f'calib.calib_obj_tracking.{idx}.txt') - ) - - scope_tracking = np.loadtxt( - os.path.join(directory, f'calib.device_tracking.{idx}.txt') - ) - - return left_image, right_image, chessboard_tracking, scope_tracking - - -def get_point_detector(intrinsic_matrix, distortion_matrix): - """ - Returns a point detector based on a set of - camera intrinsics and distortion coefficient parameters. - - :param intrinsic_matrix: [3x3] matrix - :param distortion_matrix: [1x5] matrix - :return: - """ - number_of_dots = [18, 25] - dot_separation = 3 - fiducial_indexes = [133, 141, 308, 316] - reference_image_size = [1900, 2600] - pixels_per_mm = 40 - - number_of_points = number_of_dots[0] * number_of_dots[1] - model_points = np.zeros((number_of_points, 6)) - counter = 0 - for y_index in range(number_of_dots[0]): - for x_index in range(number_of_dots[1]): - model_points[counter][0] = counter - model_points[counter][1] = (x_index + 1) * pixels_per_mm - model_points[counter][2] = (y_index + 1) * pixels_per_mm - model_points[counter][3] = x_index * dot_separation - model_points[counter][4] = y_index * dot_separation - model_points[counter][5] = 0 - counter = counter + 1 - - dot_detector_params = cv2.SimpleBlobDetector_Params() - dot_detector_params.filterByConvexity = False - dot_detector_params.filterByInertia = False - dot_detector_params.filterByCircularity = False - dot_detector_params.filterByArea = True - dot_detector_params.minArea = 50 - dot_detector_params.maxArea = 50000 - - point_detector = \ - dotty_pd.DottyGridPointDetector( - model_points, - fiducial_indexes, - intrinsic_matrix, - distortion_matrix, - reference_image_size=(reference_image_size[1], - reference_image_size[0]), - threshold_window_size=191, - dot_detector_params=dot_detector_params - ) - - return point_detector - - -def get_calib_driver(calib_dir: str): - - left_point_detector = get_point_detector( - np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.left.intrinsics.txt"), - np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.left.distortion.txt")) - - right_point_detector = get_point_detector( - np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.right.intrinsics.txt"), - np.loadtxt("tests/data/laparoscope_calibration/cbh-viking/calib.right.distortion.txt")) - - minimum_points = 50 - - calibration_driver = sc.StereoVideoCalibrationDriver(left_point_detector, - right_point_detector, - minimum_points) - - for i in range(3): - l_img, r_img, chessboard, scope = get_calib_data(calib_dir, i) - calibration_driver.grab_data(l_img, r_img, scope, chessboard) - - return calibration_driver - - -def get_ref_dot_detector(): - - camera_matrix = np.eye(3) - distortion_coefficients = np.zeros(5) - - ref_point_detector = get_point_detector(camera_matrix, - distortion_coefficients) - return ref_point_detector - - -def test_dotty_grid_iterative(): - - os.makedirs("tests/output/iterative", exist_ok=True) - - calib_dir = 'tests/data/dot_calib/11_19_11' - - calib_driver = get_calib_driver(calib_dir) - ref_detector = get_ref_dot_detector() - - ref_image = cv2.imread('tests/data/dot_calib/circles-25x18-r40-s3.png') - ref_ids, _, ref_image_points = \ - ref_detector.get_points(ref_image) - - reprojection_err, recon_err, _ = calib_driver.iterative_calibration( - 3, - ref_ids, - ref_image_points, - (ref_image.shape[1], - ref_image.shape[0])) - - assert reprojection_err < 2 - assert recon_err < 2 diff --git a/tests/video/test_precalib.py b/tests/video/test_precalib.py deleted file mode 100644 index 6873d39..0000000 --- a/tests/video/test_precalib.py +++ /dev/null @@ -1,127 +0,0 @@ -# pylint: disable=line-too-long, missing-module-docstring, invalid-name, missing-function-docstring - -import numpy as np -import cv2 -import sksurgeryimage.calibration.charuco_plus_chessboard_point_detector \ - as charuco_pd -import sksurgerycalibration.video.video_calibration_driver_stereo as sc -import tests.video.test_load_calib_utils as lcu - - -def get_calib_driver(calib_dir: str): - """ Create left/right charuco point detectors and load calibration images from directory. """ - reference_image = cv2.imread("tests/data/2020_01_20_storz/pattern_4x4_19x26_5_4_with_inset_9x14.png") - minimum_points = 50 - - number_of_squares = [19, 26] - square_tag_sizes = [5, 4] - filter_markers = True - number_of_chessboard_squares = [9, 14] - chessboard_square_size = 3 - chessboard_id_offset = 500 - - left_pd = \ - charuco_pd.CharucoPlusChessboardPointDetector( - reference_image, - minimum_number_of_points=minimum_points, - number_of_charuco_squares=number_of_squares, - size_of_charuco_squares=square_tag_sizes, - charuco_filtering=filter_markers, - number_of_chessboard_squares=number_of_chessboard_squares, - chessboard_square_size=chessboard_square_size, - chessboard_id_offset=chessboard_id_offset - ) - - right_pd = \ - charuco_pd.CharucoPlusChessboardPointDetector( - reference_image, - minimum_number_of_points=minimum_points, - number_of_charuco_squares=number_of_squares, - size_of_charuco_squares=square_tag_sizes, - charuco_filtering=filter_markers, - number_of_chessboard_squares=number_of_chessboard_squares, - chessboard_square_size=chessboard_square_size, - chessboard_id_offset=chessboard_id_offset - ) - - calibration_driver = sc.StereoVideoCalibrationDriver(left_pd, - right_pd, - minimum_points) - - for i in range(3): - l_img, r_img, chessboard, scope = lcu.get_calib_data(calib_dir, i) - calibration_driver.grab_data(l_img, r_img, scope, chessboard) - - return calibration_driver - - -# Two datasets, A and B. -# Independently calibrating them gives Stereo reprojection error < 1 -# But if we pass the intrinsics from A as precalibration for B, then -# error is ~4, so potentially something fishy going on. -def test_charuco_dataset_A(): - - calib_dir = 'tests/data/precalib/precalib_base_data' - calib_driver = get_calib_driver(calib_dir) - - stereo_reproj_err, stereo_recon_err, _ = \ - calib_driver.calibrate() - - tracked_reproj_err, tracked_recon_err, _ = \ - calib_driver.handeye_calibration() - - print(stereo_reproj_err, stereo_recon_err, tracked_reproj_err, tracked_recon_err) - assert stereo_reproj_err < 1 - assert stereo_recon_err < 4 - assert tracked_reproj_err < 3 - assert tracked_recon_err < 4 - - -def test_charuco_dataset_B(): - - calib_dir = 'tests/data/precalib/data_moved_scope' - calib_driver = get_calib_driver(calib_dir) - - stereo_reproj_err, stereo_recon_err, _ = \ - calib_driver.calibrate() - - tracked_reproj_err, tracked_recon_err, _ = \ - calib_driver.handeye_calibration() - - print(stereo_reproj_err, stereo_recon_err, tracked_reproj_err, tracked_recon_err) - assert stereo_reproj_err < 1 - assert stereo_recon_err < 3 - assert tracked_reproj_err < 4 - assert tracked_recon_err < 3 - - -def test_precalbration(): - """ Use intrinsics from A to calibration B, currently failing. """ - left_intrinsics = np.loadtxt('tests/data/precalib/precalib_base_data/calib.left.intrinsics.txt') - left_distortion = np.loadtxt('tests/data/precalib/precalib_base_data/calib.left.distortion.txt') - right_intrinsics = np.loadtxt('tests/data/precalib/precalib_base_data/calib.right.intrinsics.txt') - right_distortion = np.loadtxt('tests/data/precalib/precalib_base_data/calib.right.distortion.txt') - l2r = np.loadtxt('tests/data/precalib/precalib_base_data/calib.l2r.txt') - l2r_rmat = l2r[0:3, 0:3] - l2r_tvec = l2r[0:3, 3] - - calib_dir = 'tests/data/precalib/data_moved_scope' - calib_driver = get_calib_driver(calib_dir) - - stereo_reproj_err, stereo_recon_err, _ = \ - calib_driver.calibrate( - override_left_intrinsics=left_intrinsics, - override_left_distortion=left_distortion, - override_right_intrinsics=right_intrinsics, - override_right_distortion=right_distortion, - override_l2r_rmat=l2r_rmat, - override_l2r_tvec=l2r_tvec) - - tracked_reproj_err, tracked_recon_err, _ = \ - calib_driver.handeye_calibration() - - print(stereo_reproj_err, stereo_recon_err, tracked_reproj_err, tracked_recon_err) - assert stereo_reproj_err < 4.5 - assert stereo_recon_err < 4.5 - assert tracked_reproj_err < 5.4 - assert tracked_recon_err < 6.4 diff --git a/tests/video/test_video_calibration.py b/tests/video/test_video_calibration.py deleted file mode 100644 index b8d4805..0000000 --- a/tests/video/test_video_calibration.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: utf-8 -*- - -# pylint: disable=unused-import, superfluous-parens, line-too-long, missing-module-docstring, unused-variable, missing-function-docstring, invalid-name - -import glob -import pytest -import numpy as np -import sksurgerycalibration.video.video_calibration_utils as vu -import sksurgerycalibration.video.video_calibration_wrapper as vc - - -def test_mono_left_video_calibration(): - - image_points = [] - object_points = [] - - model = np.loadtxt('tests/data/laparoscope_calibration/chessboard_14_10_3.txt') - - files = glob.glob('tests/data/laparoscope_calibration/left/*.txt') - for file in files: - points = np.loadtxt(file) - image_points.append(vu.convert_numpy2d_to_opencv(points)) - object_points.append(vu.convert_numpy3d_to_opencv(model)) - - retval, camera_matrix, dist_coeffs, rvecs, tvecs = \ - vc.mono_video_calibration(object_points, image_points, (1920, 1080)) - - assert(np.abs(retval - 0.57759896) < 0.000001) - - -def load_first_stereo_data(): - - left_image_points = [] - right_image_points = [] - object_points = [] - - model = np.loadtxt('tests/data/laparoscope_calibration/chessboard_14_10_3.txt') - - files = glob.glob('tests/data/laparoscope_calibration/left/*.txt') - files.sort() - for file in files: - points = np.loadtxt(file) - left_image_points.append(vu.convert_numpy2d_to_opencv(points)) - object_points.append(vu.convert_numpy3d_to_opencv(model)) - - files = glob.glob('tests/data/laparoscope_calibration/right/*.txt') - files.sort() - for file in files: - points = np.loadtxt(file) - right_image_points.append(vu.convert_numpy2d_to_opencv(points)) - - # Generates, array of arrays - ids = [] - for i in range(9): - ids.append(np.asarray(range(140))) - - return object_points, \ - left_image_points, \ - right_image_points, \ - ids - - -def test_stereo_video_calibration(): - - object_points, left_image_points, right_image_points, ids = \ - load_first_stereo_data() - - s_reproj, s_recon, \ - l_c, l_d, left_rvecs, left_tvecs, \ - r_c, r_d, right_rvecs, right_tvecs, \ - l2r_r, l2r_t, \ - essential, fundamental = \ - vc.stereo_video_calibration(ids, - object_points, - left_image_points, - ids, - object_points, - right_image_points, - (1920, 1080)) - - assert (np.abs(s_reproj - 0.63022577) < 0.000001) - assert (np.abs(s_recon - 1.64274596) < 0.000001) - -# def test_experimental_mono_stereo_calib(): -# -# This isn't worth the bother. Takes 5 minutes to run. -# -# image_points = [] -# object_points = [] -# -# model = np.loadtxt('tests/data/laparoscope_calibration/chessboard_14_10_3.txt') -# -# files = glob.glob('tests/data/laparoscope_calibration/left/*.txt') -# for file in files: -# points = np.loadtxt(file) -# image_points.append(vu.convert_numpy2d_to_opencv(points)) -# object_points.append(vu.convert_numpy3d_to_opencv(model)) -# -# # Generates, array of arrays -# ids = [] -# for i in range(9): -# ids.append(np.asarray(range(140))) -# -# retval, camera_matrix, dist_coeffs, rvecs, tvecs = \ -# ve.mono_video_calibration_expt(ids, object_points, image_points, (1920, 1080)) -# -# print(camera_matrix) -# print(dist_coeffs) -# print(rvecs) -# print(tvecs) - -#def test_experimental_video_stereo_calib(): -# -# This isn't worth the bother. Takes 1.5 hours to run. -# -# object_points, left_image_points, right_image_points, ids = \ -# load_first_stereo_data() -# -# s_reproj, s_recon, \ -# l_c, l_d, left_rvecs, left_tvecs, \ -# r_c, r_d, right_rvecs, right_tvecs, \ -# l2r_r, l2r_t, \ -# essential, fundamental = \ -# ve.stereo_video_calibration_expt(ids, -# object_points, -# left_image_points, -# ids, -# object_points, -# right_image_points, -# (1920, 1080)) -# print(s_reproj) -# print(s_recon) -# print(l2r_r) -# print(l2r_t)