Skip to content
Snippets Groups Projects
Commit 31051fe1 authored by Paul McCarthy's avatar Paul McCarthy
Browse files

1. Basic overlay info panel. Need to work on formatting.

2. Actions can now be ordered

3. Some changes to Image orientation utility methods, allowing caller to
specify sform or qform.
parent b6df8af1
No related branches found
No related tags found
No related merge requests found
...@@ -277,23 +277,29 @@ class Image(props.HasProperties): ...@@ -277,23 +277,29 @@ class Image(props.HasProperties):
return len(self.shape) > 3 and self.shape[3] > 1 return len(self.shape) > 3 and self.shape[3] > 1
def getXFormCode(self): def getXFormCode(self, code=None):
"""This method returns the code contained in the NIFTI1 header, """This method returns the code contained in the NIFTI1 header,
indicating the space to which the (transformed) image is oriented. indicating the space to which the (transformed) image is oriented.
The ``code`` parameter may be either ``sform`` (the default) or
``qform`` in which case the corresponding matrix is used.
""" """
sform_code = self.nibImage.get_header()['sform_code']
# Invalid values if code is None: code = 'sform_code'
if sform_code > 4: code = constants.NIFTI_XFORM_UNKNOWN elif code == 'sform' : code = 'sform_code'
elif sform_code < 0: code = constants.NIFTI_XFORM_UNKNOWN elif code == 'qform' : code = 'qform_code'
else: raise ValueError('code must be None, sform, or qform')
# All is well code = self.nibImage.get_header()[code]
else: code = sform_code
# Invalid values
if code > 4: code = constants.NIFTI_XFORM_UNKNOWN
elif code < 0: code = constants.NIFTI_XFORM_UNKNOWN
return int(code) return int(code)
def getWorldOrientation(self, axis): def getWorldOrientation(self, axis, code=None):
"""Returns a code representing the orientation of the specified axis """Returns a code representing the orientation of the specified axis
in world space. in world space.
...@@ -316,17 +322,17 @@ class Image(props.HasProperties): ...@@ -316,17 +322,17 @@ class Image(props.HasProperties):
to superior). to superior).
""" """
if self.getXFormCode() == constants.NIFTI_XFORM_UNKNOWN: if self.getXFormCode(code) == constants.NIFTI_XFORM_UNKNOWN:
return -1 return constants.ORIENT_UNKNOWN
if axis == 0: return constants.ORIENT_L2R if axis == 0: return constants.ORIENT_L2R
elif axis == 1: return constants.ORIENT_P2A elif axis == 1: return constants.ORIENT_P2A
elif axis == 2: return constants.ORIENT_I2S elif axis == 2: return constants.ORIENT_I2S
else: return -1 else: return constants.ORIENT_UNKNOWN
def getVoxelOrientation(self, axis): def getVoxelOrientation(self, axis, code=None):
"""Returns a code representing the (estimated) orientation of the """Returns a code representing the (estimated) orientation of the
specified voxelwise axis. specified voxelwise axis.
...@@ -334,17 +340,23 @@ class Image(props.HasProperties): ...@@ -334,17 +340,23 @@ class Image(props.HasProperties):
of the return value. of the return value.
""" """
if self.getXFormCode() == constants.NIFTI_XFORM_UNKNOWN: if self.getXFormCode(code) == constants.NIFTI_XFORM_UNKNOWN:
return -1 return constants.ORIENT_UNKNOWN
if code is None: xform = self.nibImage.get_affine()
elif code == 'sform': xform = self.nibImage.get_sform()
elif code == 'qform': xform = self.nibImage.get_qform()
else: raise ValueError('code must be None, qform, or sform')
# the aff2axcodes returns one code for each # the aff2axcodes returns one code for each
# axis in the image array (i.e. in voxel space), # axis in the image array (i.e. in voxel space),
# which denotes the real world direction # which denotes the real world direction
code = nib.orientations.aff2axcodes( code = nib.orientations.aff2axcodes(
self.nibImage.get_affine(), xform,
((constants.ORIENT_R2L, constants.ORIENT_L2R), ((constants.ORIENT_R2L, constants.ORIENT_L2R),
(constants.ORIENT_A2P, constants.ORIENT_P2A), (constants.ORIENT_A2P, constants.ORIENT_P2A),
(constants.ORIENT_S2I, constants.ORIENT_I2S)))[axis] (constants.ORIENT_S2I, constants.ORIENT_I2S)))[axis]
return code return code
...@@ -354,8 +366,8 @@ class Image(props.HasProperties): ...@@ -354,8 +366,8 @@ class Image(props.HasProperties):
# so i'm just providing '*.gz'for now # so i'm just providing '*.gz'for now
ALLOWED_EXTENSIONS = ['.nii.gz', '.nii', '.img', '.hdr', '.img.gz', '.gz'] ALLOWED_EXTENSIONS = ['.nii.gz', '.nii', '.img', '.hdr', '.img.gz', '.gz']
"""The file extensions which we understand. This list is used as the default """The file extensions which we understand. This list is used as the default
if if the ``allowedExts`` parameter is not passed to any of the functions in if if the ``allowedExts`` parameter is not passed to any of the functions
this module. below.
""" """
EXTENSION_DESCRIPTIONS = ['Compressed NIFTI1 images', EXTENSION_DESCRIPTIONS = ['Compressed NIFTI1 images',
......
...@@ -145,6 +145,7 @@ titles = TypeDict({ ...@@ -145,6 +145,7 @@ titles = TypeDict({
'HistogramListPanel' : 'Histogram list', 'HistogramListPanel' : 'Histogram list',
'HistogramControlPanel' : 'Histogram control', 'HistogramControlPanel' : 'Histogram control',
'ClusterPanel' : 'Cluster browser', 'ClusterPanel' : 'Cluster browser',
'OverlayInfoPanel' : 'Overlay information',
'LookupTablePanel.loadLut' : 'Select a lookup table file', 'LookupTablePanel.loadLut' : 'Select a lookup table file',
'LookupTablePanel.labelExists' : 'Label already exists', 'LookupTablePanel.labelExists' : 'Label already exists',
...@@ -170,6 +171,7 @@ actions = TypeDict({ ...@@ -170,6 +171,7 @@ actions = TypeDict({
'CanvasPanel.toggleAtlasPanel' : 'Atlas panel', 'CanvasPanel.toggleAtlasPanel' : 'Atlas panel',
'CanvasPanel.toggleLookupTablePanel' : 'Lookup tables', 'CanvasPanel.toggleLookupTablePanel' : 'Lookup tables',
'CanvasPanel.toggleClusterPanel' : 'Cluster browser', 'CanvasPanel.toggleClusterPanel' : 'Cluster browser',
'CanvasPanel.toggleOverlayInfo' : 'Overlay information',
'OrthoPanel.toggleOrthoToolBar' : 'View properties', 'OrthoPanel.toggleOrthoToolBar' : 'View properties',
'OrthoPanel.toggleProfileToolBar' : 'Mode controls', 'OrthoPanel.toggleProfileToolBar' : 'Mode controls',
...@@ -292,7 +294,10 @@ labels = TypeDict({ ...@@ -292,7 +294,10 @@ labels = TypeDict({
'CanvasSettingsPanel.scene' : 'Scene settings', 'CanvasSettingsPanel.scene' : 'Scene settings',
'CanvasSettingsPanel.ortho' : 'Ortho view settings', 'CanvasSettingsPanel.ortho' : 'Ortho view settings',
'CanvasSettingsPanel.lightbox' : 'Lightbox settings', 'CanvasSettingsPanel.lightbox' : 'Lightbox settings',
'OverlayInfoPanel.Image.dimensions' : 'Dimensions',
'OverlayInfoPanel.Image.transform' : 'Transform/space',
'OverlayInfoPanel.Image.orient' : 'Orientation',
}) })
...@@ -548,3 +553,66 @@ anatomy = TypeDict({ ...@@ -548,3 +553,66 @@ anatomy = TypeDict({
('Image', 'space', constants.NIFTI_XFORM_TALAIRACH) : 'Talairach', ('Image', 'space', constants.NIFTI_XFORM_TALAIRACH) : 'Talairach',
('Image', 'space', constants.NIFTI_XFORM_MNI_152) : 'MNI152', ('Image', 'space', constants.NIFTI_XFORM_MNI_152) : 'MNI152',
}) })
nifti = TypeDict({
'dimensions' : 'Number of dimensions',
'dataSource' : 'Data source',
'datatype' : 'Data type',
'vox_units' : 'XYZ units',
'time_units' : 'Time units',
'descrip' : 'Description',
'qform_code' : 'QForm code',
'sform_code' : 'SForm code',
'voxOrient.0' : 'X voxel orientation',
'voxOrient.1' : 'Y voxel orientation',
'voxOrient.2' : 'Z voxel orientation',
'sformOrient.0' : 'X sform orientation',
'sformOrient.1' : 'Y sform orientation',
'sformOrient.2' : 'Z sform orientation',
'qformOrient.0' : 'X qform orientation',
'qformOrient.1' : 'Y qform orientation',
'qformOrient.2' : 'Z qform orientation',
'qform' : 'QForm matrix',
'sform' : 'SForm matrix',
'dim1' : 'dim1',
'dim2' : 'dim2',
'dim3' : 'dim3',
'dim4' : 'dim4',
'dim5' : 'dim5',
'dim6' : 'dim6',
'dim7' : 'dim7',
'pixdim1' : 'pixdim1',
'pixdim2' : 'pixdim2',
'pixdim3' : 'pixdim3',
'pixdim4' : 'pixdim4',
'pixdim5' : 'pixdim5',
'pixdim6' : 'pixdim6',
'pixdim7' : 'pixdim7',
('datatype', 0) : 'UNKNOWN',
('datatype', 1) : 'BINARY',
('datatype', 2) : 'UINT8',
('datatype', 4) : 'INT16',
('datatype', 8) : 'INT32',
('datatype', 16) : 'FLOAT32',
('datatype', 32) : 'COMPLEX64',
('datatype', 64) : 'DOUBLE64',
('datatype', 128) : 'RGB',
('datatype', 255) : 'ALL',
('datatype', 256) : 'INT8',
('datatype', 512) : 'UINT16',
('datatype', 768) : 'UINT32',
('datatype', 1024) : 'INT64',
('datatype', 1280) : 'UINT64',
('datatype', 1536) : 'FLOAT128',
('datatype', 1792) : 'COMPLEX128',
('datatype', 2048) : 'COMPLEX256',
('datatype', 2304) : 'RGBA32',
})
...@@ -22,6 +22,7 @@ or more actions. As the :class:`.FSLViewPanel` class derives from ...@@ -22,6 +22,7 @@ or more actions. As the :class:`.FSLViewPanel` class derives from
import logging import logging
import collections
import props import props
...@@ -210,7 +211,7 @@ class ActionProvider(props.SyncableHasProperties): ...@@ -210,7 +211,7 @@ class ActionProvider(props.SyncableHasProperties):
if actions is None: if actions is None:
actions = {} actions = {}
self.__actions = {} self.__actions = collections.OrderedDict()
for name, func in actions.items(): for name, func in actions.items():
act = Action(overlayList, displayCtx, action=func) act = Action(overlayList, displayCtx, action=func)
...@@ -246,7 +247,7 @@ class ActionProvider(props.SyncableHasProperties): ...@@ -246,7 +247,7 @@ class ActionProvider(props.SyncableHasProperties):
"""Return a dictionary containing ``{name -> Action}`` mappings for """Return a dictionary containing ``{name -> Action}`` mappings for
all defined actions. all defined actions.
""" """
return dict(self.__actions) return collections.OrderedDict(self.__actions)
def isEnabled(self, name): def isEnabled(self, name):
......
...@@ -16,6 +16,7 @@ from histogramlistpanel import HistogramListPanel ...@@ -16,6 +16,7 @@ from histogramlistpanel import HistogramListPanel
from histogramcontrolpanel import HistogramControlPanel from histogramcontrolpanel import HistogramControlPanel
from clusterpanel import ClusterPanel from clusterpanel import ClusterPanel
from canvassettingspanel import CanvasSettingsPanel from canvassettingspanel import CanvasSettingsPanel
from overlayinfopanel import OverlayInfoPanel
from orthotoolbar import OrthoToolBar from orthotoolbar import OrthoToolBar
from orthoprofiletoolbar import OrthoProfileToolBar from orthoprofiletoolbar import OrthoProfileToolBar
......
#!/usr/bin/env python
#
# overlayinfopanel.py -
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import collections
import wx
import wx.html as wxhtml
import fsl.data.strings as strings
import fsl.fslview.panel as fslpanel
class OverlayInfo(object):
"""A little class which encapsulates human-readable information about
one overlay. ``OverlayInfo`` objects are created and returned by the
``OverlayInfoPanel.__get*Info`` methods.
"""
def __init__(self, title):
self.title = title
self.info = []
self.sections = collections.OrderedDict()
def addSection(self, section):
self.sections[section] = []
def addInfo(self, name, info, section=None):
if section is None: self.info .append((name, info))
else: self.sections[section].append((name, info))
class OverlayInfoPanel(fslpanel.FSLViewPanel):
def __init__(self, parent, overlayList, displayCtx):
fslpanel.FSLViewPanel.__init__(self, parent, overlayList, displayCtx)
self.__info = wxhtml.HtmlWindow(self)
self.__sizer = wx.BoxSizer(wx.HORIZONTAL)
self.__sizer.Add(self.__info, flag=wx.EXPAND, proportion=1)
self.SetSizer(self.__sizer)
displayCtx .addListener('selectedOverlay',
self._name,
self.__selectedOverlayChanged)
overlayList.addListener('overlays',
self._name,
self.__selectedOverlayChanged)
self.__currentOverlay = None
self.__currentDisplay = None
self.__selectedOverlayChanged()
self.Layout()
def destroy(self):
self._displayCtx .removeListener('selectedOverlay', self._name)
self._overlayList.removeListener('overlays', self._name)
if self.__currentDisplay is not None:
self.__currentDisplay.removeListener('name', self._name)
self.__currentOverlay = None
self.__currentDisplay = None
fslpanel.FSLViewPanel.destroy(self)
def __selectedOverlayChanged(self, *a):
overlay = self._displayCtx.getSelectedOverlay()
if overlay == self.__currentOverlay:
return
if self.__currentDisplay is not None:
self.__currentDisplay.removeListener('name', self._name)
self.__currenOverlay = None
self.__currenDisplay = None
if overlay is not None:
self.__currentOverlay = overlay
self.__currentDisplay = self._displayCtx.getDisplay(overlay)
self.__currentDisplay.addListener('name',
self._name,
self.__overlayNameChanged)
self.__updateInformation()
def __overlayNameChanged(self, *a):
self.__updateInformation()
def __updateInformation(self):
overlay = self.__currentOverlay
display = self.__currentDisplay
infoFunc = '_{}__get{}Info'.format(type(self) .__name__,
type(overlay).__name__)
infoFunc = getattr(self, infoFunc, None)
if infoFunc is None:
self.__info.SetPage('')
return
info = infoFunc(overlay, display)
self.__info.SetPage(self.__formatOverlayInfo(info))
def __formatOverlayInfo(self, info):
lines = [info.title]
lines += map(str, info.info)
for sec in info.sections.keys():
lines += [sec]
lines += map(str, info.sections[sec])
return '<br>'.join(lines)
def __getImageInfo(self, overlay, display):
info = OverlayInfo(display.name)
img = overlay.nibImage
hdr = img.get_header()
voxUnits, timeUnits = hdr.get_xyzt_units()
dimSect = strings.labels[self, overlay, 'dimensions']
xformSect = strings.labels[self, overlay, 'transform']
orientSect = strings.labels[self, overlay, 'orient']
info.addSection(dimSect)
info.addSection(xformSect)
info.addSection(orientSect)
info.addInfo(strings.nifti['dataSource'], overlay.dataSource)
info.addInfo(strings.nifti['datatype'],
strings.nifti['datatype', int(hdr['datatype'])])
info.addInfo(strings.nifti['descrip'], hdr['descrip'])
info.addInfo(strings.nifti['vox_units'], voxUnits, section=dimSect)
info.addInfo(strings.nifti['time_units'], timeUnits, section=dimSect)
info.addInfo(strings.nifti['dimensions'],
'{}D'.format(len(overlay.shape)),
section=dimSect)
for i in range(len(overlay.shape)):
info.addInfo(strings.nifti['dim{}'.format(i + 1)],
str(overlay.shape[i]),
section=dimSect)
for i in range(len(overlay.shape)):
pixdim = hdr['pixdim'][i + 1]
if i < 3: pixdim = '{} {}'.format(pixdim, voxUnits)
elif i == 3: pixdim = '{} {}'.format(pixdim, timeUnits)
info.addInfo(
strings.nifti['pixdim{}'.format(i + 1)],
pixdim,
section=dimSect)
info.addInfo(strings.nifti['qform_code'],
strings.anatomy['Image', 'space', int(hdr['qform_code'])],
section=xformSect)
info.addInfo(strings.nifti['sform_code'],
strings.anatomy['Image', 'space', int(hdr['sform_code'])],
section=xformSect)
# TODO matrix formatting (you'll need to use
# HTML, or maybe get the formatOverlayInfo
# method to support different types)
info.addInfo(strings.nifti['qform'],
str(img.get_qform()),
section=xformSect)
info.addInfo(strings.nifti['sform'],
str(img.get_sform()),
section=xformSect)
for i in range(3):
orient = overlay.getVoxelOrientation(i)
orient = '{} - {}'.format(
strings.anatomy['Image', 'lowlong', orient],
strings.anatomy['Image', 'highlong', orient])
info.addInfo(strings.nifti['voxOrient.{}'.format(i)],
orient,
section=orientSect)
for i in range(3):
orient = overlay.getWorldOrientation(i, code='sform')
orient = '{} - {}'.format(
strings.anatomy['Image', 'lowlong', orient],
strings.anatomy['Image', 'highlong', orient])
info.addInfo(strings.nifti['sformOrient.{}'.format(i)],
orient,
section=orientSect)
for i in range(3):
orient = overlay.getWorldOrientation(i, code='qform')
orient = '{} - {}'.format(
strings.anatomy['Image', 'lowlong', orient],
strings.anatomy['Image', 'highlong', orient])
info.addInfo(strings.nifti['qformOrient.{}'.format(i)],
orient,
section=orientSect)
return info
def __getFEATImageInfo(self, overlay, display):
return self.__getImageInfo(overlay)
def __getModelInfo(self, overlay, display):
info = OverlayInfo(display.name)
return info
...@@ -12,6 +12,7 @@ class for all panels which display image data (e.g. the ...@@ -12,6 +12,7 @@ class for all panels which display image data (e.g. the
""" """
import logging import logging
import collections
import wx import wx
...@@ -57,22 +58,24 @@ class CanvasPanel(viewpanel.ViewPanel): ...@@ -57,22 +58,24 @@ class CanvasPanel(viewpanel.ViewPanel):
if extraActions is None: if extraActions is None:
extraActions = {} extraActions = {}
actionz = dict({ actionz = [
'screenshot' : self.screenshot, ('screenshot', self.screenshot),
'showCommandLineArgs' : self.showCommandLineArgs, ('showCommandLineArgs', self.showCommandLineArgs),
'toggleOverlayList' : lambda *a: self.togglePanel( ('toggleOverlayList', lambda *a: self.togglePanel(
fslcontrols.OverlayListPanel), fslcontrols.OverlayListPanel)),
'toggleAtlasPanel' : lambda *a: self.togglePanel( ('toggleOverlayInfo', lambda *a: self.togglePanel(
fslcontrols.AtlasPanel), fslcontrols.OverlayInfoPanel)),
'toggleDisplayProperties' : lambda *a: self.togglePanel( ('toggleAtlasPanel', lambda *a: self.togglePanel(
fslcontrols.OverlayDisplayToolBar, False, self), fslcontrols.AtlasPanel)),
'toggleLocationPanel' : lambda *a: self.togglePanel( ('toggleDisplayProperties', lambda *a: self.togglePanel(
fslcontrols.LocationPanel), fslcontrols.OverlayDisplayToolBar, False, self)),
'toggleClusterPanel' : lambda *a: self.togglePanel( ('toggleLocationPanel', lambda *a: self.togglePanel(
fslcontrols.ClusterPanel), fslcontrols.LocationPanel)),
'toggleLookupTablePanel' : lambda *a: self.togglePanel( ('toggleClusterPanel', lambda *a: self.togglePanel(
fslcontrols.LookupTablePanel), fslcontrols.ClusterPanel)),
}.items() + extraActions.items()) ('toggleLookupTablePanel', lambda *a: self.togglePanel(
fslcontrols.LookupTablePanel))]
actionz = collections.OrderedDict(actionz + extraActions.items())
viewpanel.ViewPanel.__init__( viewpanel.ViewPanel.__init__(
self, parent, overlayList, displayCtx, actionz) self, parent, overlayList, displayCtx, actionz)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment