Skip to content

Commit

Permalink
shift inflection point to profile.py and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
alanphys committed Mar 10, 2021
1 parent 77a9444 commit 1d905d3
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 32 deletions.
13 changes: 11 additions & 2 deletions pylinac/core/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from .geometry import Point, Circle
from .typing import NumberLike
from .hillreg import hill_reg, infl_point_hill_func


def stretch(array: np.ndarray, min: int=0, max: int=1, fill_dtype: Optional[np.dtype]=None) -> np.ndarray:
Expand Down Expand Up @@ -297,6 +298,14 @@ def field_edges(self, ifa: float=1.0, x: int=50, norm='max grounded', interpolat
right = right - 1
return left, right

@argue.bounds(pen_width=(0, 200))
@argue.options(side=('left', 'right'))
def infl_points(self, pen_width: float = 20, side: str = 'left'):
indices, values = self.penumbra_values(side, pen_width)
fit_params = hill_reg(indices, values)
edge_idx = infl_point_hill_func(fit_params)
return edge_idx, fit_params

@argue.bounds(lower=(0, 100), upper=(0, 100))
def penumbra_width(self, lower: int=20, upper: int=80) -> Tuple[float, float]:
"""Return the penumbra width of the profile.
Expand Down Expand Up @@ -342,7 +351,7 @@ def penumbra_values(self, side: str, dist: float=20.0):
if side == 'left':
if dist > 0:
start_idx = int(left_edge_idx - dist*self.dpmm)
end_idx = int(left_edge_idx + dist * self.dpmm)
end_idx = int(left_edge_idx + dist * self.dpmm) + 1
else:
start_idx = 0
end_idx = int(left_edge_idx + (right_edge_idx - left_edge_idx)*0.1)
Expand All @@ -353,7 +362,7 @@ def penumbra_values(self, side: str, dist: float=20.0):
else:
if dist > 0:
start_idx = int(right_edge_idx - dist*self.dpmm) # take profile values around right edge
end_idx = int(right_edge_idx + dist * self.dpmm)
end_idx = int(right_edge_idx + dist * self.dpmm) + 1
else:
start_idx = int(right_edge_idx - (right_edge_idx - left_edge_idx)*0.1)
end_idx = self.values.shape[0]
Expand Down
52 changes: 23 additions & 29 deletions pylinac/fieldparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from .core.profile import SingleProfile
from .core import pdf
from .settings import get_dicom_cmap
from .core.hillreg import hill_reg, hill_func, inv_hill_func, deriv_hill_func, infl_point_hill_func
from .core.hillreg import hill_reg, hill_func, inv_hill_func, deriv_hill_func

interpolate: bool = True
norm: str = 'max grounded' # one of 'cax', 'max', 'cax grounded', 'max grounded'
Expand All @@ -45,18 +45,14 @@ def right_edge_50(profile: SingleProfile, *args):

def left_edge_infl(profile: SingleProfile, *args):
"""Return the position of the inflection point on the left of the profile."""
indices, values = profile.penumbra_values('left', pen_width)
fit_params = hill_reg(indices, values)
left_edge_idx = infl_point_hill_func(fit_params)
left_edge_idx = profile.infl_points(pen_width, 'left')[0]
left_edge = abs(left_edge_idx - profile.center()[0])/profile.dpmm
return left_edge


def right_edge_infl(profile: SingleProfile, *args):
"""Return the position of the inflection point on the right of the profile."""
indices, values = profile.penumbra_values('right', pen_width)
fit_params = hill_reg(indices, values)
right_edge_idx = infl_point_hill_func(fit_params)
right_edge_idx = profile.infl_points(pen_width, 'right')[0]
right_edge = abs(right_edge_idx - profile.center()[0])/profile.dpmm
return right_edge

Expand Down Expand Up @@ -118,12 +114,10 @@ def penumbra_left_infl(profile: SingleProfile, *args):
Returns the distance between the locations where the dose equals 0.4 times the dose at the inflection point
and 1.6 times that dose on the left side of the profile."""
indices, values = profile.penumbra_values('left', pen_width)
fit_params = hill_reg(indices, values)
inf_idx = fit_params[2]
inf_val = hill_func(inf_idx, fit_params[0], fit_params[1], fit_params[2], fit_params[3])
upper_idx = inv_hill_func(inf_val*1.6, fit_params)
lower_idx = inv_hill_func(inf_val*0.4, fit_params)
infl_idx, fit_params = profile.infl_points(pen_width, 'left')
infl_val = hill_func(infl_idx, fit_params[0], fit_params[1], fit_params[2], fit_params[3])
upper_idx = inv_hill_func(infl_val*1.6, fit_params)
lower_idx = inv_hill_func(infl_val*0.4, fit_params)
left_penum = abs(upper_idx - lower_idx)/profile.dpmm
return left_penum

Expand All @@ -133,12 +127,10 @@ def penumbra_right_infl(profile: SingleProfile, *args):
Returns the distance between the locations where the dose equals 0.4 times the dose at the inflection point
and 1.6 times that dose on the right side of the profile."""
indices, values = profile.penumbra_values('right', pen_width)
fit_params = hill_reg(indices, values)
inf_idx = fit_params[2]
inf_val = hill_func(inf_idx, fit_params[0], fit_params[1], fit_params[2], fit_params[3])
upper_idx = inv_hill_func(inf_val*1.6, fit_params)
lower_idx = inv_hill_func(inf_val*0.4, fit_params)
infl_idx, fit_params = profile.infl_points(pen_width, 'right')
infl_val = hill_func(infl_idx, fit_params[0], fit_params[1], fit_params[2], fit_params[3])
upper_idx = inv_hill_func(infl_val*1.6, fit_params)
lower_idx = inv_hill_func(infl_val*0.4, fit_params)
right_penum = abs(upper_idx - lower_idx)/profile.dpmm
return right_penum

Expand All @@ -149,9 +141,7 @@ def penumbra_slope_left_infl(profile: SingleProfile, *args):
cax_val = profile.values.max()
else:
_, cax_val = profile.fwxm_center()
indices, values = profile.penumbra_values('left', pen_width)
fit_params = hill_reg(indices, values)
left_edge_idx = infl_point_hill_func(fit_params)
left_edge_idx, fit_params = profile.infl_points(pen_width, 'left')
inf_slope = 100*deriv_hill_func(left_edge_idx, fit_params)*profile.dpmm/cax_val
return inf_slope

Expand All @@ -162,13 +152,17 @@ def penumbra_slope_right_infl(profile: SingleProfile, *args):
cax_val = profile.values.max()
else:
_, cax_val = profile.fwxm_center()
indices, values = profile.penumbra_values('right', pen_width)
fit_params = hill_reg(indices, values)
right_edge_idx = infl_point_hill_func(fit_params)
right_edge_idx, fit_params = profile.infl_points(pen_width, 'right')
inf_slope = 100*deriv_hill_func(right_edge_idx, fit_params)*profile.dpmm/cax_val
return inf_slope


# Dose point values ----------------------------------------------------------------------------------------------------
def dose_point_left_20(profile: SingleProfile, *args):
left_edge = 0.2*left_edge_infl(profile)*profile.dpmm + profile.center()
return


# Field flatness parameters --------------------------------------------------------------------------------------------
def flatness_dose_difference(profile: SingleProfile, ifa: float=0.8):
"""The Varian specification for calculating flatness."""
Expand Down Expand Up @@ -695,7 +689,7 @@ def _plot_vert(self, axis: plt.Axes=None):
if bool([val for key, val in protocol_params.items() if 'flatness' in key]):
plot_flatness(self.vert_profile, self.infield_area, axis)
if bool([val for key, val in protocol_params.items() if 'inf' in key]):
plot_hill_func(self.vert_profile, axis)
plot_infl(self.vert_profile, axis)
# _remove_ticklabels(axis)

def _plot_horiz(self, axis: plt.Axes=None):
Expand All @@ -711,8 +705,8 @@ def _plot_horiz(self, axis: plt.Axes=None):
protocol_params = PROTOCOLS[protocol]
if bool([val for key, val in protocol_params.items() if 'flatness' in key]):
plot_flatness(self.horiz_profile, self.infield_area, axis)
if bool([val for key, val in protocol_params.items() if 'inf' in key]):
plot_hill_func(self.horiz_profile, axis)
if bool([val for key, val in protocol_params.items() if 'infl' in key]):
plot_infl(self.horiz_profile, axis)
# _remove_ticklabels(axis)

@staticmethod
Expand All @@ -732,7 +726,7 @@ def plot_flatness(profile: SingleProfile, ifa: float=0.8, axis: plt.Axes = None)
axis.axvline(right_ifa, color='g', linestyle='-.')


def plot_hill_func(profile: SingleProfile, axis: plt.Axes = None):
def plot_infl(profile: SingleProfile, axis: plt.Axes = None):
"""Plot the non-linear regression fit against the profile"""

# plot left penumbra
Expand Down
2 changes: 1 addition & 1 deletion tests_basic/test_fieldparams.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def test_penumbra_left_infl(self):
fp.interpolate = True
fp.norm = 'cax'
fp.pen_width = 0
self.assertAlmostEqual(fp.penumbra_left_infl(self.profile), 6.37, delta=self.delta)
self.assertAlmostEqual(fp.penumbra_left_infl(self.profile), 5.91, delta=self.delta)

def test_penumbra_right_infl(self):
fp.interpolate = True
Expand Down

0 comments on commit 1d905d3

Please sign in to comment.