Skip to content

Commit

Permalink
Merged in bugfix/RAM-3049_monotonic_x_values_profiles (pull request #287
Browse files Browse the repository at this point in the history
)

Bugfix RAM-3049 Add x-values checks

Approved-by: Randy Taylor
  • Loading branch information
jrkerns committed Oct 31, 2023
2 parents 76ab9b5 + 08bf9c8 commit ac81386
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 4 deletions.
8 changes: 8 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ ACR
The ``ImagePositionPatient`` tag is now the primary lookup key and ``SliceLocation`` is only used
if the former tag is unavailable.

Profiles
^^^^^^^^

* Passing *decreasing* x-values to ``SingleProfile`` would usually result in an error because the measured
width would be negative. An error will now be raised if the x-values are decreasing.
* The same error as above was also detected in the new ``<FWXM|InflectionDerivative|Hill>Profile`` classes.
Given these classes are the new standard, they have been fully fixed and can now handle decreasing x-values.

v 3.16.0
--------

Expand Down
13 changes: 9 additions & 4 deletions pylinac/core/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,9 @@ def __init__(
self.values = values
if x_values is None:
x_values = np.arange(len(values))
x_diff = np.diff(x_values)
if x_diff.max() > 0 > x_diff.min():
raise ValueError("X values must be monotonically increasing or decreasing")
self.x_values = x_values
if ground:
self.values = utils.ground(values)
Expand Down Expand Up @@ -656,12 +659,12 @@ def field_indices(self, in_field_ratio: float) -> (int, int, int):
"""
left = self.field_edge_idx(side=LEFT)
right = self.field_edge_idx(side=RIGHT)
width = right - left
width = self.field_width_px
f_left = left + (1 - in_field_ratio) / 2 * width
f_right = right - (1 - in_field_ratio) / 2 * width
left = math.ceil(f_left)
right = math.floor(f_right)
width = right - left
width = max(right, left) - min(right, left)
return left, right, width

@cached_property
Expand All @@ -676,7 +679,7 @@ def field_width_px(self) -> float:
"""The field width of the profile in pixels"""
left_idx = self.field_edge_idx(side=LEFT)
right_idx = self.field_edge_idx(side=RIGHT)
return abs(right_idx - left_idx)
return max(right_idx, left_idx) - min(right_idx, left_idx)

def field_values(
self,
Expand All @@ -686,7 +689,7 @@ def field_values(
field width."""
left = self.field_edge_idx(side=LEFT)
right = self.field_edge_idx(side=RIGHT)
width = right - left
width = self.field_width_px
f_left = left + (1 - in_field_ratio) / 2 * width
f_right = right - (1 - in_field_ratio) / 2 * width
# use floor/ceil to be conservatively exclusive of edge values.
Expand Down Expand Up @@ -1290,6 +1293,8 @@ def _interpolate(
"""Fit the data to the passed interpolation method. Will also calculate the new values to correct the measurements such as dpmm"""
if x_values is None:
x_values = np.array(range(len(values)))
if np.diff(x_values).min() < 0:
raise ValueError("Profile values must be monotonically increasing")
if interp_method == Interpolation.NONE:
return values, dpmm, x_values # do nothing
else:
Expand Down
27 changes: 27 additions & 0 deletions tests_basic/core/test_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,28 @@ def test_physical_resample_with_ints_and_small_range_raises_warning(self):


class TestFWXMProfile(TestCase):
def test_x_values_decrease_is_okay(self):
array = create_simple_9_profile()
x_values = np.arange(len(array))[::-1]
f = FWXMProfile(array, x_values=x_values)
# both width and indices should respect the x-values
self.assertEqual(f.field_width_px, 4)
self.assertEqual(f.field_indices(in_field_ratio=0.8), (7, 1, 6))

def test_not_monotonically_increasing_raises_error(self):
array = create_simple_9_profile()
x_values = np.arange(len(array))
x_values[2] = 5
with self.assertRaises(ValueError):
FWXMProfile(array, x_values=x_values)

def test_not_monotonically_decreasing_raises_error(self):
array = create_simple_9_profile()
x_values = np.arange(len(array))[::-1]
x_values[5] = 5
with self.assertRaises(ValueError):
FWXMProfile(array, x_values=x_values)

def test_center_idx(self):
array = create_simple_9_profile()
profile = FWXMProfile(array)
Expand Down Expand Up @@ -933,6 +955,11 @@ def test_normalization(self):
)
self.assertGreaterEqual(p.values.max(), 1.0)

def test_x_values_are_monotonically_increasing(self):
array = np.random.rand(1, 100).squeeze()
with self.assertRaises(ValueError):
SingleProfile(array, x_values=array, interpolation=Interpolation.NONE)

def test_beam_center(self):
# centered field
field = generate_open_field()
Expand Down

0 comments on commit ac81386

Please sign in to comment.