Skip to content

Commit

Permalink
Merged in refactor/metrics_new_module (pull request #311)
Browse files Browse the repository at this point in the history
Refactor/metrics new module

Approved-by: Randy Taylor
  • Loading branch information
jrkerns committed Dec 5, 2023
2 parents f27717f + 6244764 commit a7ffe93
Show file tree
Hide file tree
Showing 17 changed files with 1,307 additions and 1,250 deletions.
8 changes: 8 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ Winston-Lutz
Metrics
^^^^^^^

* There is a new ``metrics`` module in pylinac. Existing metrics have been moved into this module.

E.g. instead of ``from pylinac.core.metrics import SizedDiskLocator`` you would now do ``from pylinac.metrics.image import SizedDiskLocator``.
Image-based metrics are now under ``pylinac.metrics.image``. Profile-based metrics are now under ``pylinac.metrics.profile``.
Individual feature detection functions are now under ``pylinac.metrics.features``.

For backward compatibility (even though metrics are relatively new feature), the old import locations will still work
but will raise a deprecation warning.
* The documentation for metrics has been updated considerably. See :ref:`image-metrics`.
* The detection algorithm for disk/field metrics has been written out; see :ref:`image_metric_algorithm`.
* The ``DiskLocator`` class was renamed to ``SizedDiskLocator``.
Expand Down
78 changes: 39 additions & 39 deletions docs/source/topics/image_metrics.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _image-metrics:

Custom Image Metrics
====================
Images & 2D Metrics
===================

.. versionadded:: 3.16

Expand All @@ -21,23 +21,23 @@ Use Cases
Tool Legend
-----------

+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Use Case | Constraint | Class |
+=========================================================+=====================================================+====================================================================+
| Find the location of a BB in the image | The BB size and location is known approximately | :class:`~pylinac.core.metrics.SizedDiskLocator` |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Find the ROI properties of a BB in the image | The BB size and location is known approximately | :class:`~pylinac.core.metrics.SizedDiskRegion` |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Find the location of *N* BBs in the image | The BB size is known approximately | :class:`~pylinac.core.metrics.GlobalSizedDiskLocator` |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Find the location of a square field in an image | The field size is known approximately | :class:`~pylinac.core.metrics.GlobalSizedFieldLocator` |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Find the locations of *N* square fields in an image | The field size is not known | :class:`~pylinac.core.metrics.GlobalFieldLocator` |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Find the location of a circular field in an image | The field size and location are known approximately | :class:`~pylinac.core.metrics.SizedDiskLocator` (``invert=False``) |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
| Find the ROI properties of a circular field in an image | The field size and location are known approximately | :class:`~pylinac.core.metrics.SizedDiskRegion` (``invert=False``) |
+---------------------------------------------------------+-----------------------------------------------------+--------------------------------------------------------------------+
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Use Case | Constraint | Class |
+=========================================================+=====================================================+=====================================================================+
| Find the location of a BB in the image | The BB size and location is known approximately | :class:`~pylinac.metrics.image.SizedDiskLocator` |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Find the ROI properties of a BB in the image | The BB size and location is known approximately | :class:`~pylinac.metrics.image.SizedDiskRegion` |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Find the location of *N* BBs in the image | The BB size is known approximately | :class:`~pylinac.metrics.image.GlobalSizedDiskLocator` |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Find the location of a square field in an image | The field size is known approximately | :class:`~pylinac.metrics.image.GlobalSizedFieldLocator` |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Find the locations of *N* square fields in an image | The field size is not known | :class:`~pylinac.metrics.image.GlobalFieldLocator` |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Find the location of a circular field in an image | The field size and location are known approximately | :class:`~pylinac.metrics.image.SizedDiskLocator` (``invert=False``) |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+
| Find the ROI properties of a circular field in an image | The field size and location are known approximately | :class:`~pylinac.metrics.image.SizedDiskRegion` (``invert=False``) |
+---------------------------------------------------------+-----------------------------------------------------+---------------------------------------------------------------------+

Basic Usage
-----------
Expand All @@ -47,7 +47,7 @@ To calculate metrics on an image, simply pass the metric(s) to the ``compute`` m
.. code-block:: python
from pylinac.core.image import DicomImage
from pylinac.core.metrics import DiskLocator, DiskRegion
from pylinac.metrics.image import DiskLocator, DiskRegion
img = DicomImage("my_image.dcm")
metric = img.compute(
Expand All @@ -65,7 +65,7 @@ You may compute multiple metrics by passing a list of metrics:
.. code-block:: python
from pylinac.core.image import DicomImage
from pylinac.core.metrics import DiskLocator, DiskRegion
from pylinac.metrics.image import DiskLocator, DiskRegion
img = DicomImage("my_image.dcm")
metrics = img.compute(
Expand Down Expand Up @@ -93,7 +93,7 @@ Metrics might have something to plot on the image. If so, the ``plot`` method of
.. code-block:: python
from pylinac.core.image import DicomImage
from pylinac.core.metrics import DiskLocator, DiskRegion
from pylinac.metrics.image import DiskLocator, DiskRegion
img = DicomImage("my_image.dcm")
metrics = img.compute(
Expand Down Expand Up @@ -127,13 +127,13 @@ Sized Disk Locator
The values provided below are in pixels. The following sections show how variants of how to use the metrics
using physical units and relative to the center of the image.

Here's an example of using the :class:`~pylinac.core.metrics.SizedDiskLocator`:
Here's an example of using the :class:`~pylinac.metrics.image.SizedDiskLocator`:

.. code-block:: python
:caption: Search for a disk 100 pixels right and 100 pixels down from the top left of the image
from pylinac.core.image import DicomImage
from pylinac.core.metrics import DiskLocator, DiskRegion
from pylinac.metrics.image import DiskLocator, DiskRegion
img = DicomImage("my_image.dcm")
img.compute(
Expand Down Expand Up @@ -163,7 +163,7 @@ To perform the same Disk/BB location using mm instead of pixels:
:caption: Search for a disk 30mm right and 30mm down from the top left of the image
from pylinac.core.image import DicomImage
from pylinac.core.metrics import DiskLocator, DiskRegion
from pylinac.metrics.image import DiskLocator, DiskRegion
img = DicomImage("my_image.dcm")
img.compute(
Expand Down Expand Up @@ -195,7 +195,7 @@ This will look for the disk/BB 30 pixels right and 30 pixels down from the cente
:caption: Relative to center using pixels
from pylinac.core.image import DicomImage
from pylinac.core.metrics import DiskLocator, DiskRegion
from pylinac.metrics.image import DiskLocator, DiskRegion
img = DicomImage("my_image.dcm")
img.compute(
Expand Down Expand Up @@ -233,18 +233,18 @@ Sized Disk Region
^^^^^^^^^^^^^^^^^
The :class:`~pylinac.core.metrics.SizedDiskRegion` metric is the same as the :class:`~pylinac.core.metrics.SizedDiskLocator`, but instead of returning the location, it returns a
The :class:`~pylinac.metrics.image.SizedDiskRegion` metric is the same as the :class:`~pylinac.metrics.image.SizedDiskLocator`, but instead of returning the location, it returns a
`scikit-image regionprops <https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.regionprops>`__ object that is the region of the disk.
This allows one to then calculate things like the weighted centroid, area, etc.
It also supports the same class methods as the :class:`~pylinac.core.metrics.SizedDiskLocator` metric.
It also supports the same class methods as the :class:`~pylinac.metrics.image.SizedDiskLocator` metric.
Global Sized Disk Locator
^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 3.17
The :class:`~pylinac.core.metrics.GlobalSizedDiskLocator` metric is similar to the :class:`~pylinac.core.metrics.SizedDiskLocator` metric
The :class:`~pylinac.metrics.image.GlobalSizedDiskLocator` metric is similar to the :class:`~pylinac.metrics.image.SizedDiskLocator` metric
except that it searches the entire image for disks/BB, not just a small window. This is useful for finding the BB in images
where the BB is not in the expected location or unknown. This is also efficient for finding BBs in images,
even if the locations are known.
Expand All @@ -254,7 +254,7 @@ For example, here is an example analysis of an MPC image:
.. code-block:: python
from pylinac.core.image import XIM
from pylinac.core.metrics import GlobalDiskLocator
from pylinac.metrics.image import GlobalDiskLocator
img = XIM("my_image.xim")
bbs = img.compute(
Expand All @@ -279,7 +279,7 @@ Global Sized Field Locator
.. versionadded:: 3.17
The :class:`~pylinac.core.metrics.GlobalSizedFieldLocator` metric is similar to the :class:`~pylinac.core.metrics.GlobalSizedDiskLocator` metric.
The :class:`~pylinac.metrics.image.GlobalSizedFieldLocator` metric is similar to the :class:`~pylinac.metrics.image.GlobalSizedDiskLocator` metric.
This is useful for finding one or more fields in images
where the field is not in the expected location or unknown. This is also efficient when multiple fields are present in the image.
Expand Down Expand Up @@ -371,7 +371,7 @@ as well as reuse them where needed.
To write a custom plugin, you must
* Inherit from the :class:`~pylinac.core.metrics.MetricBase` class
* Inherit from the :class:`~pylinac.metrics.image.MetricBase` class
* Specify a ``name`` attribute.
* Implement the ``calculate`` method.
* (Optional) Implement the ``plot`` method if you want the metric to plot on the image.
Expand All @@ -386,7 +386,7 @@ For example, let's built a simple plugin that finds and plots an "X" at the cent
from pylinac.core.image_generator import AS1000Image, FilteredFieldLayer, GaussianFilterLayer
from pylinac.core.image import DicomImage
from pylinac.core.metrics import MetricBase
from pylinac.metrics.image import MetricBase
class ImageCenterMetric(MetricBase):
name = "Image Center"
Expand Down Expand Up @@ -567,26 +567,26 @@ Here is the plot of the final image with the BB location and threshold boundary
API
---
.. autoclass:: pylinac.core.metrics.MetricBase
.. autoclass:: pylinac.metrics.image.MetricBase
:inherited-members:
:members:
.. autoclass:: pylinac.core.metrics.SizedDiskLocator
.. autoclass:: pylinac.metrics.image.SizedDiskLocator
:inherited-members:
:members:
.. autoclass:: pylinac.core.metrics.SizedDiskRegion
.. autoclass:: pylinac.metrics.image.SizedDiskRegion
:inherited-members:
:members:
.. autoclass:: pylinac.core.metrics.GlobalSizedDiskLocator
.. autoclass:: pylinac.metrics.image.GlobalSizedDiskLocator
:inherited-members:
:members:
.. autoclass:: pylinac.core.metrics.GlobalSizedFieldLocator
.. autoclass:: pylinac.metrics.image.GlobalSizedFieldLocator
:inherited-members:
:members:
.. autoclass:: pylinac.core.metrics.GlobalFieldLocator
.. autoclass:: pylinac.metrics.image.GlobalFieldLocator
:inherited-members:
:members:
20 changes: 10 additions & 10 deletions docs/source/topics/profiles.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _profiles:

Profiles
========
Profiles & 1D Metrics
=====================

Profiles, in the context of pylinac, are 1D arrays of data that
contain a single radiation field. Colloquially, these are what
Expand Down Expand Up @@ -122,7 +122,7 @@ a penumbra plugin could be written that calculates the penumbra of the profile.

Several plugins are provided out of the box, and writing new plugins is straightforward.

Metrics are calculated by passing it as a list to the ``compute`` method.
Metrics are calculated by passing it as a list to the ``calculate`` method.
The examples below show its usage.

.. _profile_builtin_plugins:
Expand Down Expand Up @@ -676,31 +676,31 @@ API
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.PenumbraRightMetric
.. autoclass:: pylinac.metrics.profile.PenumbraRightMetric
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.PenumbraLeftMetric
.. autoclass:: pylinac.metrics.profile.PenumbraLeftMetric
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.SymmetryPointDifferenceMetric
.. autoclass:: pylinac.metrics.profile.SymmetryPointDifferenceMetric
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.SymmetryPointDifferenceQuotientMetric
.. autoclass:: pylinac.metrics.profile.SymmetryPointDifferenceQuotientMetric
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.TopDistanceMetric
.. autoclass:: pylinac.metrics.profile.TopDistanceMetric
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.FlatnessRatioMetric
.. autoclass:: pylinac.metrics.profile.FlatnessRatioMetric
:inherited-members:
:members:

.. autoclass:: pylinac.core.profile.FlatnessDifferenceMetric
.. autoclass:: pylinac.metrics.profile.FlatnessDifferenceMetric
:inherited-members:
:members:

Expand Down
5 changes: 1 addition & 4 deletions docs/source/winston_lutz.rst
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,7 @@ The algorithm works like such:
the threshold is associated with the open field. The pixels are converted to black & white and
the center of mass of the pixels is assumed to be the field CAX.

* **Find the BB** -- The image is converted to binary based on pixel values *both* above the 50% threshold as above,
and below the upper threshold. The upper threshold is an iterative value, starting at the image maximum value,
that is lowered slightly when the BB is not found. If the binary image has a reasonably circular ROI, the BB is
considered found and the pixel-weighted center of mass of the BB is considered the BB location.
* **Find the BB** -- The algorithm for finding the BB can be found here: :ref:`image_metric_algorithm`.

.. note:: Strictly speaking, the following aren't required analyses, but are explained for fullness and clarity.

Expand Down
2 changes: 1 addition & 1 deletion pylinac/core/contrast.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import numpy as np

from pylinac.core.utilities import OptionListMixin
from ..core.utilities import OptionListMixin


class Contrast(OptionListMixin):
Expand Down
2 changes: 1 addition & 1 deletion pylinac/core/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from skimage.draw import disk
from skimage.transform import rotate

from ..metrics.image import MetricBase
from ..settings import PATH_TRUNCATION_LENGTH, get_dicom_cmap
from .array_utils import (
bit_invert,
Expand All @@ -49,7 +50,6 @@
retrieve_dicom_file,
retrieve_filenames,
)
from .metrics import MetricBase
from .profile import stretch as stretcharray
from .scale import wrap360
from .utilities import decode_binary, is_close, simple_round
Expand Down
Loading

0 comments on commit a7ffe93

Please sign in to comment.