Skip to content

Commit 0d6b6fb

Browse files
committed
Exterior is also a numpy array now
1 parent b4ee139 commit 0d6b6fb

8 files changed

Lines changed: 187 additions & 311 deletions

File tree

openptv_python/calibration.py

Lines changed: 92 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,35 @@
66
import numpy as np
77
from numba import njit
88

9-
from openptv_python.vec_utils import vec_set
10-
119

1210
@njit
13-
def rotation_matrix(phi: float, omega: float, kappa: float) -> np.ndarray:
14-
"""Calculate the necessary trigonometric functions to rotate the Dmatrix of Exterior ext_par."""
15-
cp = np.cos(phi)
16-
sp = np.sin(phi)
11+
def rotation_matrix(ext: np.ndarray) -> None:
12+
"""Calculate the necessary trigonometric functions to rotate the Dmatrix of Exterior ext_par.
13+
14+
Rotation is performed by multiplication of three rotation matrices,
15+
rotation around X axis is performed first, then around Y axis, and finally around Z axis.
16+
rotation around X axis does not change values along X axis, i.e. X_omega = X
17+
18+
Maas, H.G., Gruen, A. & Papantoniou, D. Particle tracking velocimetry in
19+
three-dimensional flows. Experiments in Fluids 15, 133–146 (1993).
20+
https://doi.org/10.1007/BF00190953
21+
22+
"""
23+
omega, phi, kappa = ext[0]['omega'], ext[0]['phi'], ext[0]['kappa']
24+
25+
1726
co = np.cos(omega)
1827
so = np.sin(omega)
28+
29+
cp = np.cos(phi)
30+
sp = np.sin(phi)
31+
1932
ck = np.cos(kappa)
2033
sk = np.sin(kappa)
2134

22-
dm = np.zeros((3, 3), dtype=np.float64)
35+
# dm = np.zeros((3, 3), dtype=np.float64)
36+
dm = ext[0]['dm'] # shortcut to the dm field of the first element of the array
37+
2338
dm[0, 0] = cp * ck
2439
dm[0, 1] = -cp * sk
2540
dm[0, 2] = sp
@@ -29,59 +44,39 @@ def rotation_matrix(phi: float, omega: float, kappa: float) -> np.ndarray:
2944
dm[2, 0] = so * sk - co * sp * ck
3045
dm[2, 1] = so * ck + co * sp * sk
3146
dm[2, 2] = co * cp
32-
return dm
3347

48+
# print(dm)
3449

35-
class Exterior:
36-
"""Exterior orientation data structure."""
50+
return None
3751

38-
def __init__(self, x0=0.0, y0=0.0, z0=0.0, omega=0.0, phi=0.0, kappa=0.0, dm=None):
39-
self.x0 = x0
40-
self.y0 = y0
41-
self.z0 = z0
42-
self.omega = omega
43-
self.phi = phi
44-
self.kappa = kappa
45-
self.dm = dm if dm is not None else np.identity(3, dtype=np.float64)
52+
exterior_dtype = np.dtype([
53+
('x0', np.float64),
54+
('y0', np.float64),
55+
('z0', np.float64),
56+
('omega', np.float64),
57+
('phi', np.float64),
58+
('kappa', np.float64),
59+
('dm', np.float64, (3, 3))
60+
])
61+
Exterior = np.zeros(1, dtype=exterior_dtype).view(np.recarray) # initialize memory
62+
rotation_matrix(Exterior) # rotation should be a unit matrix
63+
assert np.allclose(np.eye(3), Exterior[0]['dm'])
4664

47-
def update_rotation_matrix(self) -> None:
48-
"""Create rotation matrix using three angles of the camera."""
49-
self.dm = rotation_matrix(self.phi, self.omega, self.kappa)
50-
51-
def set_rotation_matrix(self, dm: np.ndarray) -> None:
52-
"""Set the rotation matrix of the camera."""
53-
self.dm = dm
54-
55-
def set_pos(self, pos: np.ndarray) -> None:
56-
"""Set the position of the camera."""
57-
pos = np.array(pos, dtype = np.float64)
58-
59-
if pos.shape != (3,):
60-
raise ValueError(
61-
"Illegal array argument "
62-
+ str(pos)
63-
+ " for x, y, z. Expected array/list of 3 numbers"
64-
)
65-
self.x0, self.y0, self.z0 = pos
6665

67-
def set_angles(self, angles: np.ndarray) -> None:
68-
"""Set the angles of the camera."""
69-
self.omega, self.phi, self.kappa = angles
66+
# rotation_matrix(exterior) # inplace update of the rotation matrix
7067

71-
# adjust rotation matrix
72-
self.update_rotation_matrix()
7368

74-
def increment_attribute(self, attr_name, increment_value):
75-
"""Update the value of an attribute by increment_value."""
76-
if hasattr(self, attr_name):
77-
setattr(self, attr_name, getattr(
78-
self, attr_name) + increment_value)
69+
# def increment_attribute(self, attr_name, increment_value):
70+
# """Update the value of an attribute by increment_value."""
71+
# if hasattr(self, attr_name):
72+
# setattr(self, attr_name, getattr(
73+
# self, attr_name) + increment_value)
7974

80-
def __repr__(self) -> str:
81-
"""Return a string representation of the Exterior object."""
82-
output = f"Exterior: x0={self.x0}, y0={self.y0}, z0={self.z0}\n"
83-
output += f"omega={self.omega}, phi={self.phi}, kappa={self.kappa}\n"
84-
return output
75+
# def __repr__(self) -> str:
76+
# """Return a string representation of the Exterior object."""
77+
# output = f"Exterior: x0={self['x0']}, y0={self['y0']}, z0={self['z0']}\n"
78+
# output += f"omega={self['omega']}, phi={self['phi']}, kappa={self['kappa']}\n"
79+
# return output
8580

8681

8782
class Interior:
@@ -100,10 +95,6 @@ def set_back_focal_distance(self, cc: float) -> None:
10095
"""Set the back focal distance of the camera."""
10196
self.cc = cc
10297

103-
def default_glass_vec() -> np.ndarray:
104-
"""Return default glass vector."""
105-
return vec_set(0.0, 0.0, 1.0)
106-
10798
class ap_52:
10899
"""Additional parameters for distortion correction."""
109100

@@ -149,7 +140,7 @@ class Calibration:
149140

150141
def __init__(self, ext_par=None, int_par=None, glass_par=None, added_par=None, mmlut=None):
151142
if ext_par is None:
152-
ext_par = Exterior()
143+
ext_par = Exterior.copy()
153144
if int_par is None:
154145
int_par = Interior()
155146
if glass_par is None:
@@ -165,6 +156,7 @@ def __init__(self, ext_par=None, int_par=None, glass_par=None, added_par=None, m
165156
self.added_par = added_par
166157
self.mmlut = mmlut
167158

159+
168160
@classmethod
169161
def from_file(cls, ori_file: str, add_file: str):
170162
"""
@@ -197,7 +189,7 @@ def from_file(cls, ori_file: str, add_file: str):
197189
# skip line
198190
fp.readline()
199191

200-
# read 3 lines and set a rotation matrix
192+
# read 3 lines of rotation matrix, but recalculate it from angles
201193
_ = [[float(x) for x in fp.readline().split()] for _ in range(3)]
202194

203195
# I skip reading rotation matrix as it's set up by set_angles.
@@ -254,23 +246,42 @@ def write(self, ori_file: str, addpar_file: str):
254246
if not success:
255247
raise IOError("Failed to write ori file")
256248

249+
250+
def increment_attribute(self, attr_name, increment_value):
251+
"""Update the value of an attribute by increment_value."""
252+
if hasattr(self, attr_name):
253+
setattr(self, attr_name, getattr(
254+
self, attr_name) + increment_value)
255+
256+
def update_rotation_matrix(self) -> None:
257+
"""Update the rotation matrix of the exterior orientation."""
258+
rotation_matrix(self.ext_par)
259+
257260
def set_rotation_matrix(self, dm: np.ndarray) -> None:
258261
"""Set exterior rotation matrix."""
259262
if dm.shape != (3, 3):
260263
raise ValueError("Illegal argument for exterior rotation matrix")
261-
self.ext_par.set_rotation_matrix(dm)
264+
self.ext_par[0]['dm'] = dm
262265

263-
def set_pos(self, x_y_z_np: np.ndarray) -> None:
266+
def set_pos(self, pos: np.ndarray) -> None:
264267
"""
265268
Set exterior position.
266269
267270
Parameter: x_y_z_np - numpy array of 3 elements for x, y, z.
268271
"""
269-
self.ext_par.set_pos(x_y_z_np)
272+
pos = np.array(pos, dtype = np.float64)
270273

271-
def get_pos(self):
274+
if pos.shape != (3,):
275+
raise ValueError(
276+
"Illegal array argument "
277+
+ str(pos)
278+
+ " for x, y, z. Expected array/list of 3 numbers"
279+
)
280+
self.ext_par['x0'], self.ext_par['y0'], self.ext_par['z0'] = pos
281+
282+
def get_pos(self) -> np.ndarray:
272283
"""Return array of 3 elements representing exterior's x, y, z."""
273-
return np.r_[self.ext_par.x0, self.ext_par.y0, self.ext_par.z0]
284+
return np.r_[self.ext_par['x0'], self.ext_par['y0'], self.ext_par['z0']]
274285

275286
def set_angles(self, o_p_k_np: np.ndarray) -> None:
276287
"""
@@ -285,15 +296,16 @@ def set_angles(self, o_p_k_np: np.ndarray) -> None:
285296
f"Illegal array argument {o_p_k_np} for "
286297
"omega, phi, kappa. Expected array or list of 3 float"
287298
)
288-
self.ext_par.set_angles(o_p_k_np)
299+
self.ext_par['omega'], self.ext_par['phi'], self.ext_par['kappa'] = o_p_k_np
300+
self.update_rotation_matrix()
289301

290-
def get_angles(self):
302+
def get_angles(self) -> np.ndarray:
291303
"""Return an array of 3 elements representing omega, phi, kappa."""
292-
return np.r_[self.ext_par.omega, self.ext_par.phi, self.ext_par.kappa]
304+
return np.r_[self.ext_par['omega'], self.ext_par['phi'], self.ext_par['kappa']]
293305

294-
def get_rotation_matrix(self):
306+
def get_rotation_matrix(self) -> np.ndarray:
295307
"""Return a 3x3 numpy array that represents Exterior's rotation matrix."""
296-
return self.ext_par.dm
308+
return self.ext_par['dm'].copy()
297309

298310
def set_primary_point(self, prim_point_pos: np.ndarray) -> None:
299311
"""
@@ -418,7 +430,7 @@ def copy(self, new_copy):
418430

419431

420432
def write_ori(
421-
ext_par: Exterior,
433+
ext_par: np.ndarray,
422434
int_par: Interior,
423435
glass: np.ndarray,
424436
added_par: ap_52,
@@ -429,9 +441,9 @@ def write_ori(
429441
success = False
430442

431443
with open(filename, "w", encoding="utf-8") as fp:
432-
fp.write(f"{ext_par.x0:.8f} {ext_par.y0:.8f} {ext_par.z0:.8f}\n")
433-
fp.write(f"{ext_par.omega:.8f} {ext_par.phi:.8f} {ext_par.kappa:.8f}\n\n")
434-
for row in ext_par.dm:
444+
fp.write(f"{ext_par[0]['x0']:.8f} {ext_par[0]['y0']:.8f} {ext_par[0]['z0']:.8f}\n")
445+
fp.write(f"{ext_par[0]['omega']:.8f} {ext_par[0]['phi']:.8f} {ext_par[0]['kappa']:.8f}\n\n")
446+
for row in ext_par[0]['dm']:
435447
fp.write(f"{row[0]:.7f} {row[1]:.7f} {row[2]:.7f}\n")
436448
fp.write(f"\n{int_par.xh:.4f} {int_par.yh:.4f}\n{int_par.cc:.4f}\n")
437449
fp.write(
@@ -470,16 +482,16 @@ def read_ori(ori_file: str, add_file: str) -> Calibration:
470482
return ret
471483

472484

473-
def compare_exterior(e1: Exterior, e2: Exterior) -> bool:
485+
def compare_exterior(e1: np.ndarray, e2: np.ndarray) -> bool:
474486
"""Compare exterior orientation parameters."""
475487
return (
476-
np.allclose(e1.dm, e2.dm, atol=1e-6)
477-
and (e1.x0 == e2.x0)
478-
and (e1.y0 == e2.y0)
479-
and (e1.z0 == e2.z0)
480-
and (e1.omega == e2.omega)
481-
and (e1.phi == e2.phi)
482-
and (e1.kappa == e2.kappa)
488+
np.allclose(e1['dm'], e2['dm'], atol=1e-6)
489+
and (e1['x0'] == e2['x0'])
490+
and (e1['y0'] == e2['y0'])
491+
and (e1['z0'] == e2['z0'])
492+
and (e1['omega'] == e2['omega'])
493+
and (e1['phi'] == e2['phi'])
494+
and (e1['kappa'] == e2['kappa'])
483495
)
484496

485497

openptv_python/imgcoord.py

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from .multimed import back_trans_point, multimed_nlay, trans_cam_point
1010
from .parameters import MultimediaPar
1111
from .trafo import flat_to_dist
12-
from .vec_utils import vec_set
1312

1413

1514
def flat_image_coord(
@@ -46,43 +45,18 @@ def flat_image_coord(
4645
x_t, y_t = multimed_nlay(cal_t, mm, pos_t)
4746
# print(f"x_t {x_t}, y_t {y_t}")
4847

49-
pos_t = vec_set(x_t, y_t, pos_t[2])
48+
pos_t = np.r_[x_t, y_t, pos_t[2]]
5049
pos = back_trans_point(pos_t, mm, cal.glass_par, cross_p, cross_c)
5150

52-
# print(f"pos {pos}")
53-
54-
deno = (
55-
cal.ext_par.dm[0][2] * (pos[0] - cal.ext_par.x0)
56-
+ cal.ext_par.dm[1][2] * (pos[1] - cal.ext_par.y0)
57-
+ cal.ext_par.dm[2][2] * (pos[2] - cal.ext_par.z0)
58-
)
59-
60-
# print(f"deno {deno}")
51+
dm = cal.ext_par[0].dm
52+
origin = np.r_[cal.ext_par.x0, cal.ext_par.y0, cal.ext_par.z0]
6153

54+
deno = np.dot(dm[:,2], (pos - origin))
6255
if deno == 0:
6356
deno = 1
6457

65-
x = (
66-
-cal.int_par.cc
67-
* (
68-
cal.ext_par.dm[0][0] * (pos[0] - cal.ext_par.x0)
69-
+ cal.ext_par.dm[1][0] * (pos[1] - cal.ext_par.y0)
70-
+ cal.ext_par.dm[2][0] * (pos[2] - cal.ext_par.z0)
71-
)
72-
/ deno
73-
)
74-
75-
y = (
76-
-cal.int_par.cc
77-
* (
78-
cal.ext_par.dm[0][1] * (pos[0] - cal.ext_par.x0)
79-
+ cal.ext_par.dm[1][1] * (pos[1] - cal.ext_par.y0)
80-
+ cal.ext_par.dm[2][1] * (pos[2] - cal.ext_par.z0)
81-
)
82-
/ deno
83-
)
84-
85-
# print(f"x {x}, y {y}")
58+
x = (-cal.int_par.cc * np.dot(dm[:, 0], (pos - origin)) / deno)
59+
y = (-cal.int_par.cc * np.dot(dm[:, 1], (pos - origin)) / deno)
8660

8761
return x, y
8862

openptv_python/multimed.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import numpy as np
55
from numba import njit
66

7-
from .calibration import Calibration, Exterior
7+
from .calibration import Calibration
88
from .parameters import (
99
ControlPar,
1010
MultimediaPar,
@@ -116,7 +116,7 @@ def fast_multimed_r_nlay(
116116

117117

118118
def trans_cam_point(
119-
ex: Exterior, mm: MultimediaPar, glass_dir: np.ndarray, pos: np.ndarray
119+
ex: np.ndarray, mm: MultimediaPar, glass_dir: np.ndarray, pos: np.ndarray
120120
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, float]:
121121
"""Transform the camera and point coordinates to the glass coordinates.
122122
@@ -127,7 +127,7 @@ def trans_cam_point(
127127
128128
pos_t, cross_p, cross_c = trans_cam_point(ex, mm, glass, pos, ex_t)
129129
"""
130-
origin = np.array([ex.x0, ex.y0, ex.z0], dtype=np.float64)
130+
origin = np.r_[ex.x0, ex.y0, ex.z0]
131131
pos = pos.astype(np.float64)
132132

133133
return fast_trans_cam_point(
@@ -196,7 +196,7 @@ def back_trans_point(
196196
return fast_back_trans_point(glass, mm.d[0], cross_c, cross_p, pos_t)
197197

198198
@njit
199-
def fast_back_trans_point(glass_direction: np.recarray, d: float, cross_c, cross_p, pos_t) -> np.ndarray:
199+
def fast_back_trans_point(glass_direction: np.ndarray, d: float, cross_c, cross_p, pos_t) -> np.ndarray:
200200
"""Run numba faster version of back projection."""
201201
# Calculate the glass direction vector
202202

0 commit comments

Comments
 (0)