Пример #1
0
    def set_plotting_defaults(self):
        """Set default matplotlib plotting options for main image, dose field,
        and jacobian determinant."""

        Image.set_plotting_defaults(self)
        self.dose_kwargs = {"cmap": "jet", "alpha": 0.5, "vmin": None, "vmax": None}
        self.jacobian_kwargs = {
            "cmap": "seismic",
            "alpha": 0.5,
            "vmin": 0.8,
            "vmax": 1.2,
        }
Пример #2
0
    def __init__(self, nii, spacing=_default_spacing, plot_type="grid", **kwargs):
        """Load deformation field.

        Parameters
        ----------
        nii : str/array/nifti
            Source of the image data to load. This can be either:
                (a) The path to a NIfTI file;
                (b) A nibabel.nifti1.Nifti1Image object;
                (c) The path to a file containing a NumPy array;
                (d) A NumPy array.
        """

        Image.__init__(self, nii, **kwargs)
        if not self.valid:
            return
        if self.data.ndim != 5:
            raise RuntimeError(
                f"Deformation field in {nii} must contain a " "five-dimensional array!"
            )
        self.data = self.data[:, :, :, 0, :]
        self.set_spacing(spacing)
Пример #3
0
    def load_to(self, nii, attr, kwargs):
        """Load image data into a class attribute."""

        # Load single image
        rescale = "dose" if attr == "dose" else True
        if not isinstance(nii, dict):
            data = Image(nii, rescale=rescale, **kwargs)
            data.match_size(self, 0)
            valid = data.valid
        else:
            data = {view: Image(nii[view], rescale=rescale, **kwargs) for view in nii}
            for view in _orient:
                if view not in data or not data[view].valid:
                    data[view] = None
                else:
                    data[view].match_size(self, 0)
            valid = any([d.valid for d in data.values() if d is not None])

        setattr(self, attr, data)
        setattr(self, f"has_{attr}", valid)
        setattr(self, f"{attr}_dict", isinstance(nii, dict))
Пример #4
0
    def plot(
        self,
        view,
        sl=None,
        pos=None,
        ax=None,
        gs=None,
        figsize=None,
        zoom=None,
        zoom_centre=None,
        mpl_kwargs=None,
        show=True,
        colorbar=False,
        colorbar_label="HU",
        struct_kwargs=None,
        struct_plot_type=None,
        major_ticks=None,
        minor_ticks=None,
        ticks_all_sides=False,
        **kwargs,
    ):
        """Plot MultiImage and orthogonal view of main image and structs."""

        self.set_axes(view, ax, gs, figsize, zoom, colorbar)

        # Plot the MultiImage
        MultiImage.plot(
            self,
            view,
            sl=sl,
            pos=pos,
            ax=self.ax,
            zoom=zoom,
            zoom_centre=zoom_centre,
            colorbar=colorbar,
            show=False,
            colorbar_label=colorbar_label,
            mpl_kwargs=mpl_kwargs,
            struct_kwargs=struct_kwargs,
            struct_plot_type=struct_plot_type,
            major_ticks=major_ticks,
            minor_ticks=minor_ticks,
            ticks_all_sides=ticks_all_sides,
            **kwargs,
        )

        # Plot orthogonal view
        orthog_view = _orthog[view]
        orthog_sl = self.orthog_slices[_slider_axes[orthog_view]]
        Image.plot(
            self,
            orthog_view,
            sl=orthog_sl,
            ax=self.orthog_ax,
            mpl_kwargs=mpl_kwargs,
            show=False,
            colorbar=False,
            no_ylabel=True,
            no_title=True,
            major_ticks=major_ticks,
            minor_ticks=minor_ticks,
            ticks_all_sides=ticks_all_sides,
        )

        # Plot structures on orthogonal image
        for struct in self.structs:
            if not struct.visible:
                continue
            plot_type = struct_plot_type
            if plot_type == "centroid":
                plot_type = "contour"
            elif plot_type == "filled centroid":
                plot_type = "filled"
            struct.plot(
                orthog_view,
                sl=orthog_sl,
                ax=self.orthog_ax,
                mpl_kwargs=struct_kwargs,
                plot_type=plot_type,
                no_title=True,
            )

        # Plot indicator line
        pos = sl if not self.scale_in_mm else self.slice_to_pos(sl, _slider_axes[view])
        if view == "x-y":
            full_y = (
                self.extent[orthog_view][2:]
                if self.scale_in_mm
                else [0.5, self.n_voxels[_plot_axes[orthog_view][1]] + 0.5]
            )
            self.orthog_ax.plot([pos, pos], full_y, "r")
        else:
            full_x = (
                self.extent[orthog_view][:2]
                if self.scale_in_mm
                else [0.5, self.n_voxels[_plot_axes[orthog_view][0]] + 0.5]
            )
            self.orthog_ax.plot(full_x, [pos, pos], "r")

        if show:
            plt.tight_layout()
            plt.show()
Пример #5
0
    def plot(
        self,
        view="x-y",
        sl=None,
        pos=None,
        ax=None,
        gs=None,
        figsize=None,
        zoom=None,
        zoom_centre=None,
        mpl_kwargs=None,
        n_date=1,
        show=True,
        colorbar=False,
        colorbar_label="HU",
        dose_kwargs=None,
        masked=False,
        invert_mask=False,
        mask_color="black",
        jacobian_kwargs=None,
        df_kwargs=None,
        df_plot_type="grid",
        df_spacing=30,
        struct_kwargs=None,
        struct_plot_type="contour",
        struct_legend=True,
        legend_loc="lower left",
        struct_plot_grouping=None,
        struct_to_plot=None,
        annotate_slice=None,
        major_ticks=None,
        minor_ticks=None,
        ticks_all_sides=False,
    ):
        """Plot a 2D slice of this image and all extra features.

        Parameters
        ----------
        view : str
            Orientation in which to plot ("x-y"/"y-z"/"x-z").

        sl : int, default=None
            Slice number. If <sl> and <pos> are both None, the middle slice
            will be plotted.

        pos : float, default=None
            Position in mm of the slice to plot (will be rounded to the nearest
            slice). If <sl> and <pos> are both None, the middle slice will be
            plotted. If <sl> and <pos> are both given, <sl> supercedes <pos>.

        ax : matplotlib.pyplot.Axes, default=None
            Axes on which to plot. If None, new axes will be created.

        gs : matplotlib.gridspec.GridSpec, default=None
            If not None and <ax> is None, new axes will be created on the
            current matplotlib figure with this gridspec.

        figsize : float, default=None
            Figure height in inches; only used if <ax> and <gs> are None. If
            None, the value in _default_figsize will be used.

        zoom : int/float/tuple, default=None
            Factor by which to zoom in. If a single int or float is given,
            the same zoom factor will be applied in all directions. If a tuple
            of three values is given, these will be used as the zoom factors
            in each direction in the order (x, y, z). If None, the image will
            not be zoomed in.

        mpl_kwargs : dict, default=None
            Dictionary of keyword arguments to pass to matplotlib.imshow() for
            the main image.

        show : bool, default=True
            If True, the plotted figure will be shown via
            matplotlib.pyplot.show().

        colorbar : bool, default=True
            If True, a colorbar will be drawn alongside the plot.

        dose_kwargs : dict, default=None
            Dictionary of keyword arguments to pass to matplotlib.imshow() for
            the dose field.

        masked : bool, default=False
            If True and this object has attribute self.data_mask assigned,
            the image will be masked with the array in self.data_mask.

        invert_mask : bool, default=True
            If True and a mask is applied, the mask will be inverted.

        mask_color : matplotlib color, default="black"
            color in which to plot masked areas.

        mask_threshold : float, default=0.5
            Threshold on mask array; voxels with values below this threshold
            will be masked (or values above, if <invert_mask> is True).

        jacobian_kwargs : dict, default=None
            Dictionary of keyword arguments to pass to matplotlib.imshow() for
            the jacobian determinant.

        df_kwargs : dict, default=None
            Dictionary of keyword arguments to pass to matplotlib.imshow() for
            the deformation field.

        df_plot_type : str, default="grid"
            Type of plot ("grid"/"quiver") to produce for the deformation
            field.

        df_spacing : int/float/tuple, default=30
            Grid spacing for the deformation field plot. If self.scale_in_mm is
            true, the spacing will be in mm; otherwise in voxels. Can be either
            a single value for all directions, or a tuple of values for
            each direction in order (x, y, z).

        struct_kwargs : dict, default=None
            Dictionary of keyword arguments to pass to matplotlib for structure
            plotting.

        struct_plot_type : str, default="contour"
            Plot type for structures ("contour"/"mask"/"filled")

        struct_legend : bool, default=True
            If True, a legend will be drawn labelling any structrues visible on
            this slice.

        legend_loc : str, default='lower left'
            Position for the structure legend, if used.

        annotate_slice : str, default=None
            Color for annotation of slice number. If None, no annotation will
            be added. If True, the default color (white) will be used.
        """

        # Set date
        if self.timeseries:
            self.set_date(n_date)

        # Plot image
        self.set_ax(view, ax, gs, figsize, zoom, colorbar)
        Image.plot(
            self,
            view,
            sl,
            pos,
            ax=self.ax,
            mpl_kwargs=mpl_kwargs,
            show=False,
            colorbar=colorbar,
            colorbar_label=colorbar_label,
            masked=masked,
            invert_mask=invert_mask,
            mask_color=mask_color,
            figsize=figsize,
            major_ticks=major_ticks,
            minor_ticks=minor_ticks,
            ticks_all_sides=ticks_all_sides,
        )

        # Plot dose field
        self.dose.plot(
            view,
            self.sl,
            ax=self.ax,
            mpl_kwargs=self.get_kwargs(dose_kwargs, default=self.dose_kwargs),
            show=False,
            masked=masked,
            invert_mask=invert_mask,
            mask_color=mask_color,
            colorbar=colorbar,
            colorbar_label="Dose (Gy)",
        )

        # Plot jacobian
        self.jacobian.plot(
            view,
            self.sl,
            ax=self.ax,
            mpl_kwargs=self.get_kwargs(jacobian_kwargs, default=self.jacobian_kwargs),
            show=False,
            colorbar=colorbar,
            colorbar_label="Jacobian determinant",
        )

        # Plot standalone structures and comparisons
        for s in self.standalone_structs:
            s.plot(
                view,
                self.sl,
                ax=self.ax,
                mpl_kwargs=struct_kwargs,
                plot_type=struct_plot_type,
            )
        for s in self.struct_comparisons:
            if struct_plot_grouping == "group others":
                if s.s1.name_unique != struct_to_plot:
                    continue
            s.plot(
                view,
                self.sl,
                ax=self.ax,
                mpl_kwargs=struct_kwargs,
                plot_type=struct_plot_type,
                plot_grouping=struct_plot_grouping,
            )

        # Plot deformation field
        self.df.plot(
            view,
            self.sl,
            ax=self.ax,
            mpl_kwargs=df_kwargs,
            plot_type=df_plot_type,
            spacing=df_spacing,
        )

        # Draw structure legend
        if struct_legend and struct_plot_type != "none":
            handles = []
            for s in self.structs:
                if struct_plot_grouping == "group others":
                    if s.name_unique == struct_to_plot:
                        handles.append(mpatches.Patch(color=s.color, label=s.name_nice))
                        handles.append(mpatches.Patch(color="white", label="Others"))
                elif s.visible and s.on_slice(view, self.sl):
                    handles.append(mpatches.Patch(color=s.color, label=s.name_nice))
            if len(handles):
                self.ax.legend(
                    handles=handles, loc=legend_loc, facecolor="white", framealpha=1
                )

        self.adjust_ax(view, zoom, zoom_centre)
        self.label_ax(view, annotate_slice=annotate_slice)

        # Display image
        if show:
            plt.tight_layout()
            plt.show()
Пример #6
0
    def get_relative_width(self, view, zoom=None, colorbar=False, figsize=None):
        """Get the relative width for this plot, including all colorbars."""

        return Image.get_relative_width(
            self, view, zoom, self.get_n_colorbars(colorbar), figsize
        )
Пример #7
0
    def __init__(
        self,
        nii=None,
        dose=None,
        mask=None,
        jacobian=None,
        df=None,
        structs=None,
        multi_structs=None,
        timeseries=None,
        struct_colors=None,
        structs_as_mask=False,
        struct_names=None,
        compare_structs=False,
        comp_type="auto",
        ignore_empty_structs=False,
        ignore_unpaired_structs=False,
        structs_to_keep=None,
        structs_to_ignore=None,
        autoload_structs=True,
        mask_threshold=0.5,
        **kwargs,
    ):
        """Load a MultiImage object.

        Parameters
        ----------
        nii : str/nifti/array
            Path to a .nii/.npy file, or an nibabel nifti object/numpy array.

        title : str, default=None
            Title for this image when plotted. If None and <nii> is loaded from
            a file, the filename will be used.

        dose : str/nifti/array, default=None
            Path or object from which to load dose field.

        mask : str/nifti/array, default=None
            Path or object from which to load mask array.

        jacobian : str/nifti/array, default=None
            Path or object from which to load jacobian determinant field.

        df : str/nifti/array, default=None
            Path or object from which to load deformation field.

        structs : str/list, default=None
            A string containing a path, directory, or wildcard pointing to
            nifti file(s) containing structure(s). Can also be a list of
            paths/directories/wildcards.

        struct_colors : dict, default=None
            Custom colors to use for structures. Dictionary keys can be a
            structure name or a wildcard matching structure name(s). Values
            should be any valid matplotlib color.

        structs_as_mask : bool, default=False
            If True, structures will be used as masks.

        struct_names : list/dict, default=None
            For multi_structs, this parameter will be used to name
            the structures. Can either be a list (i.e. the first structure in
            the file will be given the first name in the list and so on), or a
            dict of numbers and names (e.g. {1: "first structure"} etc).

        compare_structs : bool, default=False
            If True, structures will be paired together into comparisons.

        mask_threshold : float, default=0.5
            Threshold on mask array; voxels with values below this threshold
            will be masked (or values above, if <invert_mask> is True).
        """

        # Flags for image type
        self.dose_as_im = False
        self.dose_comp = False
        self.timeseries = False

        # Load the scan image
        if nii is not None:
            Image.__init__(self, nii, **kwargs)
            self.timeseries = False

        # Load a dose field only
        elif dose is not None and timeseries is None:
            self.dose_as_im = True
            Image.__init__(self, dose, **kwargs)
            dose = None

        # Load a timeseries of images
        elif timeseries is not None:
            self.timeseries = True
            dates = self.get_date_dict(timeseries)
            self.dates = list(dates.keys())
            if "title" in kwargs:
                kwargs.pop("title")
            self.ims = {
                date: Image(file, title=date, **kwargs) for date, file in dates.items()
            }
            Image.__init__(self, dates[self.dates[0]], title=self.dates[0], **kwargs)
            self.date = self.dates[0]
        else:
            raise TypeError("Must provide either <nii>, <dose>, or " "<timeseries!>")
        if not self.valid:
            return

        # Load extra overlays
        self.load_to(dose, "dose", kwargs)
        self.load_to(mask, "mask", kwargs)
        self.load_to(jacobian, "jacobian", kwargs)
        self.load_df(df)

        # Load structs
        self.comp_type = comp_type
        self.load_structs(
            structs,
            multi_structs,
            names=struct_names,
            colors=struct_colors,
            compare_structs=compare_structs,
            ignore_empty=ignore_empty_structs,
            ignore_unpaired=ignore_unpaired_structs,
            comp_type=comp_type,
            to_keep=structs_to_keep,
            to_ignore=structs_to_ignore,
            autoload=autoload_structs
        )

        # Mask settings
        self.structs_as_mask = structs_as_mask
        if self.has_structs and structs_as_mask:
            self.has_mask = True
        self.mask_threshold = mask_threshold
        self.set_masks()
Пример #8
0
def test_nifti_image_zoom():
    im = Image(arr)
    im.plot(zoom=(1, 2, 3), show=False)
    assert im.valid
Пример #9
0
def test_nifti_image_plot():
    im = Image(arr)
    im.plot("x-y", 25, show=False, colorbar=True)
Пример #10
0
def test_nifti_image_downsample():
    ds = 10
    im = Image(arr, downsample=ds)
    assert im.valid
    assert im.n_voxels["z"] <= shape[2] / (ds - 1)
Пример #11
0
def test_nifti_image_from_file():
    im = Image(arr_file)
    assert im.valid
    assert im.n_voxels["y"] == shape[1]
    assert im.title == "array.npy"
Пример #12
0
def test_nifti_image_from_array():
    t = "test title"
    im = Image(arr, title=t)
    assert im.valid
    assert im.n_voxels["x"] == shape[0]
    assert im.title == t
Пример #13
0
    def __init__(
        self,
        shape,
        filename=None,
        origin=(0, 0, 0),
        voxel_sizes=(1, 1, 1),
        intensity=-1024,
        noise_std=None,
    ):
        """Create data to write to a NIfTI file, initially containing a
        blank image array.

        Parameters
        ----------
        shape : int/tuple
            Dimensions of the image array to create. If an int is given, the
            image will be created with dimensions (shape, shape, shape).

        filename : str, default=None
            Name of output NIfTI file. If given, the NIfTI file will
            automatically be written; otherwise, no file will be written until
            the "write" method is called.

        origin : tuple, default=(0, 0, 0)
            Origin in mm for the image.

        voxel_sizes : tuple, default=(1, 1, 1)
            Voxel sizes in mm for the image.

        intensity : float, default=-1000
            Intensity in HU for the background of the image.

        noise_std : float, default=None
            Standard deviation of Gaussian noise to apply to the image.
            If None, no noise will be applied.
        """

        # Create image properties
        self.shape = make_three(shape)
        voxel_sizes = [abs(v) for v in make_three(voxel_sizes)]
        self.affine = np.array(
            [
                [
                    -voxel_sizes[0],
                    0,
                    0,
                    origin[0] + (self.shape[0] - 1) * voxel_sizes[0],
                ],
                [0, voxel_sizes[1], 0, origin[1]],
                [0, 0, voxel_sizes[2], origin[2]],
                [0, 0, 0, 1],
            ]
        )
        self.max_hu = 0 if noise_std is None else noise_std * 3
        self.min_hu = -self.max_hu if self.max_hu != 0 else -20
        self.noise_std = noise_std
        self.bg_intensity = intensity
        self.background = self.make_background()
        self.shapes = []
        self.structs = []
        self.groups = {}
        self.shape_count = {}
        self.translation = None
        self.rotation = None

        # Initialise as Image
        Image.__init__(self, self.background, affine=self.affine)

        # Write to file if a filename is given
        if filename is not None:
            self.filename = os.path.expanduser(filename)
            self.write()