camera-client 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camera-client
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions.
5
5
  Author-email: Alexander Abramov <extremal.ru@gmail.com>
6
6
  License: MIT
@@ -178,7 +178,7 @@ rays = camera.ctd_to_ray(ctd_points)
178
178
 
179
179
  # Get camera position in world space (ray origin)
180
180
  key_point = camera.get_key_point()
181
- print(key_point.shape) # (3, 1) - [x, y, z] camera position
181
+ print(key_point.shape) # (3,) - [x, y, z] camera position
182
182
 
183
183
  # Ray equation: point_on_ray = key_point + t * ray_direction
184
184
  # All rays are normalized to unit length
@@ -314,7 +314,7 @@ Generate 3D ray directions from corrected (undistorted) image coordinates.
314
314
  Get the camera position (key-point) in world space.
315
315
 
316
316
  **Returns:**
317
- - `np.ndarray`: Shape (3, 1) array with [x, y, z] camera position
317
+ - `np.ndarray`: Shape (3,) array with [x, y, z] camera position
318
318
 
319
319
  ## Calibration File Format
320
320
 
@@ -334,8 +334,15 @@ The calibration file is a NumPy `.npz` archive containing:
334
334
  - `exp_im2ray`: Image to ray direction transformation
335
335
 
336
336
  ### Metadata
337
- - `im_width`, `im_height`: Image dimensions in pixels
337
+ - `format_version`: Version string of the data format
338
+ - `plan_url`: URL or path to the ground plan image
338
339
  - `plan_scale`: Scale factor for ground plane coordinates (pixels per meter)
340
+ - `plan_width`: Width of the ground plan in pixels
341
+ - `plan_height`: Height of the ground plan in pixels
342
+ - `im_src_url`: URL or path to the source (distorted) camera image
343
+ - `im_ctd_url`: URL or path to the corrected (undistorted) camera image
344
+ - `im_width`: Width of the camera image in pixels
345
+ - `im_height`: Height of the camera image in pixels
339
346
 
340
347
  ## Requirements
341
348
 
@@ -148,7 +148,7 @@ rays = camera.ctd_to_ray(ctd_points)
148
148
 
149
149
  # Get camera position in world space (ray origin)
150
150
  key_point = camera.get_key_point()
151
- print(key_point.shape) # (3, 1) - [x, y, z] camera position
151
+ print(key_point.shape) # (3,) - [x, y, z] camera position
152
152
 
153
153
  # Ray equation: point_on_ray = key_point + t * ray_direction
154
154
  # All rays are normalized to unit length
@@ -284,7 +284,7 @@ Generate 3D ray directions from corrected (undistorted) image coordinates.
284
284
  Get the camera position (key-point) in world space.
285
285
 
286
286
  **Returns:**
287
- - `np.ndarray`: Shape (3, 1) array with [x, y, z] camera position
287
+ - `np.ndarray`: Shape (3,) array with [x, y, z] camera position
288
288
 
289
289
  ## Calibration File Format
290
290
 
@@ -304,8 +304,15 @@ The calibration file is a NumPy `.npz` archive containing:
304
304
  - `exp_im2ray`: Image to ray direction transformation
305
305
 
306
306
  ### Metadata
307
- - `im_width`, `im_height`: Image dimensions in pixels
307
+ - `format_version`: Version string of the data format
308
+ - `plan_url`: URL or path to the ground plan image
308
309
  - `plan_scale`: Scale factor for ground plane coordinates (pixels per meter)
310
+ - `plan_width`: Width of the ground plan in pixels
311
+ - `plan_height`: Height of the ground plan in pixels
312
+ - `im_src_url`: URL or path to the source (distorted) camera image
313
+ - `im_ctd_url`: URL or path to the corrected (undistorted) camera image
314
+ - `im_width`: Width of the camera image in pixels
315
+ - `im_height`: Height of the camera image in pixels
309
316
 
310
317
  ## Requirements
311
318
 
@@ -0,0 +1,309 @@
1
+ import numpy as np
2
+ import sympy as sp
3
+ from camera_client.loading import read_npz_file
4
+
5
+
6
+ class CameraProjection:
7
+ """
8
+ Vectorized camera coordinate transformer for batch processing.
9
+
10
+ This class is optimized for processing multiple points at once.
11
+ All methods expect array inputs with shape (N, 2) or (N, 3).
12
+
13
+ Handles transformations between:
14
+ - src: Source (distorted) image coordinates
15
+ - ctd: Corrected (undistorted) image coordinates
16
+ - gnd: Ground (3D world) coordinates
17
+ """
18
+
19
+ def __init__(self, cam_archive_data):
20
+ """
21
+ Initialize the camera transformer with calibration data.
22
+
23
+ Args:
24
+ cam_archive_data: Dictionary containing:
25
+ - src2ctd: Source to corrected distortion map (H x W x 2)
26
+ - ctd2src: Corrected to source distortion map (H x W x 2)
27
+ - x_gnd, y_gnd, z_gnd: String expressions for ctd -> ground
28
+ - x_im, y_im: String expressions for ground -> ctd
29
+ """
30
+ data = cam_archive_data
31
+
32
+ self.plan_scale = float(data["plan_scale"])
33
+ self.plan_url = str(data["plan_url"])
34
+ self.plan_width = int(data["plan_width"])
35
+ self.plan_height = int(data["plan_height"])
36
+
37
+ self.im_src_url = str(data["im_src_url"])
38
+ self.im_ctd_url = str(data["im_ctd_url"])
39
+ self.im_width = int(data["im_width"])
40
+ self.im_height = int(data["im_height"])
41
+ self.im_wh_size = (self.im_width, self.im_height)
42
+
43
+ # Store lookup tables
44
+ self.src2ctd_points_map = data["src2ctd"]
45
+ self.ctd2src_points_map = data["ctd2src"]
46
+
47
+ self.im_size = self.src2ctd_points_map.shape[:2]
48
+
49
+ # Compile transformation expressions for ctd -> gnd
50
+ x_im, y_im, proj_height = sp.symbols("x_im y_im proj_height")
51
+ self._lambda_im2gnd = sp.lambdify(
52
+ (x_im, y_im, proj_height), sp.sympify(data["exp_im2gnd"]), "numpy"
53
+ )
54
+
55
+ # Compile transformation expressions for gnd -> ctd
56
+ x_gnd, y_gnd, z_gnd = sp.symbols("x_gnd y_gnd z_gnd")
57
+ self._lambda_gnd2im = sp.lambdify(
58
+ (x_gnd, y_gnd, z_gnd), sp.sympify(data["exp_gnd2im"]), "numpy"
59
+ )
60
+
61
+ # Compile keypoint expressions (camera position in world space)
62
+ self._lambda_key_point = sp.lambdify(
63
+ (), sp.sympify(data["exp_key_point"]), "numpy"
64
+ )
65
+
66
+ # Compile ray direction expressions for ctd -> ray
67
+ self._lambda_im2ray = sp.lambdify(
68
+ (x_im, y_im), sp.sympify(data["exp_im2ray"]), "numpy"
69
+ )
70
+
71
+ def src_to_ctd(self, points):
72
+ """
73
+ Transform from source (distorted) to corrected coordinates.
74
+
75
+ Args:
76
+ points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
77
+
78
+ Returns:
79
+ (N, 2) array of corrected points, with [nan, nan] for out-of-bounds
80
+ """
81
+ points = np.asarray(points, dtype=float)
82
+ if points.ndim != 2 or points.shape[1] != 2:
83
+ raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
84
+
85
+ N = len(points)
86
+ result = np.full((N, 2), np.nan, dtype=float)
87
+
88
+ # Check for NaN input points
89
+ valid_input = ~np.isnan(points).any(axis=1)
90
+
91
+ if not valid_input.any():
92
+ return result
93
+
94
+ # Round to integer coordinates
95
+ p_int = np.round(points[valid_input]).astype(int)
96
+
97
+ # Vectorized bounds checking
98
+ in_bounds = (
99
+ (p_int[:, 0] >= 0)
100
+ & (p_int[:, 0] < self.src2ctd_points_map.shape[1])
101
+ & (p_int[:, 1] >= 0)
102
+ & (p_int[:, 1] < self.src2ctd_points_map.shape[0])
103
+ )
104
+
105
+ # Create mask for points that are both valid input and in bounds
106
+ valid_indices = np.where(valid_input)[0]
107
+ final_valid_indices = valid_indices[in_bounds]
108
+ valid_p_int = p_int[in_bounds]
109
+
110
+ # Vectorized lookup using advanced indexing
111
+ # Note: lookup map is [y, x] indexed
112
+ result[final_valid_indices] = self.src2ctd_points_map[
113
+ valid_p_int[:, 1], valid_p_int[:, 0]
114
+ ]
115
+
116
+ return result
117
+
118
+ def ctd_to_src(self, points):
119
+ """
120
+ Transform from corrected to source (distorted) coordinates.
121
+
122
+ Args:
123
+ points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
124
+
125
+ Returns:
126
+ (N, 2) array of source points, with [nan, nan] for out-of-bounds
127
+ """
128
+ points = np.asarray(points, dtype=float)
129
+ if points.ndim != 2 or points.shape[1] != 2:
130
+ raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
131
+
132
+ N = len(points)
133
+ result = np.full((N, 2), np.nan, dtype=float)
134
+
135
+ # Check for NaN input points
136
+ valid_input = ~np.isnan(points).any(axis=1)
137
+
138
+ if not valid_input.any():
139
+ return result
140
+
141
+ # Round to integer coordinates
142
+ p_int = np.round(points[valid_input]).astype(int)
143
+
144
+ # Vectorized bounds checking
145
+ in_bounds = (
146
+ (p_int[:, 0] >= 0)
147
+ & (p_int[:, 0] < self.ctd2src_points_map.shape[1])
148
+ & (p_int[:, 1] >= 0)
149
+ & (p_int[:, 1] < self.ctd2src_points_map.shape[0])
150
+ )
151
+
152
+ # Create mask for points that are both valid input and in bounds
153
+ valid_indices = np.where(valid_input)[0]
154
+ final_valid_indices = valid_indices[in_bounds]
155
+ valid_p_int = p_int[in_bounds]
156
+
157
+ # Vectorized lookup using advanced indexing
158
+ result[final_valid_indices] = self.ctd2src_points_map[
159
+ valid_p_int[:, 1], valid_p_int[:, 0]
160
+ ]
161
+
162
+ return result
163
+
164
+ def ctd_to_gnd(self, points, h):
165
+ """
166
+ Transform from corrected image coordinates to ground (3D world) coordinates.
167
+
168
+ Args:
169
+ points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
170
+ h: Scalar height or (N,) array of heights for each point
171
+
172
+ Returns:
173
+ (N, 3) array of ground points [[x1, y1, z1], ...]
174
+ """
175
+ points = np.asarray(points, dtype=float)
176
+ if points.ndim != 2 or points.shape[1] != 2:
177
+ raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
178
+
179
+ N = points.shape[0]
180
+ x_im, y_im = points.T
181
+ hs = np.asarray(h, dtype=float)
182
+
183
+ # Call lambdified function: returns shape (3, 1, N)
184
+ # Reshape directly to (N, 3)
185
+ ps_gnd = np.asarray(self._lambda_im2gnd(x_im, y_im, hs)).reshape(3, N).T
186
+
187
+ return ps_gnd
188
+
189
+ def gnd_to_ctd(self, points):
190
+ """
191
+ Transform from ground (3D world) to corrected image coordinates.
192
+
193
+ Args:
194
+ points: (N, 3) array of ground points [[x1, y1, z1], [x2, y2, z2], ...]
195
+
196
+ Returns:
197
+ (N, 2) array of image points [[x1, y1], ...]
198
+ """
199
+ points = np.asarray(points, dtype=float)
200
+ if points.ndim != 2 or points.shape[1] != 3:
201
+ raise ValueError(f"Expected (N, 3) array, got shape {points.shape}")
202
+
203
+ N = points.shape[0]
204
+ x_gnd, y_gnd, z_gnd = points.T
205
+
206
+ # Call lambdified function: returns shape (2, 1, N)
207
+ # Reshape directly to (N, 2)
208
+ ps_im = np.asarray(self._lambda_gnd2im(x_gnd, y_gnd, z_gnd)).reshape(2, N).T
209
+
210
+ return ps_im
211
+
212
+ def src_to_gnd(self, points, h):
213
+ """
214
+ Transform from source to ground coordinates.
215
+
216
+ Args:
217
+ points: (N, 2) array of source points
218
+ h: Scalar height or (N,) array of heights
219
+
220
+ Returns:
221
+ (N, 3) array of ground points
222
+ """
223
+ ctd_points = self.src_to_ctd(points)
224
+ return self.ctd_to_gnd(ctd_points, h)
225
+
226
+ def gnd_to_src(self, points):
227
+ """
228
+ Transform from ground to source coordinates.
229
+
230
+ Args:
231
+ points: (N, 3) array of ground points
232
+
233
+ Returns:
234
+ (N, 2) array of source points
235
+ """
236
+ ctd_points = self.gnd_to_ctd(points)
237
+ return self.ctd_to_src(ctd_points)
238
+
239
+ def get_key_point(self):
240
+ """
241
+ Get the key-point of the camera in world space.
242
+
243
+ Returns:
244
+ (3,) 3D coordinates of the key-point of the camera in world space
245
+ """
246
+ key_point = self._lambda_key_point()
247
+ return np.array(key_point, dtype=np.float64).flatten()
248
+
249
+ def ctd_to_ray(self, points):
250
+ """
251
+ Transform from corrected image coordinates to 3D rays in camera space.
252
+
253
+ Args:
254
+ points: (N, 2) array of corrected image points [[x1, y1], [x2, y2], ...]
255
+
256
+ Returns:
257
+ (N, 3) array of ray directions in camera space, with [nan, nan, nan] for invalid
258
+ Ray directions are normalized to unit length.
259
+ The ray for a point is defined as the vector from key-point of the camera.
260
+ """
261
+ points = np.asarray(points, dtype=float)
262
+ if points.ndim != 2 or points.shape[1] != 2:
263
+ raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
264
+
265
+ N = points.shape[0]
266
+ x_im, y_im = points.T
267
+
268
+ # Call lambdified function: returns shape (3, 1, N)
269
+ # Reshape directly to (N, 3)
270
+ rays = np.asarray(self._lambda_im2ray(x_im, y_im)).reshape(3, N).T
271
+
272
+ # Normalize ray directions to unit length
273
+ ray_lengths = np.linalg.norm(rays, axis=1, keepdims=True)
274
+ ray_lengths = np.where(ray_lengths > 0, ray_lengths, 1.0)
275
+ rays_normalized = rays / ray_lengths
276
+
277
+ return rays_normalized
278
+
279
+ def src_to_ray(self, points):
280
+ """
281
+ Transform from source image coordinates to 3D rays in camera space.
282
+
283
+ Args:
284
+ points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
285
+
286
+ Returns:
287
+ (N, 3) array of ray directions in camera space, with [nan, nan, nan] for invalid
288
+ Ray directions are normalized to unit length.
289
+ The ray for a point is defined as the vector from key-point of the camera.
290
+ """
291
+ ctd_points = self.src_to_ctd(points)
292
+ return self.ctd_to_ray(ctd_points)
293
+
294
+ @classmethod
295
+ def load(cls, archive_path):
296
+ """
297
+ Load camera transformer from NPZ archive file.
298
+
299
+ Args:
300
+ archive_path: Path to .npz calibration file
301
+
302
+ Returns:
303
+ CameraProjectionVectorized instance
304
+ """
305
+
306
+ # Convert to regular dict for easier access
307
+ cam_data = read_npz_file(archive_path)
308
+
309
+ return cls(cam_data)
@@ -11,9 +11,15 @@ def read_npz_file(filename):
11
11
 
12
12
  Returns:
13
13
  dict: Dictionary containing the following keys:
14
+ - format_version (str): Version string of the data format
15
+ - plan_url (str): URL or path to the ground plan image
14
16
  - plan_scale (float): Scale factor for ground plane coordinates (pixels per meter)
15
- - im_width (int): Width of the image in pixels
16
- - im_height (int): Height of the image in pixels
17
+ - plan_width (int): Width of the ground plan in pixels
18
+ - plan_height (int): Height of the ground plan in pixels
19
+ - im_src_url (str): URL or path to the source (distorted) camera image
20
+ - im_ctd_url (str): URL or path to the corrected (undistorted) camera image
21
+ - im_width (int): Width of the camera image in pixels
22
+ - im_height (int): Height of the camera image in pixels
17
23
  - src2ctd (np.ndarray): Map of coordinates for undistorted (corrected) image.
18
24
  Shape: (height, width, 2) where channel 0 is X, channel 1 is Y
19
25
  - ctd2src (np.ndarray): Map of coordinates for distorted (raw) image based on undistorted.
@@ -28,7 +34,17 @@ def read_npz_file(filename):
28
34
  """
29
35
  data = np.load(filename)
30
36
 
37
+ format_version = str(data["format_version"])
38
+
39
+ # Plan options
40
+ plan_url = str(data["plan_url"])
31
41
  plan_scale = data["plan_scale"]
42
+ plan_width = data["plan_width"]
43
+ plan_height = data["plan_height"]
44
+
45
+ # Camera image options
46
+ im_src_url = str(data["im_src_url"])
47
+ im_ctd_url = str(data["im_ctd_url"])
32
48
  im_width = data["im_width"]
33
49
  im_height = data["im_height"]
34
50
 
@@ -50,16 +66,26 @@ def read_npz_file(filename):
50
66
  data.close()
51
67
 
52
68
  return {
69
+ # Plan options
70
+ "plan_url": plan_url,
53
71
  "plan_scale": plan_scale,
72
+ "plan_width": plan_width,
73
+ "plan_height": plan_height,
74
+ # Camera image option
75
+ "im_src_url": im_src_url,
76
+ "im_ctd_url": im_ctd_url,
54
77
  "im_width": im_width,
55
78
  "im_height": im_height,
79
+ # Distortion coords maps
56
80
  "src2ctd": src2ctd,
57
81
  "ctd2src": ctd2src,
58
- "map_scale_h": map_scale_h,
59
- "map_scale_w": map_scale_w,
60
- "map_scale_vang": map_scale_vang,
82
+ # Perspective projection expressions
61
83
  "exp_im2gnd": exp_im2gnd,
62
84
  "exp_gnd2im": exp_gnd2im,
63
85
  "exp_key_point": exp_key_point,
64
86
  "exp_im2ray": exp_im2ray,
87
+ # Scale maps
88
+ "map_scale_h": map_scale_h,
89
+ "map_scale_w": map_scale_w,
90
+ "map_scale_vang": map_scale_vang,
65
91
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camera-client
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions.
5
5
  Author-email: Alexander Abramov <extremal.ru@gmail.com>
6
6
  License: MIT
@@ -178,7 +178,7 @@ rays = camera.ctd_to_ray(ctd_points)
178
178
 
179
179
  # Get camera position in world space (ray origin)
180
180
  key_point = camera.get_key_point()
181
- print(key_point.shape) # (3, 1) - [x, y, z] camera position
181
+ print(key_point.shape) # (3,) - [x, y, z] camera position
182
182
 
183
183
  # Ray equation: point_on_ray = key_point + t * ray_direction
184
184
  # All rays are normalized to unit length
@@ -314,7 +314,7 @@ Generate 3D ray directions from corrected (undistorted) image coordinates.
314
314
  Get the camera position (key-point) in world space.
315
315
 
316
316
  **Returns:**
317
- - `np.ndarray`: Shape (3, 1) array with [x, y, z] camera position
317
+ - `np.ndarray`: Shape (3,) array with [x, y, z] camera position
318
318
 
319
319
  ## Calibration File Format
320
320
 
@@ -334,8 +334,15 @@ The calibration file is a NumPy `.npz` archive containing:
334
334
  - `exp_im2ray`: Image to ray direction transformation
335
335
 
336
336
  ### Metadata
337
- - `im_width`, `im_height`: Image dimensions in pixels
337
+ - `format_version`: Version string of the data format
338
+ - `plan_url`: URL or path to the ground plan image
338
339
  - `plan_scale`: Scale factor for ground plane coordinates (pixels per meter)
340
+ - `plan_width`: Width of the ground plan in pixels
341
+ - `plan_height`: Height of the ground plan in pixels
342
+ - `im_src_url`: URL or path to the source (distorted) camera image
343
+ - `im_ctd_url`: URL or path to the corrected (undistorted) camera image
344
+ - `im_width`: Width of the camera image in pixels
345
+ - `im_height`: Height of the camera image in pixels
339
346
 
340
347
  ## Requirements
341
348
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "camera-client"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Python SDK for camera calibration and projection transformations - handle lens distortion, coordinate transformations, and 3D ray casting with symbolic expressions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
@@ -1,724 +0,0 @@
1
- import numpy as np
2
- import sympy as sp
3
- from camera_client.loading import read_npz_file
4
-
5
-
6
- class CameraProjection:
7
- """
8
- Vectorized camera coordinate transformer for batch processing.
9
-
10
- This class is optimized for processing multiple points at once.
11
- All methods expect array inputs with shape (N, 2) or (N, 3).
12
-
13
- Handles transformations between:
14
- - src: Source (distorted) image coordinates
15
- - ctd: Corrected (undistorted) image coordinates
16
- - gnd: Ground (3D world) coordinates
17
- """
18
-
19
- def __init__(self, cam_archive_data):
20
- """
21
- Initialize the camera transformer with calibration data.
22
-
23
- Args:
24
- cam_archive_data: Dictionary containing:
25
- - src2ctd: Source to corrected distortion map (H x W x 2)
26
- - ctd2src: Corrected to source distortion map (H x W x 2)
27
- - x_gnd, y_gnd, z_gnd: String expressions for ctd -> ground
28
- - x_im, y_im: String expressions for ground -> ctd
29
- """
30
- data = cam_archive_data
31
-
32
- self.plan_scale = float(data["plan_scale"])
33
- self.im_width = int(data["im_width"])
34
- self.im_height = int(data["im_height"])
35
- self.im_wh_size = (self.im_width, self.im_height)
36
-
37
- # Store lookup tables
38
- self.src2ctd_points_map = data["src2ctd"]
39
- self.ctd2src_points_map = data["ctd2src"]
40
-
41
- self.im_size = self.src2ctd_points_map.shape[:2]
42
-
43
- # Compile transformation expressions for ctd -> gnd
44
- x_im, y_im, proj_height = sp.symbols("x_im y_im proj_height")
45
- self._lambda_im2gnd = sp.lambdify(
46
- (x_im, y_im, proj_height), sp.sympify(data["exp_im2gnd"]), "numpy"
47
- )
48
-
49
- # Compile transformation expressions for gnd -> ctd
50
- x_gnd, y_gnd, z_gnd = sp.symbols("x_gnd y_gnd z_gnd")
51
- self._lambda_gnd2im = sp.lambdify(
52
- (x_gnd, y_gnd, z_gnd), sp.sympify(data["exp_gnd2im"]), "numpy"
53
- )
54
-
55
- # Compile keypoint expressions (camera position in world space)
56
- self._lambda_key_point = sp.lambdify(
57
- (), sp.sympify(data["exp_key_point"]), "numpy"
58
- )
59
-
60
- # Compile ray direction expressions for ctd -> ray
61
- self._lambda_im2ray = sp.lambdify(
62
- (x_im, y_im), sp.sympify(data["exp_im2ray"]), "numpy"
63
- )
64
-
65
- def src_to_ctd(self, points):
66
- """
67
- Transform from source (distorted) to corrected coordinates.
68
-
69
- Args:
70
- points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
71
-
72
- Returns:
73
- (N, 2) array of corrected points, with [nan, nan] for out-of-bounds
74
- """
75
- points = np.asarray(points, dtype=float)
76
- if points.ndim != 2 or points.shape[1] != 2:
77
- raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
78
-
79
- N = len(points)
80
- result = np.full((N, 2), np.nan, dtype=float)
81
-
82
- # Check for NaN input points
83
- valid_input = ~np.isnan(points).any(axis=1)
84
-
85
- if not valid_input.any():
86
- return result
87
-
88
- # Round to integer coordinates
89
- p_int = np.round(points[valid_input]).astype(int)
90
-
91
- # Vectorized bounds checking
92
- in_bounds = (
93
- (p_int[:, 0] >= 0)
94
- & (p_int[:, 0] < self.src2ctd_points_map.shape[1])
95
- & (p_int[:, 1] >= 0)
96
- & (p_int[:, 1] < self.src2ctd_points_map.shape[0])
97
- )
98
-
99
- # Create mask for points that are both valid input and in bounds
100
- valid_indices = np.where(valid_input)[0]
101
- final_valid_indices = valid_indices[in_bounds]
102
- valid_p_int = p_int[in_bounds]
103
-
104
- # Vectorized lookup using advanced indexing
105
- # Note: lookup map is [y, x] indexed
106
- result[final_valid_indices] = self.src2ctd_points_map[
107
- valid_p_int[:, 1], valid_p_int[:, 0]
108
- ]
109
-
110
- return result
111
-
112
- def ctd_to_src(self, points):
113
- """
114
- Transform from corrected to source (distorted) coordinates.
115
-
116
- Args:
117
- points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
118
-
119
- Returns:
120
- (N, 2) array of source points, with [nan, nan] for out-of-bounds
121
- """
122
- points = np.asarray(points, dtype=float)
123
- if points.ndim != 2 or points.shape[1] != 2:
124
- raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
125
-
126
- N = len(points)
127
- result = np.full((N, 2), np.nan, dtype=float)
128
-
129
- # Check for NaN input points
130
- valid_input = ~np.isnan(points).any(axis=1)
131
-
132
- if not valid_input.any():
133
- return result
134
-
135
- # Round to integer coordinates
136
- p_int = np.round(points[valid_input]).astype(int)
137
-
138
- # Vectorized bounds checking
139
- in_bounds = (
140
- (p_int[:, 0] >= 0)
141
- & (p_int[:, 0] < self.ctd2src_points_map.shape[1])
142
- & (p_int[:, 1] >= 0)
143
- & (p_int[:, 1] < self.ctd2src_points_map.shape[0])
144
- )
145
-
146
- # Create mask for points that are both valid input and in bounds
147
- valid_indices = np.where(valid_input)[0]
148
- final_valid_indices = valid_indices[in_bounds]
149
- valid_p_int = p_int[in_bounds]
150
-
151
- # Vectorized lookup using advanced indexing
152
- result[final_valid_indices] = self.ctd2src_points_map[
153
- valid_p_int[:, 1], valid_p_int[:, 0]
154
- ]
155
-
156
- return result
157
-
158
- def ctd_to_gnd(self, points, h):
159
- """
160
- Transform from corrected image coordinates to ground (3D world) coordinates.
161
-
162
- Args:
163
- points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
164
- h: Scalar height or (N,) array of heights for each point
165
-
166
- Returns:
167
- (N, 3) array of ground points [[x1, y1, z1], ...]
168
- """
169
- points = np.asarray(points, dtype=float)
170
- if points.ndim != 2 or points.shape[1] != 2:
171
- raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
172
-
173
- N = points.shape[0]
174
- x_im, y_im = points.T
175
- hs = np.asarray(h, dtype=float)
176
-
177
- # Call lambdified function: returns shape (3, 1, N)
178
- # Reshape directly to (N, 3)
179
- ps_gnd = np.asarray(self._lambda_im2gnd(x_im, y_im, hs)).reshape(3, N).T
180
-
181
- return ps_gnd
182
-
183
- def gnd_to_ctd(self, points):
184
- """
185
- Transform from ground (3D world) to corrected image coordinates.
186
-
187
- Args:
188
- points: (N, 3) array of ground points [[x1, y1, z1], [x2, y2, z2], ...]
189
-
190
- Returns:
191
- (N, 2) array of image points [[x1, y1], ...]
192
- """
193
- points = np.asarray(points, dtype=float)
194
- if points.ndim != 2 or points.shape[1] != 3:
195
- raise ValueError(f"Expected (N, 3) array, got shape {points.shape}")
196
-
197
- N = points.shape[0]
198
- x_gnd, y_gnd, z_gnd = points.T
199
-
200
- # Call lambdified function: returns shape (2, 1, N)
201
- # Reshape directly to (N, 2)
202
- ps_im = np.asarray(self._lambda_gnd2im(x_gnd, y_gnd, z_gnd)).reshape(2, N).T
203
-
204
- return ps_im
205
-
206
- def src_to_gnd(self, points, h):
207
- """
208
- Transform from source to ground coordinates.
209
-
210
- Args:
211
- points: (N, 2) array of source points
212
- h: Scalar height or (N,) array of heights
213
-
214
- Returns:
215
- (N, 3) array of ground points
216
- """
217
- ctd_points = self.src_to_ctd(points)
218
- return self.ctd_to_gnd(ctd_points, h)
219
-
220
- def gnd_to_src(self, points):
221
- """
222
- Transform from ground to source coordinates.
223
-
224
- Args:
225
- points: (N, 3) array of ground points
226
-
227
- Returns:
228
- (N, 2) array of source points
229
- """
230
- ctd_points = self.gnd_to_ctd(points)
231
- return self.ctd_to_src(ctd_points)
232
-
233
- def get_key_point(self):
234
- """
235
- Get the key-point of the camera in world space.
236
-
237
- Returns:
238
- (3,) 3D coordinates of the key-point of the camera in world space
239
- """
240
- key_point = self._lambda_key_point()
241
- return np.array(key_point, dtype=np.float64)
242
-
243
- def ctd_to_ray(self, points):
244
- """
245
- Transform from corrected image coordinates to 3D rays in camera space.
246
-
247
- Args:
248
- points: (N, 2) array of corrected image points [[x1, y1], [x2, y2], ...]
249
-
250
- Returns:
251
- (N, 3) array of ray directions in camera space, with [nan, nan, nan] for invalid
252
- Ray directions are normalized to unit length.
253
- The ray for a point is defined as the vector from key-point of the camera.
254
- """
255
- points = np.asarray(points, dtype=float)
256
- if points.ndim != 2 or points.shape[1] != 2:
257
- raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
258
-
259
- N = points.shape[0]
260
- x_im, y_im = points.T
261
-
262
- # Call lambdified function: returns shape (3, 1, N)
263
- # Reshape directly to (N, 3)
264
- rays = np.asarray(self._lambda_im2ray(x_im, y_im)).reshape(3, N).T
265
-
266
- # Normalize ray directions to unit length
267
- ray_lengths = np.linalg.norm(rays, axis=1, keepdims=True)
268
- ray_lengths = np.where(ray_lengths > 0, ray_lengths, 1.0)
269
- rays_normalized = rays / ray_lengths
270
-
271
- return rays_normalized
272
-
273
- def src_to_ray(self, points):
274
- """
275
- Transform from source image coordinates to 3D rays in camera space.
276
-
277
- Args:
278
- points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
279
-
280
- Returns:
281
- (N, 3) array of ray directions in camera space, with [nan, nan, nan] for invalid
282
- Ray directions are normalized to unit length.
283
- The ray for a point is defined as the vector from key-point of the camera.
284
- """
285
- ctd_points = self.src_to_ctd(points)
286
- return self.ctd_to_ray(ctd_points)
287
-
288
- @classmethod
289
- def load(cls, archive_path):
290
- """
291
- Load camera transformer from NPZ archive file.
292
-
293
- Args:
294
- archive_path: Path to .npz calibration file
295
-
296
- Returns:
297
- CameraProjectionVectorized instance
298
- """
299
-
300
- # Convert to regular dict for easier access
301
- cam_data = read_npz_file(archive_path)
302
-
303
- return cls(cam_data)
304
-
305
-
306
- # class CameraProjection2:
307
- # """
308
- # Vectorized camera coordinate transformer for batch processing.
309
-
310
- # This class is optimized for processing multiple points at once.
311
- # All methods expect array inputs with shape (N, 2) or (N, 3).
312
-
313
- # Handles transformations between:
314
- # - src: Source (distorted) image coordinates
315
- # - ctd: Corrected (undistorted) image coordinates
316
- # - gnd: Ground (3D world) coordinates
317
- # """
318
-
319
- # # Allowed variable names for different transformation types
320
- # VARS_CTD_TO_GND = {
321
- # "x_im",
322
- # "y_im",
323
- # "proj_height",
324
- # "np",
325
- # "float64",
326
- # "asarray",
327
- # "dtype",
328
- # }
329
- # VARS_GND_TO_CTD = {"x_gnd", "y_gnd", "z_gnd", "np", "float64", "asarray", "dtype"}
330
- # VARS_CTD_TO_RAY = {"x_im", "y_im", "np", "float64", "asarray", "dtype"}
331
- # VARS_KEYPOINT = {"np", "float64", "asarray", "dtype"}
332
-
333
- # def __init__(self, cam_archive_data):
334
- # """
335
- # Initialize the camera transformer with calibration data.
336
-
337
- # Args:
338
- # cam_archive_data: Dictionary containing:
339
- # - src2ctd: Source to corrected distortion map (H x W x 2)
340
- # - ctd2src: Corrected to source distortion map (H x W x 2)
341
- # - x_gnd, y_gnd, z_gnd: String expressions for ctd -> ground
342
- # - x_im, y_im: String expressions for ground -> ctd
343
- # """
344
- # data = cam_archive_data
345
-
346
- # self.plan_scale = float(data["plan_scale"])
347
- # self.im_width = int(data["im_width"])
348
- # self.im_height = int(data["im_height"])
349
- # self.im_wh_size = (self.im_width, self.im_height)
350
-
351
- # # Store lookup tables
352
- # self.src2ctd_points_map = data["src2ctd"]
353
- # self.ctd2src_points_map = data["ctd2src"]
354
-
355
- # self.im_size = self.src2ctd_points_map.shape[:2]
356
-
357
- # # Compile transformation expressions for ctd -> gnd
358
- # self._x_gnd_func = compile_safe_expression(
359
- # data["x_gnd"],
360
- # param_names=["x_im", "y_im", "proj_height"],
361
- # allowed_vars=self.VARS_CTD_TO_GND,
362
- # )
363
- # self._y_gnd_func = compile_safe_expression(
364
- # data["y_gnd"],
365
- # param_names=["x_im", "y_im", "proj_height"],
366
- # allowed_vars=self.VARS_CTD_TO_GND,
367
- # )
368
- # self._z_gnd_func = compile_safe_expression(
369
- # data["z_gnd"],
370
- # param_names=["x_im", "y_im", "proj_height"],
371
- # allowed_vars=self.VARS_CTD_TO_GND,
372
- # )
373
-
374
- # # Compile transformation expressions for gnd -> ctd
375
- # self._x_im_func = compile_safe_expression(
376
- # data["x_im"],
377
- # param_names=["x_gnd", "y_gnd", "z_gnd"],
378
- # allowed_vars=self.VARS_GND_TO_CTD,
379
- # )
380
- # self._y_im_func = compile_safe_expression(
381
- # data["y_im"],
382
- # param_names=["x_gnd", "y_gnd", "z_gnd"],
383
- # allowed_vars=self.VARS_GND_TO_CTD,
384
- # )
385
-
386
- # # Compile keypoint expressions (camera position in world space)
387
- # self._x_key_func = compile_safe_expression(
388
- # data["x_key"],
389
- # param_names=[],
390
- # allowed_vars=self.VARS_KEYPOINT,
391
- # )
392
- # self._y_key_func = compile_safe_expression(
393
- # data["y_key"],
394
- # param_names=[],
395
- # allowed_vars=self.VARS_KEYPOINT,
396
- # )
397
- # self._z_key_func = compile_safe_expression(
398
- # data["z_key"],
399
- # param_names=[],
400
- # allowed_vars=self.VARS_KEYPOINT,
401
- # )
402
-
403
- # # Compile ray direction expressions for ctd -> ray
404
- # self._x_ray_func = compile_safe_expression(
405
- # data["x_ray"],
406
- # param_names=["x_im", "y_im"],
407
- # allowed_vars=self.VARS_CTD_TO_RAY,
408
- # )
409
- # self._y_ray_func = compile_safe_expression(
410
- # data["y_ray"],
411
- # param_names=["x_im", "y_im"],
412
- # allowed_vars=self.VARS_CTD_TO_RAY,
413
- # )
414
- # self._z_ray_func = compile_safe_expression(
415
- # data["z_ray"],
416
- # param_names=["x_im", "y_im"],
417
- # allowed_vars=self.VARS_CTD_TO_RAY,
418
- # )
419
-
420
- # def src_to_ctd(self, points):
421
- # """
422
- # Transform from source (distorted) to corrected coordinates.
423
-
424
- # Args:
425
- # points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
426
-
427
- # Returns:
428
- # (N, 2) array of corrected points, with [nan, nan] for out-of-bounds
429
- # """
430
- # points = np.asarray(points, dtype=float)
431
- # if points.ndim != 2 or points.shape[1] != 2:
432
- # raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
433
-
434
- # N = len(points)
435
- # result = np.full((N, 2), np.nan, dtype=float)
436
-
437
- # # Check for NaN input points
438
- # valid_input = ~np.isnan(points).any(axis=1)
439
-
440
- # if not valid_input.any():
441
- # return result
442
-
443
- # # Round to integer coordinates
444
- # p_int = np.round(points[valid_input]).astype(int)
445
-
446
- # # Vectorized bounds checking
447
- # in_bounds = (
448
- # (p_int[:, 0] >= 0)
449
- # & (p_int[:, 0] < self.src2ctd_points_map.shape[1])
450
- # & (p_int[:, 1] >= 0)
451
- # & (p_int[:, 1] < self.src2ctd_points_map.shape[0])
452
- # )
453
-
454
- # # Create mask for points that are both valid input and in bounds
455
- # valid_indices = np.where(valid_input)[0]
456
- # final_valid_indices = valid_indices[in_bounds]
457
- # valid_p_int = p_int[in_bounds]
458
-
459
- # # Vectorized lookup using advanced indexing
460
- # # Note: lookup map is [y, x] indexed
461
- # result[final_valid_indices] = self.src2ctd_points_map[
462
- # valid_p_int[:, 1], valid_p_int[:, 0]
463
- # ]
464
-
465
- # return result
466
-
467
- # def ctd_to_src(self, points):
468
- # """
469
- # Transform from corrected to source (distorted) coordinates.
470
-
471
- # Args:
472
- # points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
473
-
474
- # Returns:
475
- # (N, 2) array of source points, with [nan, nan] for out-of-bounds
476
- # """
477
- # points = np.asarray(points, dtype=float)
478
- # if points.ndim != 2 or points.shape[1] != 2:
479
- # raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
480
-
481
- # N = len(points)
482
- # result = np.full((N, 2), np.nan, dtype=float)
483
-
484
- # # Check for NaN input points
485
- # valid_input = ~np.isnan(points).any(axis=1)
486
-
487
- # if not valid_input.any():
488
- # return result
489
-
490
- # # Round to integer coordinates
491
- # p_int = np.round(points[valid_input]).astype(int)
492
-
493
- # # Vectorized bounds checking
494
- # in_bounds = (
495
- # (p_int[:, 0] >= 0)
496
- # & (p_int[:, 0] < self.ctd2src_points_map.shape[1])
497
- # & (p_int[:, 1] >= 0)
498
- # & (p_int[:, 1] < self.ctd2src_points_map.shape[0])
499
- # )
500
-
501
- # # Create mask for points that are both valid input and in bounds
502
- # valid_indices = np.where(valid_input)[0]
503
- # final_valid_indices = valid_indices[in_bounds]
504
- # valid_p_int = p_int[in_bounds]
505
-
506
- # # Vectorized lookup using advanced indexing
507
- # result[final_valid_indices] = self.ctd2src_points_map[
508
- # valid_p_int[:, 1], valid_p_int[:, 0]
509
- # ]
510
-
511
- # return result
512
-
513
- # def ctd_to_gnd(self, points, h):
514
- # """
515
- # Transform from corrected image coordinates to ground (3D world) coordinates.
516
-
517
- # Args:
518
- # points: (N, 2) array of corrected points [[x1, y1], [x2, y2], ...]
519
- # h: Scalar height or (N,) array of heights for each point
520
-
521
- # Returns:
522
- # (N, 3) array of ground points [[x1, y1, z1], ...], with [nan, nan, nan] for invalid
523
- # """
524
- # points = np.asarray(points, dtype=float)
525
- # if points.ndim != 2 or points.shape[1] != 2:
526
- # raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
527
-
528
- # N = len(points)
529
- # result = np.full((N, 3), np.nan, dtype=float)
530
-
531
- # # Handle scalar or array height
532
- # if np.isscalar(h):
533
- # h_array = np.full(N, h, dtype=float)
534
- # else:
535
- # h_array = np.asarray(h, dtype=float)
536
- # if h_array.shape != (N,):
537
- # raise ValueError(
538
- # f"Height array must have shape ({N},), got {h_array.shape}"
539
- # )
540
-
541
- # # Check for NaN input points
542
- # valid = ~np.isnan(points).any(axis=1)
543
-
544
- # if not valid.any():
545
- # return result
546
-
547
- # # Extract valid points
548
- # valid_points = points[valid]
549
- # valid_heights = h_array[valid]
550
-
551
- # # Vectorized expression evaluation
552
- # # NumPy operations in expressions will work element-wise on arrays
553
- # x_im = valid_points[:, 0]
554
- # y_im = valid_points[:, 1]
555
- # proj_height = valid_heights
556
-
557
- # # Evaluate expressions (they should handle arrays automatically via NumPy)
558
- # gnd_x = self._x_gnd_func(x_im, y_im, proj_height)
559
- # gnd_y = self._y_gnd_func(x_im, y_im, proj_height)
560
- # gnd_z = self._z_gnd_func(x_im, y_im, proj_height)
561
-
562
- # # Stack results and assign to valid indices
563
- # result[valid] = np.column_stack([gnd_x, gnd_y, gnd_z])
564
-
565
- # return result
566
-
567
- # def src_to_gnd(self, points, h):
568
- # """
569
- # Transform from source to ground coordinates.
570
-
571
- # Args:
572
- # points: (N, 2) array of source points
573
- # h: Scalar height or (N,) array of heights
574
-
575
- # Returns:
576
- # (N, 3) array of ground points
577
- # """
578
- # ctd_points = self.src_to_ctd(points)
579
- # return self.ctd_to_gnd(ctd_points, h)
580
-
581
- # def gnd_to_ctd(self, points):
582
- # """
583
- # Transform from ground (3D world) to corrected image coordinates.
584
-
585
- # Args:
586
- # points: (N, 3) array of ground points [[x1, y1, z1], [x2, y2, z2], ...]
587
-
588
- # Returns:
589
- # (N, 2) array of image points, with [nan, nan] for invalid
590
- # """
591
- # points = np.asarray(points, dtype=float)
592
- # if points.ndim != 2 or points.shape[1] != 3:
593
- # raise ValueError(f"Expected (N, 3) array, got shape {points.shape}")
594
-
595
- # N = len(points)
596
- # result = np.full((N, 2), np.nan, dtype=float)
597
-
598
- # # Check for NaN input points
599
- # valid = ~np.isnan(points).any(axis=1)
600
-
601
- # if not valid.any():
602
- # return result
603
-
604
- # # Extract valid points
605
- # valid_points = points[valid]
606
-
607
- # # Vectorized expression evaluation
608
- # x_gnd = valid_points[:, 0]
609
- # y_gnd = valid_points[:, 1]
610
- # z_gnd = valid_points[:, 2]
611
-
612
- # # Evaluate expressions
613
- # x_im = self._x_im_func(x_gnd, y_gnd, z_gnd)
614
- # y_im = self._y_im_func(x_gnd, y_gnd, z_gnd)
615
-
616
- # # Stack results and assign to valid indices
617
- # result[valid] = np.column_stack([x_im, y_im])
618
-
619
- # return result
620
-
621
- # def gnd_to_src(self, points):
622
- # """
623
- # Transform from ground to source coordinates.
624
-
625
- # Args:
626
- # points: (N, 3) array of ground points
627
-
628
- # Returns:
629
- # (N, 2) array of source points
630
- # """
631
- # ctd_points = self.gnd_to_ctd(points)
632
- # return self.ctd_to_src(ctd_points)
633
-
634
- # def ctd_to_ray(self, points):
635
- # """
636
- # Transform from corrected image coordinates to 3D rays in camera space.
637
-
638
- # Args:
639
- # points: (N, 2) array of corrected image points [[x1, y1], [x2, y2], ...]
640
-
641
- # Returns two argiments:
642
- # - (3,) 3D coordinates of the key-point of the camera in world space
643
- # - (N, 3) array of ray directions in camera space, with [nan, nan, nan] for invalid
644
- # Ray directions are normalized to unit length.
645
- # The ray for a point is defined as the vector from key-point of the camera.
646
- # """
647
- # points = np.asarray(points, dtype=float)
648
- # if points.ndim != 2 or points.shape[1] != 2:
649
- # raise ValueError(f"Expected (N, 2) array, got shape {points.shape}")
650
-
651
- # N = len(points)
652
- # ray_directions = np.full((N, 3), np.nan, dtype=float)
653
-
654
- # # Calculate keypoint (camera position in world space)
655
- # # These are typically constants, so we call with no arguments
656
- # keypoint = np.array(
657
- # [self._x_key_func(), self._y_key_func(), self._z_key_func()], dtype=float
658
- # )
659
-
660
- # # Check for valid points (not NaN)
661
- # valid = ~np.isnan(points).any(axis=1)
662
-
663
- # if not valid.any():
664
- # return keypoint, ray_directions
665
-
666
- # # Extract valid corrected points
667
- # valid_points = points[valid]
668
-
669
- # # Vectorized ray direction evaluation
670
- # x_im = valid_points[:, 0]
671
- # y_im = valid_points[:, 1]
672
-
673
- # # Evaluate ray direction expressions
674
- # ray_x = self._x_ray_func(x_im, y_im)
675
- # ray_y = self._y_ray_func(x_im, y_im)
676
- # ray_z = self._z_ray_func(x_im, y_im)
677
-
678
- # # Stack ray components and ensure float64 dtype
679
- # # (expressions with large integers can create object dtype arrays)
680
- # rays = np.column_stack([ray_x, ray_y, ray_z]).astype(np.float64)
681
-
682
- # # Normalize ray directions to unit length
683
- # ray_lengths = np.linalg.norm(rays, axis=1, keepdims=True)
684
- # # Avoid division by zero
685
- # ray_lengths = np.where(ray_lengths > 0, ray_lengths, 1.0)
686
- # rays_normalized = rays / ray_lengths
687
-
688
- # # Assign normalized rays to valid indices
689
- # ray_directions[valid] = rays_normalized
690
-
691
- # return keypoint, ray_directions
692
-
693
- # def src_to_ray(self, points):
694
- # """
695
- # Transform from source image coordinates to 3D rays in camera space.
696
-
697
- # Args:
698
- # points: (N, 2) array of source points [[x1, y1], [x2, y2], ...]
699
-
700
- # Returns two argiments:
701
- # - (3,) 3D coordinates of the key-point of the camera in world space
702
- # - (N, 3) array of ray directions in camera space, with [nan, nan, nan] for invalid
703
- # Ray directions are normalized to unit length.
704
- # The ray for a point is defined as the vector from key-point of the camera.
705
- # """
706
- # ctd_points = self.src_to_ctd(points)
707
- # return self.ctd_to_ray(ctd_points)
708
-
709
- # @classmethod
710
- # def load(cls, archive_path):
711
- # """
712
- # Load camera transformer from NPZ archive file.
713
-
714
- # Args:
715
- # archive_path: Path to .npz calibration file
716
-
717
- # Returns:
718
- # CameraProjectionVectorized instance
719
- # """
720
-
721
- # # Convert to regular dict for easier access
722
- # cam_data = read_npz_file(archive_path)
723
-
724
- # return cls(cam_data)
File without changes
File without changes
File without changes
File without changes