Source code for pylidc.utils

import numpy as np
from .Annotation import Annotation
import matplotlib.pyplot as plt
from skimage.measure import find_contours
from matplotlib.widgets import Slider

[docs]def consensus(anns, clevel=0.5, pad=None, ret_masks=True, verbose=True): """Return the boolean-valued consensus volume amongst the provided annotations (`anns`) at a particular consensus level (`clevel`). Parameters ---------- anns: list of `pylidc.Annotation` objects This list should be probably be one of the lists returned by the `pylidc.Scan.cluster_annotations` routine. clevel: float, default=0.5 The consensus fraction. For example, if clevel=0.5, then a voxel will have value 1 in the returned boolean volume when >= 50% of the segmentations include that voxel, and 0 otherwise. pad: int, list, or float, default=None See `Annotation.bbox` for description for this argument. ret_masks: bool, default=True If True, a list of masks is also returned corresponding to all the annotations. Note that this slightly different than calling `boolean_mask` on each respective Annotation object because these volumes will be the same shape and in a common reference frame. verbose: bool, default=True Turns the DICOM image loading message on/off. Returns ------- consensus_mask, consensus_bbox[, masks]: (ndarray, tuple[, list]) `consensus_mask` is the boolean-valued volume of the annotation masks at `clevel` consensus. `consensus_bbox` is a 3-tuple of slices that can be used to index into the image volume at the corresponding location of `consensus_mask`. `masks` is a list of boolean-valued mask volumes corresponding to each Annotation object. Each mask in the `masks` list has the same shape and is sampled in the common reference frame provided by `consensus_bbox`. """ bmats = np.array([a.bbox_matrix(pad=pad) for a in anns]) imin,jmin,kmin = bmats[:,:,0].min(axis=0) imax,jmax,kmax = bmats[:,:,1].max(axis=0) # consensus_bbox cbbox = np.array([[imin,imax], [jmin,jmax], [kmin,kmax]]) masks = [a.boolean_mask(bbox=cbbox) for a in anns] cmask = np.mean(masks, axis=0) >= clevel cbbox = tuple(slice(cb[0], cb[1]+1, None) for cb in cbbox) if ret_masks: return cmask, cbbox, masks else: return cmask, cbbox
[docs]def volume_viewer(vol, mask=None, **line_kwargs): """ Interactive volume viewer utility Parameters ---------- vol: ndarray, ndim=3 An image volume. mask: ndarray, ndim=3, dtype=bool A boolean mask volume. line_kwargs: args Any keyword arguments that can be passed to `matplotlib.pyplot.plot`. Example ------- An example:: import pylidc as pl from pylidc.utils import volume_viewer ann = pl.query(pl.Annotation).first() vol = ann.scan.to_volume() padding = 70.0 mask = ann.boolean_mask(pad=padding) bbox = ann.bbox(pad=padding) volume_viewer(vol[bbox], mask, ls='-', lw=2, c='r') """ assert vol.ndim if mask is not None: if mask.dtype != bool: raise TypeError("mask was not bool type.") if vol.shape != mask.shape: raise ValueError("Shape mismatch between image volume and mask.") k = int(0.5*vol.shape[2]) fig,ax = plt.subplots() plt.subplots_adjust(left=0, bottom=0.25) img = ax.imshow(vol[:,:,k], vmin=vol.min(), vmax=vol.max(), ax.axis('off') if mask is not None: contours = [] for i in range(vol.shape[2]): contour = [] for c in find_contours(mask[:,:,i].astype(np.float), 0.5): line = ax.plot(c[:,1], c[:,0], **line_kwargs)[0] line.set_visible(0) contour.append(line) contours.append(contour) axslice = plt.axes([0.1, 0.1, 0.75, 0.03], facecolor='0.8') sslice = Slider(axslice, 'Slice', 0, vol.shape[2]-1, valinit=k, valstep=1) def update(i): i = int(i) img.set_data(vol[:,:,i]) if mask is not None: for ic,contour in enumerate(contours): for c in contours[ic]: c.set_visible(ic == i) fig.canvas.draw_idle() sslice.on_changed(update) update(k)