Source code for fsleyes.actions.newimage

#
# newimage.py - The NewImageAction class
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`NewImageAction` class, an :class:`.Action`
which allows the user to create a new empty :class:`.Image` overlay.
"""


import            wx
import wx.grid as wxgrid

import numpy   as np
import nibabel as nib

import fsl.transform.affine      as fslaffine
import fsl.data.constants        as constants
import fsl.data.image            as fslimage
import fsleyes_widgets.floatspin as floatspin
import fsleyes.strings           as strings
from . import                       base


[docs]class NewImageAction(base.Action): """The ``NewImageAction`` class allows the user to create a new :class:`.Image`. When invoked, it displays a :class:`NewImageDialog` prompting the user to select the properties of the new image, and then creates a new image accordingly. """
[docs] def __init__(self, overlayList, displayCtx, frame): """Create a ``CopyOverlayAction``. :arg overlayList: The :class:`.OverlayList`. :arg displayCtx: The :class:`.DisplayContext`. :arg frame: The :class:`.FSLeyesFrame`. """ base.Action.__init__(self, overlayList, displayCtx, self.__newImage) self.__frame = frame
def __newImage(self): """Displays a :class:`NewImageDialog`, then creates a new :class:`.Image`, and adds it to the :class:`.OverlayList`. If the currently selected overlay is a :class:`.Nifti`, the :class:`NewImageDialog` is initialised to the properties of the selected overlay. """ ovl = self.displayCtx.getSelectedOverlay() if ovl is not None and isinstance(ovl, fslimage.Nifti): shape = ovl.shape[:3] pixdim = ovl.pixdim[:3] dtype = ovl.dtype affine = ovl.voxToWorldMat xyzUnits = ovl.xyzUnits timeUnits = ovl.timeUnits # adjust pixdims in case there # are inversions (e.g. l/r flip) pixdim = pixdim * np.sign(fslaffine.decompose(affine)[0]) else: shape = None pixdim = None dtype = None affine = np.eye(4) xyzUnits = None timeUnits = None dlg = NewImageDialog(self.__frame, shape=shape, pixdim=pixdim, affine=affine, dtype=dtype) if dlg.ShowModal() != wx.ID_OK: return img = newImage(dlg.shape, dlg.pixdim, dlg.dtype, dlg.affine, xyzUnits=xyzUnits, timeUnits=timeUnits, name='new') self.overlayList.append(img) self.displayCtx.selectOverlay(img)
[docs]def newImage(shape, pixdim, dtype, affine, xyzUnits=constants.NIFTI_UNITS_MM, timeUnits=constants.NIFTI_UNITS_SEC, name='new'): """Create a new :class:`.Image` with the specified properties. :arg shape: Tuple containing the image shape :arg pixdim: Tuple containing the image pixdims :arg dtype: ``numpy`` ``dtype`` :arg affine: ``(4, 4)`` ``numpy`` array specifying the voxel-to-world affine :arg xyzUnits: Spatial units :arg timeUnits: Temporal units """ data = np.zeros(shape, dtype=dtype) img = nib.Nifti2Image(data, affine) img.header.set_zooms(np.abs(pixdim)) img.header.set_xyzt_units(xyzUnits, timeUnits) return fslimage.Image(img, name=name)
[docs]class NewImageDialog(wx.Dialog): """The ``NewImageDialog`` displays a collection of widgets allowing the user to select the data type, shape, dimensions (pixdims), and voxel-to- world affine. """
[docs] def __init__(self, parent, shape, pixdim, affine, dtype): """Create a ``NewImageDialog``. :arg parent: ``wx`` parent object :arg shape: Tuple of three initial shape values :arg pixdim: Tuple of three initial pixdim values :arg affine: Initial affine, assumed to be a ``(4, 4)`` ``numpy`` array :arg dtype: Initial ``numpy`` dtype. Must be one of ``float32``, ``float64``, ``uint8``, ``int16``, or ``int32``. """ if shape is None: shape = (100, 100, 100) if pixdim is None: pixdim = (1, 1, 1) if affine is None: affine = np.eye(4) if dtype is None: dtype = np.float32 dtypeLabels = ['float', 'uchar', 'sshort', 'sint', 'double'] dtypeValues = [np.float32, np.uint8, np.int16, np.int32, np.float64] dtypeLabels = [strings.labels[self, l] for l in dtypeLabels] if dtype in dtypeValues: dtype = dtypeValues.index(dtype) else: dtype = 0 self.__dtypeValues = dtypeValues wx.Dialog.__init__(self, parent, title=strings.titles[self], style=wx.DEFAULT_DIALOG_STYLE) dtypeLabel = strings.labels[self, 'dtype'] shapeLabel = strings.labels[self, 'shape'] pixdimLabel = strings.labels[self, 'pixdim'] affineLabel = strings.labels[self, 'affine'] linkLabel = strings.labels[self, 'link'] okLabel = strings.labels[self, 'ok'] cancelLabel = strings.labels[self, 'cancel'] self.__dtypeLabel = wx.StaticText(self, label=dtypeLabel) self.__shapeLabel = wx.StaticText(self, label=shapeLabel) self.__pixdimLabel = wx.StaticText(self, label=pixdimLabel) self.__affineLabel = wx.StaticText(self, label=affineLabel) self.__xLabel = wx.StaticText(self, label='X') self.__yLabel = wx.StaticText(self, label='Y') self.__zLabel = wx.StaticText(self, label='Z') self.__dtype = wx.Choice(self, choices=dtypeLabels) self.__ok = wx.Button(self, id=wx.ID_OK, label=okLabel) self.__cancel = wx.Button(self, id=wx.ID_CANCEL, label=cancelLabel) self.__dtype.SetSelection(dtype) shapewidgets = [] pixdimwidgets = [] for i in range(3): shapew = floatspin.FloatSpinCtrl(self, minValue=1, maxValue=2 ** 64 - 1, increment=1, value=shape[i], style=floatspin.FSC_INTEGER) pixdimw = floatspin.FloatSpinCtrl(self, minValue=-100, maxValue=100, increment=0.5, value=pixdim[i]) shapewidgets .append(shapew) pixdimwidgets.append(pixdimw) self.__shapex = shapewidgets[ 0] self.__shapey = shapewidgets[ 1] self.__shapez = shapewidgets[ 2] self.__pixdimx = pixdimwidgets[0] self.__pixdimy = pixdimwidgets[1] self.__pixdimz = pixdimwidgets[2] self.__link = wx.CheckBox(self, label=linkLabel) self.__link.SetValue(True) self.__affine = wxgrid.Grid(self) self.__affine.SetDefaultEditor(wxgrid.GridCellFloatEditor(-1, 2)) self.__affine.CreateGrid(4, 4) self.__affine.HideRowLabels() self.__affine.HideColLabels() for i in range(4): self.__affine.SetColFormatFloat(i, -1, 2) self.affine = affine self.__mainSizer = wx.BoxSizer(wx.VERTICAL) self.__dtypeSizer = wx.BoxSizer(wx.HORIZONTAL) self.__affineSizer = wx.BoxSizer(wx.HORIZONTAL) self.__affineLblSizer = wx.BoxSizer(wx.HORIZONTAL) self.__shapepixSizer = wx.BoxSizer(wx.HORIZONTAL) self.__shapepixGrid = wx.FlexGridSizer(4, 3, 0, 0) self.__buttonSizer = wx.BoxSizer(wx.HORIZONTAL) self.__dtypeSizer.Add((1, 1), proportion=1) self.__dtypeSizer.Add(self.__dtypeLabel) self.__dtypeSizer.Add(self.__dtype) self.__dtypeSizer.Add((1, 1), proportion=1) self.__shapepixGrid .Add((1, 1)) self.__shapepixGrid .Add(self.__shapeLabel) self.__shapepixGrid .Add(self.__pixdimLabel) self.__shapepixGrid .Add(self.__xLabel) self.__shapepixGrid .Add(self.__shapex) self.__shapepixGrid .Add(self.__pixdimx) self.__shapepixGrid .Add(self.__yLabel) self.__shapepixGrid .Add(self.__shapey) self.__shapepixGrid .Add(self.__pixdimy) self.__shapepixGrid .Add(self.__zLabel) self.__shapepixGrid .Add(self.__shapez) self.__shapepixGrid .Add(self.__pixdimz) self.__shapepixSizer.Add((1, 1), proportion=1) self.__shapepixSizer.Add(self.__shapepixGrid) self.__shapepixSizer.Add((1, 1), proportion=1) self.__affineSizer.Add((1, 1), proportion=1) self.__affineSizer.Add(self.__affine) self.__affineSizer.Add((1, 1), proportion=1) self.__affineLblSizer.Add(self.__affineLabel) self.__affineLblSizer.Add((10, 1)) self.__affineLblSizer.Add(self.__link) self.__buttonSizer.Add((1, 1), proportion=1) self.__buttonSizer.Add(self.__ok) self.__buttonSizer.Add(self.__cancel) self.__buttonSizer.Add((1, 1), proportion=1) szargs = {'flag' : wx.EXPAND | wx.LEFT | wx.RIGHT, 'border' : 5} self.__mainSizer.Add((1, 10)) self.__mainSizer.Add(self.__dtypeSizer, **szargs) self.__mainSizer.Add((1, 10)) self.__mainSizer.Add(self.__shapepixSizer, **szargs) self.__mainSizer.Add((1, 10)) self.__mainSizer.Add(self.__affineLblSizer, **szargs) self.__mainSizer.Add((1, 10)) self.__mainSizer.Add(self.__affineSizer, **szargs) self.__mainSizer.Add((1, 10)) self.__mainSizer.Add(self.__buttonSizer, **szargs) self.__mainSizer.Add((1, 10)) self.SetSizer(self.__mainSizer) self.Layout() self.Fit() self.__ok.SetFocus() self.__pixdimx.Bind(floatspin.EVT_FLOATSPIN, self.__onPixdim) self.__pixdimy.Bind(floatspin.EVT_FLOATSPIN, self.__onPixdim) self.__pixdimz.Bind(floatspin.EVT_FLOATSPIN, self.__onPixdim) self.__affine .Bind(wxgrid.EVT_GRID_CELL_CHANGED, self.__onAffine) self.__link .Bind(wx.EVT_CHECKBOX, self.__onLink)
def __onLink(self, ev): """Called when the link checkbox changes. If linking is enabled, calls :meth:`__onAffine`. """ if not self.link: return # affine takes precedence self.__onAffine(ev) def __onPixdim(self, ev): """Called when any pixdim widget changes. If linking is enabled, reconstructs the affine with the new pixdim values. """ if not self.link: return # If we construct an affine with # scales == 0, we get explosions if any([p == 0 for p in self.pixdim]): return offsets, rotations = fslaffine.decompose(self.affine)[1:] self.affine = fslaffine.compose(self.pixdim, offsets, rotations) def __onAffine(self, ev): """Called when the affine changes. If linking is enabled, updates the pixdim values from the new affine. """ if not self.link: return scales = fslaffine.decompose(self.affine)[0] self.__pixdimx.SetValue(scales[0]) self.__pixdimy.SetValue(scales[1]) self.__pixdimz.SetValue(scales[2]) @property def linkWidget(self): """Return a reference to the link widget. """ return self.__link @property def shapeWidgets(self): """Return a reference to the three shape widgets. """ return (self.__shapex, self.__shapey, self.__shapez) @property def pixdimWidgets(self): """Return a reference to the three pixdim widgets. """ return (self.__pixdimx, self.__pixdimy, self.__pixdimz) @property def dtypeWidget(self): """Return a reference to the dtype widget. """ return self.__dtype @property def affineWidget(self): """Return a reference to the affine widget. """ return self.__affine @property def ok(self): """Return a reference to the ok button. """ return self.__ok @property def cancel(self): """Return a reference to the cancel button. """ return self.__cancel @property def link(self): """Return a tuple containing the current affine-dimension link checkbox value. """ return self.__link.GetValue() @property def shape(self): """Return a tuple containing the current shape values. """ return (self.__shapex.GetValue(), self.__shapey.GetValue(), self.__shapez.GetValue()) @property def pixdim(self): """Return a tuple containing the current pixdim values. """ return (self.__pixdimx.GetValue(), self.__pixdimy.GetValue(), self.__pixdimz.GetValue()) @property def dtype(self): """Return the currently selected data type, as a ``numpy.dtype``. """ return self.__dtypeValues[self.__dtype.GetSelection()] @property def affine(self): """Return the current content of the affine grid, as a ``numpy`` array of shape ``(4, 4)``. """ aff = np.zeros((4, 4), dtype=np.float64) for i in range(4): for j in range(4): aff[i, j] = float(self.__affine.GetCellValue(i, j)) return aff @affine.setter def affine(self, aff): """Set the current contents of the affine grid to ``aff``, assumed to be a ``(4, 4)`` ``numpy`` array. """ for i in range(4): for j in range(4): val = '{:0.2f}'.format(aff[i, j]) self.__affine.SetCellValue((i, j), val)