Something went wrong on our end
Forked from
FSL / fslpy
5067 commits behind the upstream repository.
-
Paul McCarthy authored
Refactored runwindow.py a bit, made code cleaner. The ProcessManger thread class may even be better off as a separate module, as it will probably grow to be quite important.
Paul McCarthy authoredRefactored runwindow.py a bit, made code cleaner. The ProcessManger thread class may even be better off as a separate module, as it will probably grow to be quite important.
bet.py 9.65 KiB
#!/usr/bin/env python
#
# bet.py - Front end to the FSL BET tool.
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
import os
import sys
from collections import OrderedDict
import wx
import nibabel as nb
import fsl.props as props
import fsl.data.imagefile as imagefile
import fsl.utils.runwindow as runwindow
import fsl.utils.imageview as imageview
runChoices = OrderedDict((
# 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')))
class Options(props.HasProperties):
inputImage = props.FilePath(exists=True, suffixes=imagefile._allowedExts, required=True)
outputImage = props.FilePath( required=True)
t2Image = props.FilePath(exists=True, suffixes=imagefile._allowedExts, required=lambda i: i.runChoice == '-A2')
runChoice = props.Choice(runChoices)
outputExtracted = props.Boolean(default=True)
outputMaskImage = props.Boolean(default=False)
outputSkull = props.Boolean(default=False)
outputSurfaceOverlay = props.Boolean(default=False)
outputMesh = props.Boolean(default=False)
thresholdImages = props.Boolean(default=False)
fractionalIntensity = props.Double(default=0.5, minval=0.0, maxval=1.0)
thresholdGradient = props.Double(default=0.0, minval=-1.0, maxval=1.0)
headRadius = props.Double(default=0.0, minval=0.0)
xCoordinate = props.Double(default=0.0, minval=0.0)
yCoordinate = props.Double(default=0.0, minval=0.0)
zCoordinate = props.Double(default=0.0, minval=0.0)
def setOutputImage(self, value, valid, *a):
"""
When a (valid) input image file name is selected, the output
image is set to the same name, with a suffix of '_brain'.
"""
if not valid: return
value = imagefile.removeExt(value)
self.outputImage = value + '_brain'
def clearT2Image(self, value, *a):
"""
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
that it is invalid doesn't really matter), props will still
complain that the T2 image is invalid. So here, when the run
choice is changed to something other than -A2, the T2 image is
cleared, and props won't complain.
"""
if value != '-A2': self.t2Image = None
def __init__(self):
"""
Adds a few callback listeners for various bits of behaviour.
"""
Options.inputImage.addListener(
self, 'setOutputImage', self.setOutputImage)
Options.runChoice.addListener(
self, 'clearT2Image', self.clearT2Image)
def genBetCmd(self):
"""
Generates a command line call to the bet shell script, from
the current option values.
"""
errors = self.validateAll()
if len(errors) > 0:
raise ValueError('Options are not valid')
cmd = ['bet']
cmd.append(self.inputImage)
cmd.append(self.outputImage)
cmd.append('-v')
runChoice = self.runChoice
if runChoice.strip() != '':
cmd.append(runChoice)
if runChoice == '-A2':
cmd.append(self.t2Image)
if not self.outputExtracted: cmd.append('-n')
if self.outputMaskImage: cmd.append('-m')
if self.outputSkull: cmd.append('-s')
if self.outputSurfaceOverlay: cmd.append('-o')
if self.outputMesh: cmd.append('-e')
if self.thresholdImages: cmd.append('-t')
cmd.append('-f')
cmd.append('{}'.format(self.fractionalIntensity))
cmd.append('-g')
cmd.append('{}'.format(self.thresholdGradient))
if self.headRadius > 0.0:
cmd.append('-r')
cmd.append('{}'.format(self.headRadius))
if all((self.xCoordinate > 0.0,
self.yCoordinate > 0.0,
self.zCoordinate > 0.0)):
cmd.append('-c')
cmd.append('{}'.format(self.xCoordinate))
cmd.append('{}'.format(self.yCoordinate))
cmd.append('{}'.format(self.zCoordinate))
return cmd
optLabels = {
'inputImage' : 'Input image',
'outputImage' : 'Output image',
'runChoice' : 'Run options',
't2Image' : 'T2 image',
'outputExtracted' : 'Output brain-extracted image',
'outputMaskImage' : 'Output binary brain mask image',
'thresholdImages' : 'Apply thresholding to brain and mask image',
'outputSkull' : 'Output exterior skull surface image',
'outputMesh' : 'Generate brain surface as mesh in .vtk format',
'outputSurfaceOverlay' : 'Output brain surface overlaid onto original image',
'fractionalIntensity' : 'Fractional intensity threshold',
'thresholdGradient' : 'Threshold gradient',
'headRadius' : 'Head radius (mm)',
'centreCoords' : 'Centre coordinates (voxels)',
'xCoordinate' : 'X',
'yCoordinate' : 'Y',
'zCoordinate' : 'Z'
}
optTooltips = {
'fractionalIntensity' : 'Smaller values give larger brain outline estimates.',
'thresholdGradient' : 'Positive values give larger brain outline at bottom, smaller at top.',
'headRadius' : 'Initial surface sphere is set to half of this.',
'centreCoords' : 'Coordinates (voxels) for centre of initial brain surface sphere.'
}
def selectHeadCentre(opts, button):
"""
Pops up a little dialog window allowing the user to interactively
select the head centre location.
"""
image = nb.load(opts.inputImage)
parent = button.GetTopLevelParent()
frame = imageview.ImageFrame(parent, image.get_data(), opts.inputImage)
panel = frame.panel
panel.setLocation(
opts.xCoordinate,
opts.yCoordinate,
opts.zCoordinate)
# Whenever the x/y/z coordinates change on
# the Options object,update the dialog view.
def updateViewX(val, *a): panel.setXLocation(val)
def updateViewY(val, *a): panel.setYLocation(val)
def updateViewZ(val, *a): panel.setZLocation(val)
optListeners = (
('xCoordinate', 'updateViewX_{}'.format(id(panel)), updateViewX),
('yCoordinate', 'updateViewY_{}'.format(id(panel)), updateViewY),
('zCoordinate', 'updateViewZ_{}'.format(id(panel)), updateViewZ))
for listener in optListeners:
opts.addListener(*listener)
def rmListeners(ev):
for listener in optListeners:
prop = listener[0]
name = listener[1]
opts.removeListener(prop, name)
# Remove the listeners when the dialog is closed
frame.Bind(wx.EVT_WINDOW_DESTROY, rmListeners)
# And whenever the x/y/z coordinates change
# on the dialog, update the option values.
def updateOpts(ev):
opts.xCoordinate = ev.x
opts.yCoordinate = ev.y
opts.zCoordinate = ev.z
panel.Bind(imageview.EVT_LOCATION_EVENT, updateOpts)
# Position the dialog by the button that was clicked
pos = button.GetScreenPosition()
frame.SetPosition(pos)
frame.Show()
betView = props.NotebookGroup((
props.VGroup(
label='BET options',
children=(
'inputImage',
'outputImage',
'fractionalIntensity',
'runChoice',
props.Widget('t2Image', visibleWhen=lambda i: i.runChoice == '-A2')
)),
props.VGroup(
label='Advanced options',
children=(
'outputExtracted',
'outputMaskImage',
'thresholdImages',
'outputSkull',
'outputSurfaceOverlay',
'outputMesh',
'thresholdGradient',
'headRadius',
props.HGroup(
key='centreCoords',
children=(
props.Button(text='Select',
callback=selectHeadCentre,
enabledWhen=lambda i: i.isValid('inputImage')),
'xCoordinate',
'yCoordinate',
'zCoordinate'))))))
def interface(parent, opts):
return props.buildGUI(
parent, opts, betView, optLabels, optTooltips)
def runBet(parent, opts):
def onFinish(window, exitCode):
if exitCode != 0: return
image = nb.load(imagefile.addExt(opts.outputImage))
frame = imageview.ImageFrame(window,
image.get_data(),
title=opts.outputImage)
frame.Show()
runwindow.checkAndRun('BET', opts, parent, Options.genBetCmd,
optLabels=optLabels,
onFinish=onFinish,
modal=False)
FSL_TOOLNAME = 'BET'
FSL_HELPPAGE = 'bet'
FSL_OPTIONS = Options
FSL_INTERFACE = interface
FSL_RUNTOOL = runBet