From 36ac17474add6c623a1a3cd35d0a81f7169eaa60 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauld.mccarthy@gmail.com> Date: Wed, 14 Oct 2015 16:05:48 +0100 Subject: [PATCH] OrthoPanel.__init__ now takes a boolean flag, allowing toolbars to be toggled. Bet, feat, flirt tools now working. --- fsl/fsleyes/views/orthopanel.py | 19 ++- fsl/tools/bet.py | 70 ++++++----- fsl/tools/feat.py | 205 ++++++++++++++++++++------------ fsl/tools/flirt.py | 132 ++++++++++++-------- 4 files changed, 264 insertions(+), 162 deletions(-) diff --git a/fsl/fsleyes/views/orthopanel.py b/fsl/fsleyes/views/orthopanel.py index c75be4e30..5ae778dac 100644 --- a/fsl/fsleyes/views/orthopanel.py +++ b/fsl/fsleyes/views/orthopanel.py @@ -122,12 +122,14 @@ class OrthoPanel(canvaspanel.CanvasPanel): """ - def __init__(self, parent, overlayList, displayCtx): + def __init__(self, parent, overlayList, displayCtx, addToolbars=True): """Create an ``OrthoPanel``. :arg parent: The :mod:`wx` parent. :arg overlayList: An :class:`.OverlayList` instance. :arg displayCtx: A :class:`.DisplayContext` instance. + :arg addToolbars: If ``False``, the toolbars (listed above) are not + added. Defaults to ``True``. """ sceneOpts = orthoopts.OrthoOpts() @@ -246,7 +248,7 @@ class OrthoPanel(canvaspanel.CanvasPanel): # The ViewPanel AuiManager seems to # struggle if we add these toolbars # immediately, so we'll do it asynchronously - def addToolbars(): + def _addToolbars(): self.togglePanel(overlaydisplaytoolbar.OverlayDisplayToolBar, viewPanel=self) self.togglePanel(orthotoolbar.OrthoToolBar, @@ -254,7 +256,8 @@ class OrthoPanel(canvaspanel.CanvasPanel): self.togglePanel(orthoedittoolbar.OrthoEditToolBar, ortho=self) - wx.CallAfter(addToolbars) + if addToolbars: + wx.CallAfter(_addToolbars) def destroy(self): @@ -812,7 +815,10 @@ class OrthoFrame(wx.Frame): ctx, dummyCanvas = fslgl.getWXGLContext() fslgl.bootstrap() - self.panel = OrthoPanel(self, overlayList, displayCtx) + self.panel = OrthoPanel(self, + overlayList, + displayCtx, + addToolbars=False) self.Layout() if dummyCanvas is not None: @@ -852,7 +858,10 @@ class OrthoDialog(wx.Dialog): ctx, dummyCanvas = fslgl.getWXGLContext() fslgl.bootstrap() - self.panel = OrthoPanel(self, overlayList, displayCtx) + self.panel = OrthoPanel(self, + overlayList, + displayCtx, + addToolbars=False) self.Layout() if dummyCanvas is not None: diff --git a/fsl/tools/bet.py b/fsl/tools/bet.py index f5f54f22b..7d026c910 100644 --- a/fsl/tools/bet.py +++ b/fsl/tools/bet.py @@ -4,10 +4,12 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module implements a GUI front end to the FSL BET tool. """ -from collections import OrderedDict +import numpy as np import props +props.initGUI() # The colour maps module must be initialised # before the displaycontext module can be loaded @@ -16,26 +18,33 @@ colourmaps.init() import fsl.data.image as fslimage import fsl.utils.transform as transform +import fsl.fsleyes.overlay as fsloverlay import fsl.fsleyes.displaycontext as displaycontext import fsl.fsleyes.gl as fslgl -runChoices = OrderedDict(( + +runChoices = [' ', '-R', '-S', '-B', '-Z', '-F', '-A', '-A2'] + + +runChoiceLabels = { # This is a bit silly, but we can't use an empty # string as a key here, due to the way that props # handles empty strings. - (' ', 'Run standard brain extraction using bet2'), - ('-R', 'Robust brain centre estimation (iterates bet2 several times)'), - ('-S', 'Eye & optic nerve cleanup (can be useful in SIENA)'), - ('-B', 'Bias field & neck cleanup (can be useful in SIENA)'), - ('-Z', 'Improve BET if FOV is very small in Z'), - ('-F', 'Apply to 4D FMRI data'), - ('-A', 'Run bet2 and then betsurf to get additional skull and scalp ' - 'surfaces'), - ('-A2', 'As above, when also feeding in non-brain extracted T2'))) + ' ' : 'Run standard brain extraction using bet2', + '-R' : 'Robust brain centre estimation (iterates bet2 several times)', + '-S' : 'Eye & optic nerve cleanup (can be useful in SIENA)', + '-B' : 'Bias field & neck cleanup (can be useful in SIENA)', + '-Z' : 'Improve BET if FOV is very small in Z', + '-F' : 'Apply to 4D FMRI data', + '-A' : 'Run bet2 and then betsurf to get additional skull and scalp ' + 'surfaces', + '-A2' : 'As above, when also feeding in non-brain extracted T2'} class Options(props.HasProperties): + """The ``Options`` class contains all of the options required to run BET. + """ inputImage = props.FilePath( exists=True, @@ -65,8 +74,7 @@ class Options(props.HasProperties): def setOutputImage(self, value, valid, *a): - """ - When a (valid) input image file name is selected, the output + """When a (valid) input image file name is selected, the output image is set to the same name, with a suffix of '_brain'. """ @@ -76,8 +84,7 @@ class Options(props.HasProperties): def clearT2Image(self, value, *a): - """ - This is a bit of a hack. If the user provides an invalid value + """This is a bit of a hack. If the user provides an invalid value for the T2 image (when running bet with the -A2 flag), but then changes their run choice to something other than -A2 (meaning that the invalid T2 image won't actually be used, so the fact @@ -90,17 +97,14 @@ class Options(props.HasProperties): def __init__(self): - """ - Adds a few callback listeners for various bits of behaviour. - """ + """Adds a few callback listeners for various bits of behaviour. """ self.addListener('inputImage', 'setOutputImage', self.setOutputImage) self.addListener('runChoice', 'clearT2Image', self.clearT2Image) def genBetCmd(self): - """ - Generates a command line call to the bet shell script, from + """Generates a command line call to the bet shell script, from the current option values. """ @@ -185,8 +189,7 @@ optTooltips = { def selectHeadCentre(opts, button): - """ - Pops up a little dialog window allowing the user to interactively + """Pops up a little dialog window allowing the user to interactively select the head centre location. """ import wx @@ -197,17 +200,19 @@ def selectHeadCentre(opts, button): fslgl.bootstrap() image = fslimage.Image(opts.inputImage) - imageList = fslimage.ImageList([image]) + imageList = fsloverlay.OverlayList([image]) displayCtx = displaycontext.DisplayContext(imageList) - display = displayCtx.getDisplay(image) + display = displayCtx.getDisplay(image, overlayType='volume') + volOpts = display.getDisplayOpts() parent = button.GetTopLevelParent() frame = orthopanel.OrthoDialog(parent, imageList, displayCtx, opts.inputImage, - style=wx.RESIZE_BORDER) - v2dMat = display.getTransform('voxel', 'display') - d2vMat = display.getTransform('display', 'voxel') + style=(wx.DEFAULT_DIALOG_STYLE | + wx.RESIZE_BORDER)) + v2dMat = volOpts.getTransform('voxel', 'display') + d2vMat = volOpts.getTransform('display', 'voxel') # Whenever the x/y/z coordinates change on # the ortho panel, update the option values. @@ -233,6 +238,10 @@ def selectHeadCentre(opts, button): voxCoords = [opts.xCoordinate, opts.yCoordinate, opts.zCoordinate] + + if voxCoords == [0, 0, 0]: + voxCoords = np.array(image.shape[:3]) / 2 + worldCoords = transform.transform([voxCoords], v2dMat)[0] displayCtx.location = worldCoords @@ -249,7 +258,7 @@ betView = props.NotebookGroup(( 'inputImage', 'outputImage', 'fractionalIntensity', - 'runChoice', + props.Widget('runChoice', labels=runChoiceLabels), props.Widget('t2Image', visibleWhen=lambda i: i.runChoice == '-A2') )), props.VGroup( @@ -276,6 +285,7 @@ betView = props.NotebookGroup(( def interface(parent, args, opts): + """Generates the BET gui. """ import wx @@ -290,6 +300,7 @@ def interface(parent, args, opts): def runBet(parent, opts): + """Runs BET using the specified options. """ import fsl.utils.runwindow as runwindow import fsl.fsleyes.views.orthopanel as orthopanel @@ -304,8 +315,7 @@ def runBet(parent, opts): inImage = fslimage.Image(opts.inputImage) outImage = fslimage.Image(opts.outputImage) - imageList = fslimage.ImageList([inImage, outImage]) - + imageList = fsloverlay.OverlayList([inImage, outImage]) displayCtx = displaycontext.DisplayContext(imageList) outDisplay = displayCtx.getDisplay(outImage) outOpts = outDisplay.getDisplayOpts() diff --git a/fsl/tools/feat.py b/fsl/tools/feat.py index e430d3a46..759335e5a 100644 --- a/fsl/tools/feat.py +++ b/fsl/tools/feat.py @@ -1,81 +1,124 @@ #!/usr/bin/env python # -# feat.py - +# feat.py - FEAT front end. # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module implements a non-functional front end to the FSL FEAT tool. """ -from collections import OrderedDict - import props - -analysisTypeOpts = OrderedDict(( - ('firstLevel', 'First-level analysis'), - ('highLevel', 'Higher-level analysis'))) - -analysisStageOpts = OrderedDict(( - ('full', 'Full analysis'), - ('pre', 'Pre-stats'), - ('pre-stats', 'Pre-stats + Stats'), - ('stats', 'Stats'), - ('stats-post', 'Stats + Post-stats'), - ('post', 'Post-stats'), - ('reg', 'Registration only'))) - -highLevelInputTypes = OrderedDict(( - ('featDirs', 'Inputs are lower-level FEAT directories'), - ('copeImages', 'Inputs are 3D cope images from FEAT directories'))) - -sliceTimingOpts = OrderedDict(( - ('none', 'None'), - ('regup', 'Regular up (0, 1, 2, ..., n-1)'), - ('regdown', 'Regular down (n-1, n-2, ..., 0'), - ('int', 'Interleaved (0, 2, 4, ..., 1, 3, 5, ...)'), - ('orderFile', 'Use slice order file'), - ('timingFile', 'Use slice timings file'))) - -perfusionOpts = OrderedDict(( - ('tag', 'First timepoint is tag'), - ('control', 'First timepoint is control'))) - -motionParameterOpts = OrderedDict(( - ('none', "Don't Add Motion Parameters"), - ('standard', "Standard Motion Parameters"), - ('extended', "Standard + Extended Motion Parameters"))) - -effectModellingOpts = OrderedDict(( - ('fixed', 'Fixed effects'), - ('ols', 'Mixed effects: Simple OLS'), - ('flame1', 'Mixed effects: FLAME 1'), - ('flame2', 'Mixed effects: FLAME 1+2'))) - -zRenderingOpts = OrderedDict(( - ('actual', 'Use actual Z min/max'), - ('preset', 'Use preset Z min/max'))) - -blobOpts = OrderedDict(( - ('solid', 'Solid blobs'), - ('transparent', 'Transparent blobs'))) - -regSearchOpts = OrderedDict(( - ('none', 'No search'), - ('normal', 'Normal search'), - ('full', 'Full search'))) - -regDofOpts = OrderedDict(( - ('3', '3 DOF'), - ('6', '6 DOF'), - ('7', '7 DOF'), - ('9', '9 DOF'), - ('12', '12 DOF'))) - -regStructDofOpts = OrderedDict(( - ('3', '3 DOF'), - ('6', '6 DOF'), - ('7', '7 DOF'), - ('BBR', 'BBR'), - ('12', '12 DOF'))) +props.initGUI() + +analysisTypeOpts = ['firstLevel', + 'highLevel'] +analysisTypeOptLabels = { + 'firstLevel' : 'First-level analysis', + 'highLevel' : 'Higher-level analysis'} + +analysisStageOpts = ['full', + 'pre', + 'pre-stats', + 'stats', + 'stats-post', + 'post', + 'reg'] +analysisStageOptLabels = { + 'full' : 'Full analysis', + 'pre' : 'Pre-stats', + 'pre-stats' : 'Pre-stats + Stats', + 'stats' : 'Stats', + 'stats-post' : 'Stats + Post-stats', + 'post' : 'Post-stats', + 'reg' : 'Registration only'} + +highLevelInputTypes = ['featDirs', + 'copeImages'] +highLevelInputTypeLabels = { + 'featDirs' : 'Inputs are lower-level FEAT directories', + 'copeImages' : 'Inputs are 3D cope images from FEAT directories'} + +sliceTimingOpts = ['none', + 'regup', + 'regdown', + 'int', + 'orderFile', + 'timingFile'] +sliceTimingOptLabels = { + 'none' : 'None', + 'regup' : 'Regular up (0, 1, 2, ..., n-1)', + 'regdown' : 'Regular down (n-1, n-2, ..., 0', + 'int' : 'Interleaved (0, 2, 4, ..., 1, 3, 5, ...)', + 'orderFile' : 'Use slice order file', + 'timingFile' : 'Use slice timings file'} + +perfusionOpts = ['tag', + 'control'] +perfusionOptLabels = { + 'tag' : 'First timepoint is tag', + 'control' : 'First timepoint is control'} + +motionParameterOpts = ['none', + 'standard', + 'extended'] +motionParameterOptLabels = { + 'none' : "Don't Add Motion Parameters", + 'standard' : "Standard Motion Parameters", + 'extended' : "Standard + Extended Motion Parameters"} + +effectModellingOpts = ['fixed', + 'ols', + 'flame1', + 'flame2'] +effectModellingOptLabels = { + 'fixed' : 'Fixed effects', + 'ols' : 'Mixed effects: Simple OLS', + 'flame1' : 'Mixed effects: FLAME 1', + 'flame2' : 'Mixed effects: FLAME 1+2'} + +zRenderingOpts = ['actual', + 'preset'] +zRenderingOptLabels = { + 'actual' : 'Use actual Z min/max', + 'preset' : 'Use preset Z min/max'} + +blobOpts = ['solid', + 'transparent'] +blobOptLabels = { + 'solid' : 'Solid blobs', + 'transparent' : 'Transparent blobs'} + +regSearchOpts = ['none', + 'normal', + 'full'] +regSearchOptLabels = { + 'none' : 'No search', + 'normal' : 'Normal search', + 'full' : 'Full search'} + +regDofOpts = ['3', + '6', + '7', + '9', + '12'] +regDofOptLabels = { + '3' : '3 DOF', + '6' : '6 DOF', + '7' : '7 DOF', + '9' : '9 DOF', + '12' : '12 DOF'} + +regStructDofOpts = ['3', + '6', + '7', + 'BBR', + '12'] +regStructDofOptLabels = { + '3' : '3 DOF', + '6' : '6 DOF', + '7' : '7 DOF', + 'BBR' : 'BBR', + '12' : '12 DOF'} class Options(props.HasProperties): @@ -358,7 +401,7 @@ dataView = props.VGroup( props.VGroup( visibleWhen=lambda i: i.analysisType == 'highLevel', children=( - 'inputDataType', + props.Widget('inputDataType', labels=highLevelInputTypeLabels), props.Widget( 'higherLevelFeatInput', visibleWhen=lambda i: i.inputDataType == 'featDirs'), @@ -385,7 +428,7 @@ prestatsView = props.VGroup( 'b0_TE', 'b0_unwarpDir', 'b0_signalLossThreshold')), - 'sliceTimingCorrection', + props.Widget('sliceTimingCorrection', labels=sliceTimingOptLabels), props.Widget( 'sliceTimingFile', visibleWhen=lambda i: i.sliceTimingCorrection == 'timingFile'), @@ -401,6 +444,7 @@ prestatsView = props.VGroup( children=( 'perfusionSubtraction', props.Widget('perfusionOption', + labels=perfusionOptLabels, visibleWhen=lambda i: i.perfusionSubtraction))), 'temporalHighpass', 'melodic')) @@ -410,7 +454,7 @@ statsView = props.VGroup( enabledWhen=lambda i: tabEnabled(i, 'Stats'), children=( 'useFILMPrewhitening', - 'addMotionParameters', + props.Widget('addMotionParameters', labels=motionParameterOptLabels), 'voxelwiseConfoundList', 'applyExternalScript', 'addAdditionalConfounds', @@ -442,14 +486,14 @@ postStatsView = props.VGroup( border=True, visibleWhen=lambda i: i.thresholding != 'None', children=( - 'renderZMinMax', + props.Widget('renderZMinMax', labels=zRenderingOptLabels), props.Widget( 'renderZMin', visibleWhen=lambda i: i.renderZMinMax == 'preset'), props.Widget( 'renderZMax', visibleWhen=lambda i: i.renderZMinMax == 'preset'), - 'blobTypes')))) + props.Widget('blobTypes', labels=blobOptLabels))))) regView = props.VGroup( label='Registration', @@ -460,7 +504,9 @@ regView = props.VGroup( label='Functional -> Expanded functional', border=True, visibleWhen=lambda i: i.expandedFunctionalImage is not None, - children=('functionalSearch', 'functionalDof')), + children=( + props.Widget('functionalSearch', labels=regSearchOptLabels), + props.Widget('functionalDof', labels=regDofOptLabels))), 'mainStructuralImage', props.HGroup( label='Functional -> Structural', @@ -473,7 +519,9 @@ regView = props.VGroup( border=True, visibleWhen=lambda i: i.standardSpaceImage is not None, children=( - props.HGroup(('standardSearch', 'standardDof')), + props.HGroup(( + props.Widget('standardSearch', labels=regSearchOptLabels), + props.Widget('standardDof', labels=regDofOptLabels))), props.HGroup(( 'nonLinearReg', props.Widget( @@ -482,8 +530,9 @@ regView = props.VGroup( featView = props.VGroup(( - 'analysisType', + props.Widget('analysisType', labels=analysisTypeOptLabels), props.Widget('analysisStages', + labels=analysisStageOptLabels, enabledWhen=lambda i: i.analysisType == 'firstLevel'), props.NotebookGroup(( miscView, diff --git a/fsl/tools/flirt.py b/fsl/tools/flirt.py index 20f497913..609252fa2 100644 --- a/fsl/tools/flirt.py +++ b/fsl/tools/flirt.py @@ -4,49 +4,76 @@ # # Author: Paul McCarthy <pauldmccarthy@gmail.com> # +"""This module implements a non-functional front end to the FSL flirt tool. +""" -from collections import OrderedDict import props - -flirtModes = OrderedDict(( - ('single', 'Input image -> Reference image'), - ('multiple', 'Low res image -> High res image -> Reference image'))) - - -modelDOFChoices = OrderedDict(( - ('rigid3', 'Rigid Body (3 parameter model)'), - ('translate', 'Translation Only (3 parameter model)'), - ('rigid6', 'Rigid Body (6 parameter model)'), - ('rescale', 'Global Rescale (7 parameter model)'), - ('trad', 'Traditional (9 parameter model)'), - ('affine', 'Affine (12 parameter model)'))) - - -searchModes = OrderedDict(( - ('nosearch', 'Already virtually aligned (no search)'), - ('orient', 'Not aligned, but same orientation'), - ('misorient', 'Incorrectly oriented'))) - - -costFunctions = OrderedDict(( - ('correlation', 'Correlation Ratio'), - ('mutualinfo', 'Mutual Information'), - ('normmutualinfo', 'Normalised Mutual Information'), - ('normcorrelation', 'Normalised Correlation (intra-modal)'), - ('leastsquares', 'Least Squares (intra-modal)'))) - -interpolationMethods = OrderedDict(( - ('trilinear', 'Tri-Linear'), - ('nn', 'Nearest Neighbour'), - ('spline', 'Spline'), - ('sinc', 'Sinc'))) - - -sincWindowOpts = OrderedDict(( - ('rect', 'Rectangular'), - ('hanning', 'Hanning'), - ('blackman', 'Blackman'))) +props.initGUI() + +flirtModes = ['single', + 'multiple'] +modelDOFChoices = ['rigid3', + 'translate', + 'rigid6', + 'rescale', + 'trad', + 'affine'] +searchModes = ['nosearch', + 'orient', + 'misorient'] +costFunctions = ['correlation', + 'mutualinfo', + 'normmutualinfo', + 'normcorrelation', + 'leastsquares'] +interpolationMethods = ['trilinear', + 'nn', + 'spline', + 'sinc'] +sincWindowOpts = ['rect', + 'hanning', + 'blackman'] + + +flirtModeLabels = { + 'single' : 'Input image -> Reference image', + 'multiple' : 'Low res image -> High res image -> Reference image'} + + +modelDOFChoiceLabels = { + 'rigid3' : 'Rigid Body (3 parameter model)', + 'translate' : 'Translation Only (3 parameter model)', + 'rigid6' : 'Rigid Body (6 parameter model)', + 'rescale' : 'Global Rescale (7 parameter model)', + 'trad' : 'Traditional (9 parameter model)', + 'affine' : 'Affine (12 parameter model)'} + + +searchModeLabels = { + 'nosearch' : 'Already virtually aligned (no search)', + 'orient' : 'Not aligned, but same orientation', + 'misorient' : 'Incorrectly oriented'} + + +costFunctionLabels = { + 'correlation' : 'Correlation Ratio', + 'mutualinfo' : 'Mutual Information', + 'normmutualinfo' : 'Normalised Mutual Information', + 'normcorrelation' : 'Normalised Correlation (intra-modal)', + 'leastsquares' : 'Least Squares (intra-modal)'} + +interpolationMethodLabels = { + 'trilinear' : 'Tri-Linear', + 'nn' : 'Nearest Neighbour', + 'spline' : 'Spline', + 'sinc' : 'Sinc'} + + +sincWindowOptLabels = { + 'rect' : 'Rectangular', + 'hanning' : 'Hanning', + 'blackman' : 'Blackman'} def inSingleMode( opts): return opts.flirtMode == 'single' @@ -101,7 +128,7 @@ tooltips = None searchOptions = props.VGroup( label='Search', children=( - 'searchMode', + props.Widget('searchMode', labels=searchModeLabels), props.HGroup(('searchAngleXMin', 'searchAngleXMax'), visibleWhen=lambda i: i.searchMode != 'nosearch'), props.HGroup(('searchAngleYMin', 'searchAngleYMax'), @@ -112,7 +139,7 @@ searchOptions = props.VGroup( costFuncOptions = props.VGroup( label='Cost Function', children=( - 'costFunction', + props.Widget('costFunction', labels=costFunctionLabels), props.Widget( 'costHistBins', visibleWhen=lambda i: i.costFunction in ['correlation', @@ -122,8 +149,9 @@ costFuncOptions = props.VGroup( interpOptions = props.VGroup( label='Interpolation', children=( - 'interpolation', - props.VGroup(('sincWindow', 'sincWindowWidth'), + props.Widget('interpolation', labels=interpolationMethodLabels), + props.VGroup((props.Widget('sincWindow', labels=sincWindowOptLabels), + 'sincWindowWidth'), visibleWhen=lambda i: i.interpolation == 'sinc'))) weightVolOptions = props.VGroup( @@ -133,14 +161,20 @@ weightVolOptions = props.VGroup( 'weightingInput')) flirtView = props.VGroup(( - 'flirtMode', + props.Widget('flirtMode', labels=flirtModeLabels), 'refImage', - props.Widget('inputImage', visibleWhen=inSingleMode), - props.Widget('inToRefMode', visibleWhen=inSingleMode), + props.Widget('inputImage', visibleWhen=inSingleMode), + props.Widget('inToRefMode', + labels=modelDOFChoiceLabels, + visibleWhen=inSingleMode), props.Widget('hiResImage', visibleWhen=inMultipleMode), - props.Widget('hiToRefMode', visibleWhen=inMultipleMode), + props.Widget('hiToRefMode', + labels=modelDOFChoiceLabels, + visibleWhen=inMultipleMode), props.Widget('loResImage', visibleWhen=inMultipleMode), - props.Widget('loToHiMode', visibleWhen=inMultipleMode), + props.Widget('loToHiMode', + labels=modelDOFChoiceLabels, + visibleWhen=inMultipleMode), 'outputImage', 'sndyImages', props.NotebookGroup(label='Advanced Options', -- GitLab