Skip to content

Commit

Permalink
Merged in feature/RAM-4186-starshot-angles (pull request #489)
Browse files Browse the repository at this point in the history
RAM-4186 add starshot angles to output

Approved-by: Randy Taylor
  • Loading branch information
jrkerns committed Dec 5, 2024
2 parents 32ed41a + f7cd976 commit 7741174
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 4 deletions.
5 changes: 5 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Image Metrics
* There was a memory bug when computing metrics. This shouldn't affect
one-off computations, but could affect long-running processes. This has been fixed.

Starshot
^^^^^^^^

* :bdg-success:`Feature` Angles of the spokes are now reported via the ``angles`` attribute of ``results_data``. See :ref:`interpreting-starshot-results`.

v 3.29.0
--------

Expand Down
2 changes: 2 additions & 0 deletions docs/source/starshot_docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ section.
* ``circle_radius_mm`` -- The radius of the minimum circle that touches all the star lines in mm.
* ``circle_diameter_mm`` -- The diameter of the minimum circle that touches all the star lines in mm.
* ``circle_center_x_y`` -- The center position of the minimum circle in pixels.
* ``angles`` -- The angles of the spokes in degrees. 0 is pointing straight up. +90 is to the right; -90 is to the left.
Angles are always between +/-90.
* ``passed`` -- Whether the analysis passed or failed.

Troubleshooting
Expand Down
38 changes: 36 additions & 2 deletions pylinac/starshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import copy
import io
import math
import webbrowser
from pathlib import Path
from typing import BinaryIO
Expand Down Expand Up @@ -66,6 +67,10 @@ class StarshotResults(ResultBase):
description="The center position of the minimum circle in pixels.",
title="Circle center pixel (X, Y)",
)
angles: list[float] = Field(
description="The angles of the radiation lines in degrees. The angles are relative to the x-axis and range from +/- 90 degrees.",
title="Radiation line angles (degrees)",
)
passed: bool = Field(description="Whether the analysis passed or failed.")


Expand Down Expand Up @@ -94,6 +99,8 @@ class Starshot(ResultsDataMixin[StarshotResults], QuaacMixin):
>>> mystar.plot_analyzed_image()
"""

angles: list[float]

def __init__(self, filepath: str | BinaryIO, **kwargs):
"""
Parameters
Expand Down Expand Up @@ -284,6 +291,7 @@ def analyze(
self._get_reasonable_wobble(
start_point, fwhm, min_peak_height, radius, recursive, local_max
)
self.angles = calculate_angles(self.lines)

def _get_reasonable_wobble(
self, start_point, fwhm, min_peak_height, radius, recursive, local_max
Expand Down Expand Up @@ -421,6 +429,7 @@ def _generate_results_data(self) -> StarshotResults:
circle_diameter_mm=self.wobble.radius_mm * 2,
circle_radius_mm=self.wobble.radius_mm,
circle_center_x_y=(self.wobble.center.x, self.wobble.center.y),
angles=self.angles,
passed=self.passed,
)

Expand Down Expand Up @@ -471,8 +480,13 @@ def plotly_analyzed_images(
show_colorbar=show_colorbar,
**kwargs,
)
for line in self.lines:
line.plotly(fig, color="blue", showlegend=False)
for idx, line in enumerate(self.lines):
line.plotly(
fig,
color="blue",
showlegend=show_legend,
name=f"Line {idx} ({self.angles[idx]:2.2f}°)",
)
self.wobble.plotly(
fig,
line_color="green",
Expand Down Expand Up @@ -799,3 +813,23 @@ def get_peak_height():

def get_radius():
yield from np.linspace(0.95, 0.1, 10)


def calculate_angles(lines: list[Line]) -> list[float]:
"""Calculate the angles of the starshot spokes. What makes this
somewhat annoying is that the zero-angle is defined as pointing up (vs right for a unit angle)
and that we display the image with the y-axis increasing downward (vs upward)."""
angles = []
for line in lines:
try:
phi_rad = math.atan(line.m)
phi_deg = math.degrees(phi_rad) - 90
# Normalize the angle to be within (-90, +90) degrees
if phi_deg > 90:
phi_deg -= 180
elif phi_deg <= -90:
phi_deg += 180
except ZeroDivisionError:
phi_deg = 90
angles.append(phi_deg)
return angles
20 changes: 18 additions & 2 deletions tests_basic/test_starshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from parameterized import parameterized

from pylinac import Starshot
from pylinac.core.geometry import Point
from pylinac.starshot import StarshotResults
from pylinac.core.geometry import Line, Point
from pylinac.starshot import StarshotResults, calculate_angles
from tests_basic.core.test_utilities import QuaacTestBase, ResultsDataBase
from tests_basic.utils import (
CloudFileMixin,
Expand Down Expand Up @@ -78,6 +78,22 @@ def test_range_of_pixel_values(self, max_val: float):
self.assertLessEqual(star.wobble.diameter_mm, 0.35)
self.assertTrue(star.passed)

@parameterized.expand(
[
((0, 0), (1, 1), -45),
((0, 0), (-1, -1), -45),
((0, 0), (1, 0), 90),
((0, 0), (-1, 0), 90),
((0, 0), (0, 1), 0),
((0, 0), (0, -1), 0),
((0, 0), (1, -1), 45),
((0, 0), (1, -0.5), 26.56),
]
)
def test_calculate_angle(self, point1, point2, angle):
angle = calculate_angles([Line(Point(*point1), Point(*point2))])[0]
self.assertAlmostEqual(angle, angle, places=2)


class TestPlottingSaving(TestCase):
@classmethod
Expand Down

0 comments on commit 7741174

Please sign in to comment.