Skip to content

Commit dd9ea01

Browse files
committed
Add principal point from intrinsic matrix
1 parent 5abf96d commit dd9ea01

File tree

7 files changed

+136
-3
lines changed

7 files changed

+136
-3
lines changed

diffdrr/_modidx.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
'diffdrr.utils._convert_to_rotation_matrix': ( 'api/utils.html#_convert_to_rotation_matrix',
6060
'diffdrr/utils.py'),
6161
'diffdrr.utils.convert': ('api/utils.html#convert', 'diffdrr/utils.py'),
62+
'diffdrr.utils.get_focal_length': ('api/utils.html#get_focal_length', 'diffdrr/utils.py'),
63+
'diffdrr.utils.get_principal_point': ('api/utils.html#get_principal_point', 'diffdrr/utils.py'),
64+
'diffdrr.utils.parse_intrinsic_matrix': ('api/utils.html#parse_intrinsic_matrix', 'diffdrr/utils.py'),
6265
'diffdrr.utils.quaternion_adjugate_to_quaternion': ( 'api/utils.html#quaternion_adjugate_to_quaternion',
6366
'diffdrr/utils.py'),
6467
'diffdrr.utils.quaternion_to_quaternion_adjugate': ( 'api/utils.html#quaternion_to_quaternion_adjugate',

diffdrr/detector.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def __init__(
3030
width: int, # Width of the X-ray detector
3131
delx: float, # Pixel spacing in the X-direction
3232
dely: float, # Pixel spacing in the Y-direction
33+
x0: float, # Principal point X-offset
34+
y0: float, # Principal point Y-offset
3335
n_subsample: int | None = None, # Number of target points to randomly sample
3436
reverse_x_axis: bool = False, # If pose includes reflection (in E(3) not SE(3)), reverse x-axis
3537
):
@@ -39,6 +41,8 @@ def __init__(
3941
self.width = width
4042
self.delx = delx
4143
self.dely = dely
44+
self.x0 = x0
45+
self.y0 = y0
4246
self.n_subsample = n_subsample
4347
if self.n_subsample is not None:
4448
self.subsamples = []
@@ -79,6 +83,10 @@ def _initialize_carm(self: Detector):
7983
source = source.unsqueeze(0)
8084
target = target.unsqueeze(0)
8185

86+
# Apply principal point offset
87+
target[..., 2] -= self.x0
88+
target[..., 1] -= self.y0
89+
8290
if self.n_subsample is not None:
8391
sample = torch.randperm(self.height * self.width)[: int(self.n_subsample)]
8492
target = target[:, sample, :]

diffdrr/drr.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def __init__(
2828
width: int
2929
| None = None, # Width of the rendered DRR (if not provided, set to `height`)
3030
dely: float | None = None, # Y-axis pixel size (if not provided, set to `delx`)
31+
x0: float = 0.0, # Principal point X-offset
32+
y0: float = 0.0, # Principal point Y-offset
3133
p_subsample: float | None = None, # Proportion of pixels to randomly subsample
3234
reshape: bool = True, # Return DRR with shape (b, 1, h, w)
3335
reverse_x_axis: bool = False, # If pose includes reflection (in E(3) not SE(3)), reverse x-axis
@@ -49,6 +51,8 @@ def __init__(
4951
width,
5052
delx,
5153
dely,
54+
x0,
55+
y0,
5256
n_subsample=n_subsample,
5357
reverse_x_axis=reverse_x_axis,
5458
)

diffdrr/utils.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
# %% auto 0
44
__all__ = ['rotation_10d_to_quaternion', 'quaternion_to_rotation_10d', 'quaternion_adjugate_to_quaternion',
5-
'quaternion_to_quaternion_adjugate', 'convert']
5+
'quaternion_to_quaternion_adjugate', 'convert', 'get_focal_length', 'get_principal_point',
6+
'parse_intrinsic_matrix']
67

78
# %% ../notebooks/api/06_utils.ipynb 4
89
import torch
@@ -13,9 +14,9 @@
1314
"euler_angles",
1415
"matrix",
1516
"quaternion",
17+
"quaternion_adjugate",
1618
"rotation_6d",
1719
"rotation_10d",
18-
"quaternion_adjugate",
1920
]
2021

2122
# %% ../notebooks/api/06_utils.ipynb 6
@@ -153,3 +154,37 @@ def _convert_from_rotation_matrix(matrix, parameterization, convention=None):
153154
f"parameterization must be in {PARAMETERIZATIONS}, not {parameterization}"
154155
)
155156
return rotation
157+
158+
# %% ../notebooks/api/06_utils.ipynb 13
159+
def get_focal_length(
160+
intrinsic, # Intrinsic matrix (3 x 3 tensor)
161+
delx: float, # X-direction spacing (in units length)
162+
dely: float, # Y-direction spacing (in units length)
163+
) -> float: # Focal length (in units length)
164+
fx = intrinsic[0, 0]
165+
fy = intrinsic[1, 1]
166+
return abs((fx * delx) + (fy * delx)).item() / 2.0
167+
168+
# %% ../notebooks/api/06_utils.ipynb 14
169+
def get_principal_point(
170+
intrinsic, # Intrinsic matrix (3 x 3 tensor)
171+
height: int, # Y-direction length
172+
width: int, # X-direction length
173+
delx: float, # X-direction spacing (in units length)
174+
dely: float, # Y-direction spacing (in units length)
175+
):
176+
x0 = delx * (width / 2 - intrinsic[0, 2])
177+
y0 = dely * (height / 2 - intrinsic[1, 2])
178+
return x0.item(), y0.item()
179+
180+
# %% ../notebooks/api/06_utils.ipynb 15
181+
def parse_intrinsic_matrix(
182+
intrinsic, # Intrinsic matrix (3 x 3 tensor)
183+
height: int, # Y-direction length
184+
width: int, # X-direction length
185+
delx: float, # X-direction spacing (in units length)
186+
dely: float, # Y-direction spacing (in units length)
187+
):
188+
focal_length = get_focal_length(intrinsic, delx, dely)
189+
x0, y0 = get_principal_point(intrinsic, height, width, delx, dely)
190+
return focal_length, x0, y0

notebooks/api/00_drr.ipynb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@
121121
" delx: float, # X-axis pixel size\n",
122122
" width: int | None = None, # Width of the rendered DRR (if not provided, set to `height`)\n",
123123
" dely: float | None = None, # Y-axis pixel size (if not provided, set to `delx`)\n",
124+
" x0: float = 0.0, # Principal point X-offset\n",
125+
" y0: float = 0.0, # Principal point Y-offset\n",
124126
" p_subsample: float | None = None, # Proportion of pixels to randomly subsample\n",
125127
" reshape: bool = True, # Return DRR with shape (b, 1, h, w)\n",
126128
" reverse_x_axis: bool = False, # If pose includes reflection (in E(3) not SE(3)), reverse x-axis\n",
@@ -141,6 +143,8 @@
141143
" width,\n",
142144
" delx,\n",
143145
" dely,\n",
146+
" x0,\n",
147+
" y0,\n",
144148
" n_subsample=n_subsample,\n",
145149
" reverse_x_axis=reverse_x_axis,\n",
146150
" )\n",

notebooks/api/02_detector.ipynb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
" width: int, # Width of the X-ray detector\n",
9494
" delx: float, # Pixel spacing in the X-direction\n",
9595
" dely: float, # Pixel spacing in the Y-direction\n",
96+
" x0: float, # Principal point X-offset\n",
97+
" y0: float, # Principal point Y-offset\n",
9698
" n_subsample: int | None = None, # Number of target points to randomly sample\n",
9799
" reverse_x_axis: bool = False, # If pose includes reflection (in E(3) not SE(3)), reverse x-axis\n",
98100
" ):\n",
@@ -102,6 +104,8 @@
102104
" self.width = width\n",
103105
" self.delx = delx\n",
104106
" self.dely = dely\n",
107+
" self.x0 = x0\n",
108+
" self.y0 = y0\n",
105109
" self.n_subsample = n_subsample\n",
106110
" if self.n_subsample is not None:\n",
107111
" self.subsamples = []\n",
@@ -150,6 +154,10 @@
150154
" source = source.unsqueeze(0)\n",
151155
" target = target.unsqueeze(0)\n",
152156
"\n",
157+
" # Apply principal point offset\n",
158+
" target[..., 2] -= self.x0\n",
159+
" target[..., 1] -= self.y0\n",
160+
"\n",
153161
" if self.n_subsample is not None:\n",
154162
" sample = torch.randperm(self.height * self.width)[: int(self.n_subsample)]\n",
155163
" target = target[:, sample, :]\n",

notebooks/api/06_utils.ipynb

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@
6767
" \"euler_angles\",\n",
6868
" \"matrix\",\n",
6969
" \"quaternion\",\n",
70+
" \"quaternion_adjugate\",\n",
7071
" \"rotation_6d\",\n",
7172
" \"rotation_10d\",\n",
72-
" \"quaternion_adjugate\",\n",
7373
"]"
7474
]
7575
},
@@ -256,6 +256,77 @@
256256
" return rotation"
257257
]
258258
},
259+
{
260+
"cell_type": "markdown",
261+
"id": "735bc47e-8fbe-4e56-8b28-3a0aa36ac6c9",
262+
"metadata": {},
263+
"source": [
264+
"## Intrinsic matrix parsing\n",
265+
"\n",
266+
"From a calibrated camera's intrinsic matrix, calculate the following properties:\n",
267+
"\n",
268+
"- Focal length (in units length)\n",
269+
"- Principal point (in units length)"
270+
]
271+
},
272+
{
273+
"cell_type": "code",
274+
"execution_count": null,
275+
"id": "21f2cbe7-2edb-497e-ae26-2810ae383b06",
276+
"metadata": {},
277+
"outputs": [],
278+
"source": [
279+
"#| export\n",
280+
"def get_focal_length(\n",
281+
" intrinsic, # Intrinsic matrix (3 x 3 tensor)\n",
282+
" delx: float, # X-direction spacing (in units length)\n",
283+
" dely: float, # Y-direction spacing (in units length)\n",
284+
") -> float: # Focal length (in units length)\n",
285+
" fx = intrinsic[0, 0]\n",
286+
" fy = intrinsic[1, 1]\n",
287+
" return abs((fx * delx) + (fy * delx)).item() / 2.0"
288+
]
289+
},
290+
{
291+
"cell_type": "code",
292+
"execution_count": null,
293+
"id": "7f8cc2a3-7589-4c24-a75a-ec1a0f921750",
294+
"metadata": {},
295+
"outputs": [],
296+
"source": [
297+
"#| export\n",
298+
"def get_principal_point(\n",
299+
" intrinsic, # Intrinsic matrix (3 x 3 tensor)\n",
300+
" height: int, # Y-direction length\n",
301+
" width: int, # X-direction length\n",
302+
" delx: float, # X-direction spacing (in units length)\n",
303+
" dely: float, # Y-direction spacing (in units length)\n",
304+
"):\n",
305+
" x0 = delx * (width / 2 - intrinsic[0, 2])\n",
306+
" y0 = dely * (height / 2 - intrinsic[1, 2])\n",
307+
" return x0.item(), y0.item()"
308+
]
309+
},
310+
{
311+
"cell_type": "code",
312+
"execution_count": null,
313+
"id": "b26ca510-b8c3-4383-ad67-4ff9d852484d",
314+
"metadata": {},
315+
"outputs": [],
316+
"source": [
317+
"#| export\n",
318+
"def parse_intrinsic_matrix(\n",
319+
" intrinsic, # Intrinsic matrix (3 x 3 tensor)\n",
320+
" height: int, # Y-direction length\n",
321+
" width: int, # X-direction length\n",
322+
" delx: float, # X-direction spacing (in units length)\n",
323+
" dely: float, # Y-direction spacing (in units length)\n",
324+
"):\n",
325+
" focal_length = get_focal_length(intrinsic, delx, dely)\n",
326+
" x0, y0 = get_principal_point(intrinsic, height, width, delx, dely)\n",
327+
" return focal_length, x0, y0"
328+
]
329+
},
259330
{
260331
"cell_type": "code",
261332
"execution_count": null,

0 commit comments

Comments
 (0)