Commit 64cd57c6 authored by Taylor Hanayik's avatar Taylor Hanayik
Browse files

Merge branch 'props' into 'master'

Props

See merge request !2
parents edb0d0f1 acee8809
......@@ -5,4 +5,6 @@ __pycache__/
fslgui.egg-info/
run.sh
/mockups
/build
/dist
.vscode/
"""
core functions for setting up and controlling GUIs
"""
import os
import sys
import json
import wx
import yaml
import fsleyes_props as props
import fsl.gui.exceptions as fslerrs
import fsl.gui.views as fslviews
import fsl.gui.widgets as fslwidgets
allowedContainerWidgets = (
'column', 'row', 'group',
'page', 'notebook'
)
allowedWidgets = (
*allowedContainerWidgets,
'filepath', 'checkbox', 'choice',
'number', 'button', 'FsleyesImage'
)
allowedConfigKeys = (
'appName', 'windowSize', 'width',
'height', 'layout',
)
allowedKeys = (
*allowedConfigKeys,
*allowedWidgets,
)
def isGroupKey(key):
"""
returns True is key == "group"
returns False otherwise
"""
if key == "group":
return True
else:
return False
def isPageKey(key):
"""
returns True is key == "group"
returns False otherwise
"""
if key == "page":
return True
else:
return False
def isContainerKey(key):
"""
returns True if this key is associated with a container widget
from allowedContainerWidgets.
returns False otherwise
a container widget is not really intereactive for the user.
it holds other widgets as children
"""
if key in allowedContainerWidgets:
return True
else:
return False
def loadSpec(specFile):
"""
return the loaded yaml data as a dict
"""
with open(specFile) as sf:
spec = yaml.load(sf, Loader=yaml.FullLoader)
return spec
def checkSpec(buildSpec):
"""
make sure the build spec contains only expected fields
"""
def dictCheck(d):
if type(d) is list:
for entry in d:
print(entry)
for k, v in entry.items():
k, _ = parseWidgetKey(k)
if k not in allowedKeys:
raise fslerrs.NotAValidKey("{} is not an allowed buildSpec key".format(k))
if type(v) is dict:
dictCheck(v)
dictCheck(buildSpec)
def layoutFrom(widget):
"""
redo layout of all widgets up the parent tree from this widget.
Stop when we get to a frame. This was taken from a wx wiki post
"""
while widget.GetParent():
widget.Layout()
widget = widget.GetParent()
widget.Layout()
if widget.IsTopLevel():
break
def parseWidgetKey(key):
"""
returns string tuple (key, tag)
the string "key" must not contain more than one underscore
"""
parts = key.split(sep="_")
t = ""
if len(parts) == 1:
n = parts[0]
elif len(parts) == 2:
n = parts[0]
t = parts[-1]
elif len(parts) > 2:
raise Exception("keys must be in the forms 'key' or 'key_name'")
else:
n = ""
return n, t
def isGroup(widget):
if isinstance(widget.GetSizer(), wx.StaticBoxSizer):
return True
else:
return False
def isNotebook(widget):
if isinstance(widget, wx.Notebook):
return True
else:
return False
def isPage(widget):
if isinstance(widget.GetParent(), wx.Notebook):
return True
else:
return False
def addWidgetToGroup(parent, widget):
w = widget(parent.GetSizer().GetStaticBox())
return w
def addPageToNotebook(parent, widget, name):
parent.AddPage(widget, name)
return widget
def widgetFromKey(key):
"""
returns the appropriate widget creation function,
but does not return the actual widget instance.
That comes later.
"""
widget = getattr(fslwidgets, key)
return widget
def makeWidget(parent, propObj, key, tag, value):
"""
returns the appropriate widget from a key string
key: str parsed from the form "key_tag" or "key"
tag: str parsed from the form "key_tag"
value: the dict value for this key. Only value==dict is used here
"""
if key in allowedWidgets:
wid = widgetFromKey(key)
print('making widget: ', key)
if isinstance(value, dict):
w = wid(parent, propObj, **value)
else:
if isGroupKey(key):
w = wid(parent, tag)
else:
if isGroup(parent):
w = addWidgetToGroup(parent, wid)
elif isNotebook(parent):
w = wid(parent)
w = addPageToNotebook(parent, w, tag)
else:
w = wid(parent)
return w
def layout(parent, buildSpec, propObj):
"""
layout all allowed widgets in a buildSpec
"""
for entry in buildSpec:
for k, v in entry.items():
k, t = parseWidgetKey(k)
w = makeWidget(parent, propObj, k, t, v,)
if not isPage(w):
parent.GetSizer().Add(w, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
layoutFrom(w)
if isContainerKey(k):
if type(v) is list:
layout(w, v, propObj)
parent.Layout()
return parent
def buildGUI(buildSpec, propObj):
"""
build a GUI from a build spec dictionary (or JSON)
"""
checkSpec(buildSpec)
mainWin = wx.Frame(None)
mainWin.SetTitle(buildSpec['appName'])
mainWin.SetSize((buildSpec['windowSize']['width'], buildSpec['windowSize']['height']))
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainWin.SetSizer(mainSizer)
mainWin = layout(mainWin, buildSpec['layout'], propObj)
mainWin.Centre()
mainWin.Show()
return mainWin
"""
custom exceptions to be used in fslgui
"""
class NotAValidKey(Exception):
pass
class MissingRequiredKey(Exception):
pass
......@@ -9,6 +9,9 @@ import random
import wx
from fsl.data import image
import fsleyes_props as props
#from fsleyes_props.build import buildGUI
props.initGUI()
import fsl.gui.views as fslviews
import fsl.gui.tools as fsltools
......@@ -210,4 +213,7 @@ class FlirtGui(BaseGui):
else:
self.view.input.set_label("Input image*")
self.view.input_lowres.Hide()
self._layout_from(self.view.input_lowres)
\ No newline at end of file
self._layout_from(self.view.input_lowres)
#!/usr/bin/env python
import wx
import sys
from fsl.gui.guis import BetGui
import argparse
from collections import OrderedDict
import wx
import yaml
from fsl.utils import idle
import fsleyes_props as props
import fsl.gui.core as core
import fsl.gui.views as fslViews
import fsl.gui.tools as fslTools
class Bet(props.HasProperties):
"""
Bet is the data model for Bet Gui
"""
betRunChoices = OrderedDict((
('default', 'Default Brain extraction'),
('robust', 'Robust'),
('eyeClean', 'Eye and optic nerve cleanup'),
('biasAndNeckClean', 'Bias field and neck Clean'),
('smallZ', 'Brain has small Z FOV'),
('4d', 'Apply to 4D fMRI'),
('withBetSurf', 'Also run betsurf'),
('withBetSurfT2', 'Run betsurf with additional T2 image')
))
inputFile = props.FilePath(required=True, exists=True)
outputFile = props.FilePath(required=True, exists=False)
fval = props.Real(precision=0.001, minval=0, maxval=1, clamped=True)
betRunType = props.Choice(choices=list(betRunChoices.values()))
parser = argparse.ArgumentParser(description="FSL's brain extraction tool")
def __init__(
self,
**kwargs
):
super().__init__(**kwargs)
def main():
args = parser.parse_args()
# get an app instance
app = wx.App()
frame = wx.Frame(None, size=(800, 600))
sizer = wx.BoxSizer(wx.VERTICAL)
betgui = BetGui(frame, "BET")
sizer.Add(betgui.view, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
frame.SetSizer(sizer)
frame.Centre()
frame.Show()
bet = Bet()
betView = core.loadSpec(fslViews.bet)
gui = core.buildGUI(betView, bet)
app.MainLoop()
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python
import sys
import argparse
from collections import OrderedDict
import wx
import yaml
from fsl.utils import idle
import fsleyes_props as props
import fsl.gui.core as core
import fsl.gui.views as fslViews
import fsl.gui.tools as fslTools
def run(p, button):
print('RUN COMMAND: ')
print(p.command())
class Pnm(props.HasProperties):
"""
Pnm is the data model for PnmGui
"""
physioFile = props.FilePath(required=True, exists=True)
imageFile = props.FilePath(required=True, exists=True)
sliceOrderChoices = OrderedDict((
('up', 'Up'),
('down', 'Down'),
('iup', 'Interleaved Up'),
('idown', 'Interleaved Down'),
('file', 'User Specified File')
))
scannerSliceDirChoices = OrderedDict((
('x', 'X'),
('y', 'Y'),
('z', 'Z')
))
idxCardiac = props.Int(minval=0, slider=False)
idxResp = props.Int(minval=0, slider=False)
idxTrig = props.Int(minval=0, slider=False)
pulseOxTrig = props.Boolean()
sampleRate = props.Int(minVal=0, slider=False)
tr = props.Real(precision=0.01, slider=False)
sliceOrder = props.Choice(choices=list(sliceOrderChoices.values()))
sliceDir = props.Choice(choices=list(scannerSliceDirChoices.values()))
sliceTimeFile = props.FilePath(required=False, exists=True)
outputFile = props.FilePath(required=True, exists=False)
orderCardiacEV = props.Int(minVal=0, slider=False)
orderRespEV = props.Int(minVal=0, slider=False)
orderCardiacIntEV = props.Int(minVal=0, slider=False)
orderRespIntEV = props.Int(minVal=0, slider=False)
rvt = props.Boolean()
heartRate = props.Boolean()
csf = props.Boolean()
csfFile = props.FilePath(required=False, exists=True)
cardiacSmoothSec = props.Real(precision=0.001, slider=False)
respSmoothSec = props.Real(precision=0.001, slider=False)
hrSmoothSec = props.Real(precision=0.001, slider=False)
rvtSmoothSec = props.Real(precision=0.001, slider=False)
applyCleanup = props.Boolean()
invertRespTrace = props.Boolean()
invertCardiacTrace = props.Boolean()
runButton = props.Button(text='Run', callback=run)
def command(self):
return "pnm_stage1 blah blah blah"
def __init__(
self,
**kwargs
):
super().__init__(**kwargs)
def main():
# get an app instance
app = wx.App()
pnm = Pnm()
pnmView = core.loadSpec(fslViews.pnm)
gui = core.buildGUI(pnmView, pnm)
app.MainLoop()
if __name__ == "__main__":
sys.exit(main())
......@@ -8,9 +8,11 @@ tools are models that store data and allow FSL tools to be called from other pyt
# Author: Taylor Hanayik <hanayik@gmail.com>
import os
import subprocess
from collections import OrderedDict
from fsl.utils import idle
from fsl.utils import idle
import fsleyes_props as props
class Bet(object):
......@@ -318,3 +320,5 @@ class Flirt(object):
search_zmax=90
):
pass
---
# the pnm view layout specification
appName: BET
windowSize:
width: 800
height: 500
layout:
- notebook:
- page_Inputs:
- group_Files:
- column:
- filepath: {label: Input file, propName: inputFile}
- filepath: {label: Output file, propName: outputFile}
- row:
- number: {label: F value, propName: fval, showLimits: 0}
- column:
- FsleyesImage: {name: FsleyesImage}
- page_Options:
- column:
- choice: {label: BET run type, propName: betRunType}
\ No newline at end of file
---
# the pnm view layout specification
appName: PNM
windowSize:
width: 800
height: 840
layout:
- notebook:
- page_Setup:
- group_Input:
- column:
- filepath: {label: Physio file, propName: physioFile}
- filepath: {label: Image file, propName: imageFile}
- group_Format:
- row:
- number: {label: Caridac column, propName: idxCardiac}
- number: {label: Respitory column, propName: idxResp}
- number: {label: Scan Trig column, propName: idxTrig}
- row:
- checkbox: {label: Pulse Ox Trig, propName: pulseOxTrig}
- number: {label: Sample Rate (Hz), propName: sampleRate}
- number: {label: TR (sec), propName: tr}
- row:
- choice: {label: Slice Order, propName: sliceOrder}
- filepath: {label: Slice time file, propName: sliceTimeFile}
- choice: {label: Slice Direction, propName: sliceDir}
- group_Output:
- column:
- filepath: {label: Output file, propName: outputFile}
- group_EVs:
- row:
- number: {label: Cardiac EV order, propName: orderCardiacEV}
- number: {label: Resp EV order, propName: orderRespEV}
- number: {label: Cariac interaction EV order, propName: orderCardiacIntEV}
- number: {label: Resp interaction EV order, propName: orderRespIntEV}
- row:
- checkbox: {label: RVT, propName: rvt}
- checkbox: {label: Heart Rate, propName: heartRate}
- checkbox: {label: CSF, propName: csf}
- column:
- filepath: {label: CSF mask, propName: csfFile}
- page_Advanced:
- row:
- number: {label: Cardiac smooth (sec), propName: cardiacSmoothSec}
- number: {label: Resp smooth (sec), propName: respSmoothSec}
- number: {label: HR smooth (sec), propName: hrSmoothSec}
- number: {label: RVT smooth (sec), propName: rvtSmoothSec}
- row:
- checkbox: {label: Clean up, propName: applyCleanup}
- checkbox: {label: Invert resp trace, propName: invertRespTrace}
- checkbox: {label: Invert cardiac trace, propName: invertCardiacTrace}
- page_Plots:
- row:
- button: {label: Run, propName: runButton}
\ No newline at end of file
#!/usr/bin/env python
#
# bet_view.py
#
# Author: Taylor Hanayik <hanayik@gmail.com>
"""
views are the graphical layouts of windows.
User interaction is controlled by guis.py.
Data is stored and updated using tools.py
Author: Taylor Hanayik, hanayik@gmail.com
"""
from os.path import dirname, join, abspath
import wx
import wx.lib.scrolledpanel as scrolled
......@@ -201,7 +210,7 @@ class BetView(wx.Panel):
self.SetSizer(sizer)
class PnmView(wx.Panel):
class NotUsedPnmView(wx.Panel):
"""
PnmView defines the graphical layout of widgets for the PNM gui
"""
......@@ -209,6 +218,15 @@ class PnmView(wx.Panel):
super().__init__(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
# init title panel
self.title_panel = fslwidgets.Title(self, title=title)
# add title panel
sizer.Add(self.title_panel, proportion=0, flag=wx.ALIGN_CENTER | wx.ALL, border=5)
#
# lastly set the sizer with all widgets added
self.SetSizer(sizer)
class FlirtView(wx.Panel):
"""
......@@ -240,7 +258,7 @@ class FlirtView(wx.Panel):
self.input_lowres = fslwidgets.Input(self).set_label("Low res image*")
sizer.Add(self.input_lowres, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
self.input_lowres.Hide()
# high res input panel
self.input = fslwidgets.Input(self).set_label("Input image*")
sizer.Add(self.input, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
......@@ -259,12 +277,18 @@ class FlirtView(wx.Panel):
# lastly set the sizer with all widgets added
self.SetSizer(sizer)
VIEW_PATH = join(abspath(dirname(__file__)), 'view_specs')
pnm = join(VIEW_PATH, 'pnm_spec.yaml')
bet = join(VIEW_PATH, 'bet_spec.yaml')
class FslView():
"""
FslView is the main FSL start window. not to be
confused with the deprecated image viewer fslview (RIP)
"""
......@@ -13,12 +13,120 @@ import fsleyes.views.orthopanel as orthopanel
import fsleyes.profiles as profiles
import fsleyes.profiles.profilemap as profilemap
import fsleyes.colourmaps as colourmaps
import fsleyes_props as props
from fsl.utils.platform import platform as fslplatform
from fsl.utils import idle
import fsl.data.image as fslimage
import fsl.gui.icons as fslicons