Skip to content

Commit

Permalink
Merged in refactor/mtmf_wl_speedup (pull request #382)
Browse files Browse the repository at this point in the history
Multi-target multi-field WL speedup

Approved-by: Randy Taylor
  • Loading branch information
jrkerns committed May 2, 2024
2 parents 7ced05e + 8998080 commit 1a5d289
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 19 deletions.
2 changes: 2 additions & 0 deletions bitbucket-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ definitions:
- python -m venv venv
- source venv/bin/activate
- pip install .[dev]
- pip uninstall pylinac -y
- pip freeze
artifacts:
- venv/**
Expand All @@ -45,6 +46,7 @@ definitions:
- "*.rst"
- step: &cbct-tests
name: Run CBCT Tests
size: 2x
script:
- source venv/bin/activate
- pytest tests_basic/test_cbct.py -n 2 --cov=pylinac.cbct --cov-report term --junitxml=./test-reports/pytest_results.xml
Expand Down
11 changes: 11 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,23 @@ Picket Fence
* A new method is available ``plot_leaf_error``. This method will create a figure of the leaf error boxplot. This is
similar to the leaf error subplot that shows up at the right/bottom of the analyzed image, but can be called independently.

Winston-Lutz
^^^^^^^^^^^^

* For multi-target multi-field analysis, the analysis has been sped up considerably. The speedup depends on the
image size and the number of BBs, but overall the speed up is ~2x.
* Calls to WL image's ``plot()`` method now accepts keyword arguments that are passed to the underlying image plot method.
E.g. ``wl_image.plot(vmin=1)``.

Core
^^^^

* Pylinac is meant to be compatible with all Python versions still in security lifecycles, which is currently 3.8.
Some syntax was introduced that was not compatible with Python 3.8. This has been fixed. Note that
Python 3.8 will be EOL in October 2024. The next pylinac release after that will drop support for Python 3.8.
* When computing image metrics, a failed metric analysis would still add the metric to the running list of metrics under
certain conditions such as running image metrics in a try clause.
This could result in errors when trying to plot the metrics. Now, if a metric computation fails, the metric is not added to the list.

v 3.22.0
--------
Expand Down
2 changes: 1 addition & 1 deletion pylinac/core/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,8 @@ def compute(self, metrics: list[MetricBase] | MetricBase) -> Any | dict[str, Any
metrics = [metrics]
for metric in metrics:
metric.inject_image(self)
self.metrics.append(metric)
value = metric.context_calculate()
self.metrics.append(metric)
metric_data[metric.name] = value
# TODO: use |= when 3.9 is min supported version
self.metric_values.update(metric_data)
Expand Down
10 changes: 8 additions & 2 deletions pylinac/ct.py
Original file line number Diff line number Diff line change
Expand Up @@ -1889,8 +1889,14 @@ def find_phantom_axis(self) -> (Callable, Callable):
)
common_idxs = np.intersect1d(x_idxs, y_idxs)
# fit to 1D polynomials; inspiration: https://stackoverflow.com/a/45351484
fit_zx = np.poly1d(np.polyfit(zs[common_idxs], center_xs[common_idxs], deg=1))
fit_zy = np.poly1d(np.polyfit(zs[common_idxs], center_ys[common_idxs], deg=1))
# rcond should be explicitly passed. Started randomly failing in the pipe. v1.14.0 numpy release notes
# say it should be explicitly passed. Value is arbitrary but small and tests pass.
fit_zx = np.poly1d(
np.polyfit(zs[common_idxs], center_xs[common_idxs], deg=1, rcond=0.00001)
)
fit_zy = np.poly1d(
np.polyfit(zs[common_idxs], center_ys[common_idxs], deg=1, rcond=0.00001)
)
return fit_zx, fit_zy

@property
Expand Down
48 changes: 32 additions & 16 deletions pylinac/winston_lutz.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,7 @@
is_solid,
is_symmetric,
)
from .metrics.image import (
GlobalSizedDiskLocator,
GlobalSizedFieldLocator,
SizedDiskLocator,
)
from .metrics.image import GlobalSizedFieldLocator, SizedDiskLocator

BB_ERROR_MESSAGE = (
"Unable to locate the BB. Make sure the field edges do not obscure the BB, that there are no artifacts in the images, that the 'bb_size' parameter is close to reality, "
Expand Down Expand Up @@ -667,6 +663,7 @@ def plot(
clear_fig: bool = False,
zoom: bool = True,
legend: bool = True,
**kwargs,
) -> plt.Axes:
"""Plot an individual WL image.
Expand All @@ -683,7 +680,9 @@ def plot(
legend : bool
Whether to show the legend.
"""
ax = super().plot(ax=ax, show=False, clear_fig=clear_fig, show_metrics=True)
ax = super().plot(
ax=ax, show=False, clear_fig=clear_fig, show_metrics=True, **kwargs
)
# show EPID center
ax.axvline(x=self.epid.x, color="b")
epid_handle = ax.axhline(y=self.epid.y, color="b")
Expand Down Expand Up @@ -2090,17 +2089,34 @@ def find_bb_centroids(
self, bb_diameter_mm: float, low_density: bool
) -> list[Point]:
"""Find the specific BB based on the arrangement rather than a single one. This is in local pixel coordinates"""
# get initial starting conditions
bb_tolerance_mm = self._calculate_bb_tolerance(bb_diameter_mm)
centers = self.compute(
metrics=GlobalSizedDiskLocator(
radius_mm=bb_diameter_mm / 2,
radius_tolerance_mm=bb_tolerance_mm,
invert=not low_density,
detection_conditions=self.detection_conditions,
max_number=len(self.bb_arrangement),
centers = []
for bb in self.bb_arrangement:
bb_diameter_mm = bb.bb_size_mm
bb_tolerance_mm = self._calculate_bb_tolerance(bb_diameter_mm)
left, sup = bb_projection_with_rotation(
offset_left=bb.offset_left_mm,
offset_up=bb.offset_up_mm,
offset_in=bb.offset_in_mm,
gantry=self.gantry_angle,
couch=self.couch_angle,
sad=self.sad,
)
)
try:
new_centers = self.compute(
metrics=SizedDiskLocator.from_center_physical(
expected_position_mm=Point(
x=left, y=-sup
), # we do -sup because sup is in WL coordinate space but the disk locator is in image coordinates.
search_window_mm=(40 + bb_diameter_mm, 40 + bb_diameter_mm),
radius_mm=bb_diameter_mm / 2,
radius_tolerance_mm=bb_tolerance_mm / 2,
invert=not low_density,
detection_conditions=self.detection_conditions,
)
)
centers.extend(new_centers)
except ValueError:
pass
return centers


Expand Down

0 comments on commit 1a5d289

Please sign in to comment.