• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SciKit-Surgery / scikit-surgeryvtk / 4323596638

pending completion
4323596638

push

github-actions

Miguel Xochicale
refactors unit tests skipif messages (#187)

332 of 1997 relevant lines covered (16.62%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

13.64
/sksurgeryvtk/utils/projection_utils.py
1
# -*- coding: utf-8 -*-
2

3
"""
4✔
4
Any useful little utilities to do with projecting 3D to 2D.
5
"""
6

7
import cv2
4✔
8
import vtk
4✔
9
import numpy as np
4✔
10
import sksurgerycore.utilities.validate_matrix as vm
4✔
11

12

13
def _validate_input_for_projection(points,
4✔
14
                                   camera_to_world,
15
                                   camera_matrix,
16
                                   distortion=None):
17
    """
18
    Validation of input, for both project_points and
19
    project_facing_points.
20

21
    :param points: nx3 ndarray representing 3D points, typically in millimetres
22
    :param camera_to_world: 4x4 ndarray representing camera_to_world transform
23
    :param camera_matrix: 3x3 ndarray representing OpenCV camera intrinsics
24
    :param distortion: 1x4,5 etc. OpenCV distortion parameters
25
    :raises ValueError, TypeError:
26
    :return: nx2 ndarray representing 2D points, typically in pixels
27
    """
28
    if points is None:
×
29
        raise ValueError('points is NULL')
×
30
    if not isinstance(points, np.ndarray):
×
31
        raise TypeError('points is not an np.ndarray')
×
32
    if len(points.shape) != 2:
×
33
        raise ValueError("points should have 2 dimensions.")
×
34
    if points.shape[1] != 3:
×
35
        raise ValueError("points should have 3 columns.")
×
36

37
    if camera_to_world is None:
×
38
        raise ValueError('camera_to_world is NULL')
×
39

40
    vm.validate_rigid_matrix(camera_to_world)
×
41

42
    if camera_matrix is None:
×
43
        raise ValueError('camera_matrix is NULL')
×
44

45
    vm.validate_camera_matrix(camera_matrix)
×
46

47
    if distortion is not None:
×
48
        vm.validate_distortion_coefficients(distortion)
×
49

50

51
def project_points(points,
4✔
52
                   camera_to_world,
53
                   camera_matrix,
54
                   distortion=None
55
                  ):
56
    """
57
    Projects all 3D points to 2D, using OpenCV cv2.projectPoints().
58

59
    :param points: nx3 ndarray representing 3D points, typically in millimetres
60
    :param camera_to_world: 4x4 ndarray representing camera to world transform
61
    :param camera_matrix: 3x3 ndarray representing OpenCV camera intrinsics
62
    :param distortion: 1x4,5 etc. OpenCV distortion parameters
63
    :raises ValueError, TypeError:
64
    :return: nx2 ndarray representing 2D points, typically in pixels
65
    """
66

67
    _validate_input_for_projection(points,
×
68
                                   camera_to_world,
69
                                   camera_matrix,
70
                                   distortion)
71

72
    world_to_camera = np.linalg.inv(camera_to_world)
×
73

74
    t_vec = np.zeros((3, 1))
×
75
    t_vec[0:3, :] = world_to_camera[0:3, 3:4]
×
76
    r_vec, _ = cv2.Rodrigues(world_to_camera[0:3, 0:3])
×
77

78
    projected, _ = cv2.projectPoints(points,
×
79
                                     r_vec,
80
                                     t_vec,
81
                                     camera_matrix,
82
                                     distortion
83
                                    )
84

85
    return projected
×
86

87

88
def project_facing_points(points,
4✔
89
                          normals,
90
                          camera_to_world,
91
                          camera_matrix,
92
                          distortion=None,
93
                          upper_cos_theta=0
94
                         ):
95
    """
96
    Projects 3D points that face the camera to 2D pixels.
97

98
    This assumes:
99

100
      Camera direction is a unit vector from the camera, towards focal point.
101
      Surface Normal is a unit vector pointing out from the surface.
102

103
    Vectors are not checked for unit length.
104

105
    :param points: nx3 ndarray representing 3D points, typically in millimetres
106
    :param normals: nx3 ndarray representing unit normals for the same points
107
    :param camera_to_world: 4x4 ndarray representing camera to world transform
108
    :param camera_matrix: 3x3 ndarray representing OpenCV camera intrinsics
109
    :param distortion: 1x4,5 etc. OpenCV distortion parameters
110
    :param upper_cos_theta: upper limit for cos theta, angle between normal
111
    and viewing direction, where cos theta is normally -1 to 0.
112
    :raises ValueError, TypeError:
113
    :return: projected_facing_points_2d
114
    """
115
    _validate_input_for_projection(points,
×
116
                                   camera_to_world,
117
                                   camera_matrix,
118
                                   distortion)
119

120
    if normals is None:
×
121
        raise ValueError("normals is NULL")
×
122
    if not isinstance(normals, np.ndarray):
×
123
        raise TypeError('normals is not an np.ndarray')
×
124
    if normals.shape != points.shape:
×
125
        raise ValueError("normals and points should have the same shape")
×
126

127
    camera_pose = np.array([[0, 0], [0, 0], [0, 1]])  # Origin and focal point
×
128
    transformed = np.matmul(camera_to_world[0:3, 0:3], camera_pose)
×
129
    camera_direction = np.array([[transformed[0][1] - transformed[0][0]],
×
130
                                 [transformed[1][1] - transformed[1][0]],
131
                                 [transformed[2][1] - transformed[2][0]]
132
                                ]
133
                               )
134
    camera_direction_t = camera_direction.transpose()
×
135

136
    facing_points = points[np.einsum('ij,ij->i', normals, camera_direction_t)
×
137
                           < upper_cos_theta]
138

139
    projected_points = np.zeros((0, 1, 2))
×
140

141
    if facing_points.shape[0] > 0:
×
142
        projected_points = project_points(facing_points,
×
143
                                          camera_to_world,
144
                                          camera_matrix,
145
                                          distortion=distortion
146
                                         )
147
    return projected_points
×
148

149

150
def compute_rms_error(model_points,
4✔
151
                      image_points,
152
                      renderer,
153
                      scale_x,
154
                      scale_y,
155
                      image_height
156
                     ):
157
    """
158
    Mainly for unit testing. Computes rms error between projected
159
    model points, and image points.
160

161
    :param model_points: nx3 numpy array of 3D points
162
    :param image_points: nx2 numpy array of 2D expected points
163
    :param renderer: vtkRenderer
164
    :param scale_x: scale factor for x
165
    :param scale_y: scale factor for y
166
    :param image_height: image height
167
    """
168
    coord_3d = vtk.vtkCoordinate()
×
169
    coord_3d.SetCoordinateSystemToWorld()
×
170
    counter = 0
×
171
    rms = 0
×
172

173
    for m_c in model_points:
×
174

175
        coord_3d.SetValue(float(m_c[0]), float(m_c[1]), float(m_c[2]))
×
176
        i_c = image_points[counter]
×
177

178
        # This will scale to the vtkRenderWindow, which may
179
        # well be a different size to the original image.
180
        p_x, p_y = coord_3d.GetComputedDoubleDisplayValue(renderer)
×
181

182
        # Scale them up to the right image size.
183
        p_x *= scale_x
×
184
        p_y *= scale_y
×
185

186
        # And flip the y-coordinate, as OpenGL numbers Y from bottom up,
187
        # OpenCV numbers top-down.
188
        p_y = image_height - 1 - p_y  #
×
189

190
        # Difference between VTK points and reference points.
191
        d_x = p_x - float(i_c[0])
×
192
        d_y = p_y - float(i_c[1])
×
193
        rms += (d_x * d_x + d_y * d_y)
×
194

195
        counter += 1
×
196

197
    rms /= float(counter)
×
198
    rms = np.sqrt(rms)
×
199

200
    return rms
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc