#
# applyflirtxfm.py - The ApplyFlirtXfmAction class.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`ApplyFlirtXfmAction` class, an
:class:`.Action` which allows the user to load a FLIRT transformation
matrix and apply it to an :class:`.Image` overlay.
A number of related standalone classes and functions are also defined in this
module:
.. autosummary::
:nosignatures:
FlirtFileDialog
promptForFlirtFiles
guessFlirtFiles
calculateTransform
"""
import os
import os.path as op
import numpy as np
import wx
import fsl.data.image as fslimage
import fsl.transform.flirt as flirt
import fsleyes.strings as strings
import fsleyes.actions.base as base
[docs]class ApplyFlirtXfmAction(base.NeedOverlayAction):
"""The ``ApplyFlirtXfmAction`` class is an action which allows the user to
load a FLIRT transformation matrix (or other affine file) and apply it to
the currently selected overlay, if it is an :class:`.Image` instance.
A :class:`FlirtFileDialog` is used to prompt the user to select a
transformation matrix and reference image. The :func:`calculateTransform`
function is used to calculate the source voxel -> reference world
transformation, and the image ``voxToWorldMat`` is then updated
accordingly.
"""
[docs] def __init__(self, overlayList, displayCtx, frame):
"""Create an ``ApplyFlirtXfmAction``.
:arg overlayList: The :class:`.OverlayList`.
:arg displayCtx: The :class:`.DisplayContext`.
:arg frame: The :class:`.FSLeyesFrame`.
"""
base.NeedOverlayAction.__init__(
self, overlayList, displayCtx, func=self.__applyFlirtXfm)
self.__frame = frame
def __applyFlirtXfm(self):
"""Called when this action is executed.
"""
displayCtx = self.displayCtx
overlayList = self.overlayList
overlay = displayCtx.getSelectedOverlay()
affType, matFile, refFile = promptForFlirtFiles(
self.__frame, overlay, overlayList, displayCtx)
if all((affType is None, matFile is None, refFile is None)):
return
if affType == 'flirt':
xform = calculateTransform(
overlay, overlayList, displayCtx, matFile, refFile)
elif affType == 'v2w':
xform = np.loadtxt(matFile)
overlay.voxToWorldMat = xform
[docs]def promptForFlirtFiles(parent, overlay, overlayList, displayCtx, save=False):
"""Displays a dialog prompting the user to select a FLIRT
transformation matrix file and associated reference image for
the given overlay.
:arg parent: The :mod:`wx` parent object.
:arg overlay: The overlay to load a FLIRT matrix for.
:arg overlayList: The :class:`.OverlayList` instance.
:arg displayCtx: The :class:`.DisplayContext` instance.
:arg save: Prompt the user to save a transformation matrix instead.
:returns: A tuple containing:
- The affine type currently one of ``'flirt'``, indicating a
FLIRT matrix, or ``'v2w'``, indicating a "raw" voxel-to-world
matrix.
- The selected matrix file.
- The selected reference image file (``None`` if
``affType is 'v2w'``)
If the user cancelled the dialog, all elements of this tuple will
be ``None``.
"""
if overlay.dataSource is not None:
matFile, refFile = guessFlirtFiles(overlay.dataSource)
else:
matFile, refFile = None, None
refOptFiles = []
refOpts = []
selectedRef = None
for ovl in reversed(overlayList):
if not isinstance(ovl, fslimage.Image): continue
refOptFiles.append(ovl.dataSource)
refOpts .append(displayCtx.getDisplay(ovl).name)
# If a reference file has been
# guesed, make sure it is
# selected in the drop down box
if refFile is not None and ovl.dataSource == refFile:
selectedRef = len(refOpts) - 1
if len(refOpts) == 0:
refOpts = None
refOptFiles = None
dlg = FlirtFileDialog(parent,
overlay.dataSource,
refOpts=refOpts,
refOptFiles=refOptFiles,
selectedRef=selectedRef,
matFile=matFile,
refFile=refFile,
save=save)
dlg.Layout()
dlg.Fit()
dlg.CentreOnParent()
if dlg.ShowModal() == wx.ID_OK:
return dlg.GetAffineType(), dlg.GetMatFile(), dlg.GetRefFile()
else:
return None, None, None
[docs]def guessFlirtFiles(path):
"""Given a ``path`` to a NIFTI image file, tries to guess an appropriate
FLIRT transformation matrix file and reference image. The guess is based
on the path location (e.g. if it is a FEAT or MELODIC image).
Returns a tuple containing paths to the matrix file and reference image,
or ``(None, None)`` if a guess couldn't be made.
"""
import fsl.data.featanalysis as featanalysis
import fsl.data.melodicanalysis as melodicanalysis
if path is None:
return None, None
filename = op.basename(path)
dirname = op.dirname( path)
regDir = None
srcRefMap = {}
func2struc = 'example_func2highres.mat'
struc2std = 'highres2standard.mat'
featDir = featanalysis .getAnalysisDir(dirname)
melDir = melodicanalysis.getAnalysisDir(dirname)
# TODO more heuristics
if featDir is not None:
regDir = op.join(featDir, 'reg')
srcRefMap = {
'example_func' : ('highres', func2struc),
'filtered_func_data' : ('highres', func2struc),
'mean_func' : ('highres', func2struc),
'thresh_zstat' : ('highres', func2struc),
op.join('stats', 'zstat') : ('highres', func2struc),
op.join('stats', 'tstat') : ('highres', func2struc),
op.join('stats', 'cope') : ('highres', func2struc),
op.join('stats', 'pe') : ('highres', func2struc),
'highres' : ('standard', struc2std),
'highres_head' : ('standard', struc2std),
'struc' : ('standard', struc2std),
'struct' : ('standard', struc2std),
'struct_brain' : ('standard', struc2std),
'struc_brain' : ('standard', struc2std),
}
elif melodicanalysis.isMelodicDir(dirname):
if melDir.startswith('filtered_func_data'):
regDir = op.join(melDir, '..', 'reg')
else:
regDir = op.join(melDir, 'reg')
srcRefMap = {
'filtered_func_data' : ('highres', func2struc),
'melodic_IC' : ('highres', func2struc),
'example_func' : ('highres', func2struc),
'mean_func' : ('highres', func2struc),
'highres' : ('standard', struc2std),
'highres_head' : ('standard', struc2std),
'struc' : ('standard', struc2std),
'struct' : ('standard', struc2std),
'struct_brain' : ('standard', struc2std),
'struc_brain' : ('standard', struc2std),
}
matFile = None
refFile = None
for src, (ref, mat) in srcRefMap.items():
if not filename.startswith(src):
continue
mat = op.join(regDir, mat)
ref = op.join(regDir, ref)
try: ref = fslimage.addExt(ref)
except Exception: continue
if op.exists(mat):
matFile = mat
refFile = ref
break
return matFile, refFile
[docs]class FlirtFileDialog(wx.Dialog):
"""The ``FlirtFileDialog`` class is a ``wx.Dialog`` which prompts the user to
select a FLIRT (or other affine) transformation matrix and reference image
associated with a source image.
The user can select a reference image either from a drop down box, or
by selecting a file in the file system.
"""
[docs] def __init__(self,
parent,
srcFile,
refOpts=None,
refOptFiles=None,
selectedRef=None,
matFile=None,
refFile=None,
save=False):
"""Create a ``FlirtFileDialog``.
:arg parent: The ``wx`` parent object.
:arg srcFile: Path to the FLIRT source image file
:arg refOpts: Options to use in the reference image drop down box.
:arg refOptFiles: File paths which correspond to the ``refOpts``.
:arg selectedRef: Index of initially selected ``refOpt``.
:arg matFile: Initial path to a FLIRT transformation matrix file
:arg refFile: Initial Path to a FLIRT reference image file
:arg save: If ``True``, the user will be prompted to save a
FLIRT matrix. Otherwise (the default), the user will
be prompted to load an existing FLIRT matrix.
"""
titleKey = {True : 'save', False : 'load'}[save]
wx.Dialog.__init__(self,
parent,
title=strings.titles[self, titleKey],
style=(wx.DEFAULT_DIALOG_STYLE | wx.STAY_ON_TOP))
if refOpts is None: refOpts = []
if refOptFiles is None: refOptFiles = []
if selectedRef is None: selectedRef = 0
self.__srcFile = srcFile
self.__refOpts = list(refOpts)
self.__refOptFiles = list(refOptFiles)
self.__matFile = None
self.__refFile = None
self.__save = save
self.__affineTypes = ['flirt', 'v2w']
affTypeOpts = [strings.labels[self, 'affType', at]
for at in self.__affineTypes]
refOpts = list(refOpts) + [strings.labels[self, 'refChoiceSelectFile']]
overlayName = wx.StaticText(self, style=wx.ALIGN_CENTRE_HORIZONTAL)
label = wx.StaticText(self, style=wx.ALIGN_CENTRE_HORIZONTAL)
affType = wx.Choice(self, choices=affTypeOpts)
affTypeLabel = wx.StaticText(self)
refChoiceLabel = wx.StaticText(self)
matFileLabel = wx.StaticText(self)
refFileLabel = wx.StaticText(self)
refChoice = wx.Choice(self, choices=refOpts)
matFileText = wx.TextCtrl(self)
refFileText = wx.TextCtrl(self)
matFileButton = wx.Button(self)
refFileButton = wx.Button(self)
okButton = wx.Button(self, wx.ID_OK)
cancelButton = wx.Button(self, wx.ID_CANCEL)
self.__matFileText = matFileText
self.__refFileText = refFileText
self.__refFileLabel = refFileLabel
self.__refFileButton = refFileButton
self.__refChoiceLabel = refChoiceLabel
self.__refChoice = refChoice
self.__affType = affType
if srcFile is None:
srcName = strings.labels[self, 'inmemory']
else:
srcName = op.basename(srcFile)
srcName = fslimage.removeExt(srcFile)
overlayName .SetLabel(strings.labels[self, 'source'].format(srcName))
affTypeLabel .SetLabel(strings.labels[self, 'affType'])
refChoiceLabel.SetLabel(strings.labels[self, 'refImage'])
matFileLabel .SetLabel(strings.labels[self, 'matFile'])
refFileLabel .SetLabel(strings.labels[self, 'refFile'])
matFileButton .SetLabel(strings.labels[self, 'selectFile'])
refFileButton .SetLabel(strings.labels[self, 'selectFile'])
okButton .SetLabel(strings.labels[self, 'ok'])
cancelButton .SetLabel(strings.labels[self, 'cancel'])
refChoice .SetSelection(selectedRef)
affType .SetSelection(0)
if save: label.SetLabel(strings.labels[self, 'save.message'])
else: label.SetLabel(strings.labels[self, 'load.message'])
if matFile is not None:
matFileText.SetValue(matFile)
matFileText.SetInsertionPointEnd()
if refFile is not None:
refFileText.SetValue(refFile)
refFileText.SetInsertionPointEnd()
sizer = wx.BoxSizer(wx.VERTICAL)
widgetSizer = wx.FlexGridSizer(4, 3, 0, 0)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
widgetSizer.AddGrowableCol(1)
sizer.Add((1, 10), flag=wx.EXPAND)
sizer.Add(overlayName, flag=wx.ALIGN_CENTRE)
sizer.Add((1, 10), flag=wx.EXPAND)
sizer.Add(label, flag=wx.ALIGN_CENTRE)
sizer.Add((1, 10), flag=wx.EXPAND)
sizer.Add(widgetSizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
sizer.Add((1, 10), flag=wx.EXPAND)
sizer.Add(btnSizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
sizer.Add((1, 10), flag=wx.EXPAND)
widgetSizer.Add(affTypeLabel, flag=wx.ALL, border=3)
widgetSizer.Add(affType, flag=wx.EXPAND, proportion=1)
widgetSizer.Add((-1, -1))
if refOpts is not None:
widgetSizer.Add(refChoiceLabel, flag=wx.ALL, border=3)
widgetSizer.Add(refChoice, flag=wx.EXPAND, proportion=1)
widgetSizer.Add((-1, -1))
else:
widgetSizer.Add((-1, -1))
widgetSizer.Add((-1, -1))
widgetSizer.Add((-1, -1))
widgetSizer.Add(refFileLabel, flag=wx.ALL, border=3)
widgetSizer.Add(refFileText, flag=wx.EXPAND, proportion=1)
widgetSizer.Add(refFileButton, flag=wx.EXPAND)
widgetSizer.Add(matFileLabel, flag=wx.ALL, border=3)
widgetSizer.Add(matFileText, flag=wx.EXPAND, proportion=1)
widgetSizer.Add(matFileButton, flag=wx.EXPAND)
btnSizer.Add((10, 1), flag=wx.EXPAND, proportion=1)
btnSizer.Add(okButton, flag=wx.EXPAND)
btnSizer.Add((10, 1), flag=wx.EXPAND)
btnSizer.Add(cancelButton, flag=wx.EXPAND)
btnSizer.Add((10, 1), flag=wx.EXPAND, proportion=1)
matFileText.SetMinSize((400, -1))
refFileText.SetMinSize((400, -1))
okButton.SetDefault()
self.SetSizer(sizer)
okButton .Bind(wx.EVT_BUTTON, self.__onOkButton)
cancelButton .Bind(wx.EVT_BUTTON, self.__onCancelButton)
matFileButton.Bind(wx.EVT_BUTTON, self.__onMatFileButton)
refFileButton.Bind(wx.EVT_BUTTON, self.__onRefFileButton)
affType .Bind(wx.EVT_CHOICE, self.__onAffType)
refChoice .Bind(wx.EVT_CHOICE, self.__onRefChoice)
self.__okButton = okButton
self.__cancelButton = cancelButton
self.__matFileText = matFileText
self.__refFileText = refFileText
self.__affType = affType
self.__refChoice = refChoice
if len(refOpts) == 1:
refChoice .Disable()
refChoiceLabel.Disable()
else:
self.__onRefChoice(None)
@property
def ok(self):
"""Return a reference to the OK button. """
return self.__okButton
@property
def cancel(self):
"""Return a reference to the cancel button. """
return self.__cancelButton
@property
def matFileText(self):
"""Return a reference to the matrix file text entry widget. """
return self.__matFileText
@property
def refFileText(self):
"""Return a reference to the reference file text entry widget. """
return self.__refFileText
@property
def affType(self):
"""Return a reference to the affine type dropdown widget. """
return self.__affType
@property
def refChoice(self):
"""Return a reference to the reference file dropdown widget. """
return self.__refChoice
[docs] def GetAffineType(self):
"""Return the currently selected affine type - currently either
``'flirt'`` (indicating a FSL FLIRT matrix file), or ``'v2w'``
(indicating a "raw" voxel-to-world affine).
"""
return self.__affineTypes[self.__affType.GetSelection()]
[docs] def GetMatFile(self):
"""Returns the current value of the matrix file as a string, or
``None``, if the file path is not valid.
"""
matFile = self.__matFileText.GetValue()
if self.__save or op.exists(matFile): return op.abspath(matFile)
else: return None
[docs] def GetRefFile(self):
"""Returns the current value of the reference file, as a string, or
``None``, if the file path is not valid.
"""
choice = self.__refChoice.GetSelection()
if choice < len(self.__refOpts):
refFile = self.__refOptFiles[choice]
else:
refFile = self.__refFileText.GetValue()
if op.exists(refFile): return op.abspath(refFile)
else: return None
def __onOkButton(self, ev):
"""Called when the user clicks the ok button. Closes the dialog.
"""
self.EndModal(wx.ID_OK)
def __onCancelButton(self, ev):
"""Called when the user clicks the cancel button. Closes the dialog.
"""
self.__matFileText.SetValue('')
self.__refFileText.SetValue('')
self.EndModal(wx.ID_CANCEL)
def __onAffType(self, ev):
"""Called when the user changes the affine type. Enables/disables
widgets related to the reference image (as they are only used for
FLIRT affines).
"""
affType = self.__affineTypes[self.__affType.GetSelection()]
isFlirt = affType == 'flirt'
self.__refChoice .Enable(isFlirt)
self.__refChoiceLabel.Enable(isFlirt)
self.__refFileLabel .Enable(isFlirt)
self.__refFileText .Enable(isFlirt)
self.__refFileButton .Enable(isFlirt)
def __onRefChoice(self, ev):
"""Called when the user changes the selection in the reference image
drop down box. Enables/disables the reference image file selection
widgets as necessary.
"""
choice = self.__refChoice.GetSelection()
selectFile = choice >= len(self.__refOpts)
self.__refFileLabel .Enable(selectFile)
self.__refFileText .Enable(selectFile)
self.__refFileButton.Enable(selectFile)
def __onMatFileButton(self, ev):
"""Called when the user clicks the matrix file select button.
Displays a file dialog prompting the user to select a matrix file.
"""
if self.__save: style = wx.FD_SAVE
else: style = wx.FD_OPEN
matFile = self.__matFileText.GetValue().strip()
if matFile == '':
if self.__srcFile is None: dirName = os.getcwd()
else: dirName = op.dirname(self.__srcFile)
fileName = 'xform.mat'
else:
dirName, fileName = op.split(matFile)
dlg = wx.FileDialog(
self,
defaultDir=dirName,
defaultFile=fileName,
message=strings.messages[self, 'matFile'],
style=style)
if dlg.ShowModal() == wx.ID_OK:
self.__matFileText.SetValue(dlg.GetPath())
def __onRefFileButton(self, ev):
"""Called when the user clicks the reference file select button.
Displays a file dialog prompting the user to select a reference file.
"""
refFile = self.__refFileText.GetValue()
if refFile == '':
if self.__srcFile is None: refFile = ''
else: refFile = self.__srcFile
dlg = wx.FileDialog(
self,
defaultFile=refFile,
message=strings.messages[self, 'refFile'],
style=wx.FD_OPEN)
if dlg.ShowModal() == wx.ID_OK:
self.__refFileText.SetValue(dlg.GetPath())