Skip to content

Commit d0c57b7

Browse files
Add VF options dataclass (OpenwaterHealth#165)
1 parent d295c90 commit d0c57b7

2 files changed

Lines changed: 98 additions & 11 deletions

File tree

src/openlifu/virtual_fit.py

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4+
from dataclasses import dataclass
45
from typing import List, Sequence, Tuple
56

67
import numpy as np
@@ -17,12 +18,83 @@
1718
spherical_interpolator_from_mesh,
1819
vtk_img_from_array_and_affine,
1920
)
21+
from openlifu.util.dict_conversion import DictMixin
22+
from openlifu.util.units import getunitconversion
2023

2124
log = logging.getLogger("VirtualFit")
2225

2326
ras2asl_3x3 = np.array([[0,1,0],[0,0,1],[-1,0,0]], dtype=float) # ASL means Anterior-Superior-Left coordinates
2427
asl2ras_3x3 = ras2asl_3x3.transpose()
2528

29+
@dataclass
30+
class VirtualFitOptions(DictMixin):
31+
"""Parameters to configure the `virtual_fit` algorithm.
32+
33+
The terms 'pitch' and 'yaw' used here refer to the following target-centric angular coordinates in patient space:
34+
pitch: The angle between the anterior axis through the target and the ray from from the target to the projection of
35+
a given point into the anterior-superior plane.
36+
yaw: The angle between the anterior-superior plane through the target and the ray from the target to a given point.
37+
38+
Another way to describe them in terms of standard spherical coordinates centered at the target in ASL (anterior-superior-left) space:
39+
pitch: The azimuthal spherical coordinate.
40+
yaw: 90 degrees minus the polar spherical coordinate.
41+
"""
42+
43+
units:float = "mm"
44+
"""The units of length used in the length attributes of this class"""
45+
46+
transducer_steering_center_distance:float = 50.
47+
"""Distance from the transducer origin axially to the center of the steering zone in the units `units`"""
48+
49+
steering_limits:Tuple[Tuple[float,float],Tuple[float,float],Tuple[float,float]] = ((-50,50),(-50,50),(-50,50))
50+
"""Distance from the transducer origin axially to the center of the steering zone in the units `units`"""
51+
52+
pitch_range : Tuple[float,float] = (-10,150)
53+
"""Range of pitches to include in the transducer fitting search grid, in degrees"""
54+
55+
pitch_step : float = 5
56+
"""Pitch step size when forming the transducer fitting search grid, in degrees"""
57+
58+
yaw_range : Tuple[float, float] = (-65, 65)
59+
"""Range of yaws to include in the transducer fitting search grid, in degrees"""
60+
61+
yaw_step : float = 5
62+
"""Yaw step size when forming the transducer fitting search grid, in degrees"""
63+
64+
planefit_dyaw_extent:float = 15
65+
"""Left and right extents of the point grid to be used for plane fitting along the local yaw axes,
66+
in units of `units`. The plane fitting point grid will be twice this size, since this is left
67+
and right extents. (Note that this has units of length, not angle!)"""
68+
69+
planefit_dyaw_step:float = 3
70+
"""Local yaw axis step size to use when constructing plane fitting grids. In spatial units of `units`."""
71+
72+
planefit_dpitch_extent:float = 15
73+
"""Left and right extents of the point grid to be used for plane fitting along the local pitch axes,
74+
in spatial units of `units`. The plane fitting point grid will be twice this size, since this is left
75+
and right extents."""
76+
77+
planefit_dpitch_step:float = 3
78+
"""Local pitch axis step size to use when constructing plane fitting grids. In spatial units of `units`."""
79+
80+
def to_units(self, target_units:str) -> VirtualFitOptions:
81+
"""Do unit conversion and return a version of this VirtualFitOptions that uses
82+
`target_units` as the units for all attributes that have units of length."""
83+
conversion_factor = getunitconversion(from_unit = self.units, to_unit=target_units)
84+
return VirtualFitOptions(
85+
units = target_units,
86+
transducer_steering_center_distance = conversion_factor * self.transducer_steering_center_distance,
87+
steering_limits = tuple(map(tuple,conversion_factor*np.array(self.steering_limits))),
88+
pitch_range = self.pitch_range,
89+
pitch_step = self.pitch_step,
90+
yaw_range = self.yaw_range,
91+
yaw_step = self.yaw_step,
92+
planefit_dyaw_extent = conversion_factor * self.planefit_dyaw_extent,
93+
planefit_dyaw_step = conversion_factor * self.planefit_dyaw_step,
94+
planefit_dpitch_extent = conversion_factor * self.planefit_dpitch_extent,
95+
planefit_dpitch_step = conversion_factor * self.planefit_dpitch_step,
96+
)
97+
2698
# (Currently we disable pylint E1121 because it is a temporary issue
2799
# which should be resolved by #165 and #166)
28100
def virtual_fit( # pylint: disable=E1121
@@ -49,7 +121,7 @@ def virtual_fit( # pylint: disable=E1121
49121
volume_array: A 3D volume MRI
50122
volume_affine_RAS: A 4x4 affine transform that maps `volume_array` into RAS space with certain units
51123
target_RAS: A 3D point, in the coordinates and units of `volume_affine_RAS`
52-
pitch_range: Range of pitches to include in the transducer fitting search grid, in degrees
124+
pitch_range:
53125
pitch_step: Pitch step size when forming the transducer fitting search grid, in degrees
54126
yaw_range: Range of yaws to include in the transducer fitting search grid, in degrees
55127
yaw_step: Yaw step size when forming the transducer fitting search grid, in degrees
@@ -68,16 +140,7 @@ def virtual_fit( # pylint: disable=E1121
68140
In spatial units of `volume_affine_RAS`.
69141
70142
Returns: A list of transducer transform candidates sorted starting from the best-scoring one. The transforms map transducer space
71-
into LPS space, and they are in the same units as `volume_affine_RAS`.
72-
73-
The terms 'pitch' and 'yaw' used here refer to the following target-centric angular coordinates in patient space:
74-
pitch: The angle between the anterior axis through the target and the ray from from the target to the projection of
75-
a given point into the anterior-superior plane.
76-
yaw: The angle between the anterior-superior plane through the target and the ray from the target to a given point.
77-
78-
Another way to describe them in terms of standard spherical coordinates centered at the target in ASL (anterior-superior-left) space:
79-
pitch: The azimuthal spherical coordinate.
80-
yaw: 90 degrees minus the polar spherical coordinate.
143+
into LPS space, and they are in the same units as the RAS space of `volume_affine_RAS`.
81144
"""
82145

83146
log.info("Computing foreground mask...")

tests/test_virtual_fit.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from __future__ import annotations
2+
3+
from openlifu.virtual_fit import VirtualFitOptions
4+
5+
6+
def test_unit_conversion():
7+
vfo = VirtualFitOptions(
8+
units="cm",
9+
transducer_steering_center_distance=45.,
10+
pitch_range=(-12,14),
11+
yaw_range=(-13,15),
12+
pitch_step = 9,
13+
yaw_step = 10,
14+
planefit_dyaw_extent = 2.3,
15+
steering_limits=((-10,11),(-12,13),(-14,15)),
16+
)
17+
vfo_converted = vfo.to_units("mm")
18+
assert vfo_converted.transducer_steering_center_distance == 10*vfo.transducer_steering_center_distance
19+
assert vfo_converted.planefit_dyaw_extent == 10*vfo.planefit_dyaw_extent
20+
assert vfo_converted.yaw_step == vfo.yaw_step
21+
assert vfo_converted.pitch_range == vfo.pitch_range
22+
assert isinstance(vfo_converted.steering_limits, tuple)
23+
assert all(isinstance(sl, tuple) for sl in vfo_converted.steering_limits)
24+
assert vfo_converted.steering_limits[2][0] == 10*vfo.steering_limits[2][0]

0 commit comments

Comments
 (0)