Commit e955cb66 authored by Taylor Hanayik's avatar Taylor Hanayik
Browse files

made gui builder and now use yaml layout spec for designs

parent db1e85f4
......@@ -7,26 +7,42 @@ 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
keyToWidgetMap = {
'column' : fslviews.Column,
'row' : fslviews.Row,
'filePicker' : wx.TextCtrl,
'checkBox' : wx.CheckBox,
}
allowedWidgets = (keyToWidgetMap.keys())
# keyToWidgetMap = {
# 'column' : fslwidgets.column,
# 'row' : fslwidgets.row,
# 'group' : fslwidgets.group,
# 'filepath' : fslwidgets.filepath,
# 'checkbox' : fslwidgets.checkbox,
# 'choice' : fslwidgets.choice,
# 'number' : fslwidgets.number,
# 'button' : fslwidgets.button,
# }
# allowedWidgets = (keyToWidgetMap.keys())
allowedWidgets = (
'column', 'row', 'group', 'filepath',
'checkbox', 'choice', 'number', 'button'
)
allowedContainerWidgets = (
'column', 'row',
'column', 'row', 'group',
)
allowedConfigKeys = (
'appName', 'windowSize', 'width', 'height',
'layout',
)
allowedKeys = (
......@@ -35,17 +51,53 @@ allowedKeys = (
)
def isGroup(key):
parts = key.split(sep='_')
g = parts[0]
if g == "group":
return True
else:
return False
def isContainer(key):
if isGroup(key):
key = "group"
if key in allowedContainerWidgets:
return True
else:
return False
def getGroupName(key):
if not isGroup(key):
raise Exception("group must be in the forms 'group' or 'group_name'")
parts = key.split(sep="_")
if len(parts) == 2:
n = parts[-1]
elif len(parts) > 2:
raise Exception("group must be in the forms 'group' or 'group_name'")
else:
n = ""
return n
def load_spec(specFile):
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):
for k, v in d.items():
if k not in allowedKeys:
raise fslerrs.NotAValidKey("{} is not an allowed buildSpec key".format(k))
if type(v) is dict:
dictCheck(v)
if type(d) is list:
for entry in d:
print(entry)
for k, v in entry.items():
if (k not in allowedKeys) & (not isGroup(k)):
raise fslerrs.NotAValidKey("{} is not an allowed buildSpec key".format(k))
if type(v) is dict:
dictCheck(v)
dictCheck(buildSpec)
......@@ -53,6 +105,8 @@ def widgetFromKey(key):
"""
return an appropriate widget for a given key
"""
if isGroup(key):
key = "group"
if key in keyToWidgetMap:
return keyToWidgetMap[key]
else:
......@@ -65,40 +119,49 @@ def _layout_from(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 layout(buildSpec, parent):
def layout(parent, buildSpec, propObj):
"""
layout all allowed widgets in a buildSpec
"""
for k, v in buildSpec.items():
if k in allowedWidgets:
print("adding widget: ", k)
_ = widgetFromKey(k)
if type(v) is dict:
w = _(parent, **v)
else:
w = _(parent)
parent.GetSizer().Add(w, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
_layout_from(w)
if k in allowedContainerWidgets:
if type(v) is list:
for widget in v:
layout(widget, w)
for entry in buildSpec:
for k, v in entry.items():
if (k in allowedWidgets) or (isGroup(k)):
# wid = widgetFromKey(k)
if isGroup(k):
wid = getattr(fslwidgets, 'group')
else:
wid = getattr(fslwidgets, k)
if isinstance(v, dict):
w = wid(parent, propObj, **v)
else:
if isGroup(k):
w = wid(parent, getGroupName(k))
else:
if isinstance(parent.GetSizer(), wx.StaticBoxSizer):
w = wid(parent.GetSizer().GetStaticBox())
else:
w = wid(parent)
parent.GetSizer().Add(w, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
_layout_from(w)
if isContainer(k):
if type(v) is list:
layout(w, v, propObj)
parent.Layout()
return parent
def buildGUI(buildSpec):
def buildGUI(buildSpec, propObj):
"""
build a GUI from a build spec dictionary (or JSON)
"""
checkSpec(buildSpec)
app = wx.App()
mainWin = wx.Frame(None)
mainWin.SetTitle(buildSpec['appName'])
......@@ -106,8 +169,7 @@ def buildGUI(buildSpec):
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainWin.SetSizer(mainSizer)
mainWin = layout(buildSpec, mainWin)
mainWin = layout(mainWin, buildSpec['layout'], propObj)
mainWin.Centre()
mainWin.Show()
......
......@@ -5,3 +5,7 @@ custom exceptions to be used in fslgui
class NotAValidKey(Exception):
pass
class MissingRequiredKey(Exception):
pass
......@@ -215,62 +215,5 @@ class FlirtGui(BaseGui):
self.view.input_lowres.Hide()
self._layout_from(self.view.input_lowres)
class PnmGui(BaseGui):
"""
The PnmGui is the controller for the PnmView that
can interact with the Pnm model
"""
labels = {
'physioFile' : 'Physio file',
'imgVolume' : 'Image data (4D)',
'sliceOrder' : 'Volume slice order',
'sliceDir' : 'Slice direction',
'idxCardiac' : 'Cardiac Column #',
'idxResp' : 'Resp Column #',
'idxTrig' : 'Trig Column #'
}
def __init__(self, parent, title="PNM", data=None):
super().__init__()
if data is None:
self.data = fsltools.Pnm(slice_order='up')
self.view = self._make_view(parent)
def _make_view(self, parent):
"""
make a view made up of wx widgets
"""
view = wx.Panel(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
inputFiles = props.buildGUI(view, self.data,
view=props.VGroup(
children=(
props.Widget('physioFile'),
props.Widget('imgVolume'),
),
border=False),
labels=self.labels)
sizer.Add(inputFiles, proportion=0, flag= wx.ALL | wx.EXPAND, border=5)
physioIds = props.buildGUI(view, self.data,
view=props.HGroup(
children=(
props.Widget('idxCardiac'),
props.Widget('idxResp'),
props.Widget('idxTrig'),
),
border=False,),
labels=self.labels)
sizer.Add(physioIds, proportion=0, flag= wx.ALL | wx.EXPAND, border=5)
#TODO add next PNM sections. Make it similar to existing PNM gui
# set the sizer last
view.SetSizer(sizer)
return view
#!/usr/bin/env python
import sys
#from fsl.gui.guis import PnmGui
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 guiCore
import fsl.gui.views as fslviews
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.keys()))
sliceDir = props.Choice(choices=list(scannerSliceDirChoices.keys()))
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()
gui = guiCore.buildGUI(fslviews.PnmView)
"""
frame = wx.Frame(None, size=(800, 600), title="PNM")
sizer = wx.BoxSizer(wx.VERTICAL)
pnm_gui = PnmGui(frame, "PNM")
sizer.Add(pnm_gui.view, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
frame.SetSizer(sizer)
frame.Centre()
frame.Show()
"""
pnm = Pnm()
pnmView = core.load_spec(fslViews.pnm)
gui = core.buildGUI(pnmView, pnm)
app.MainLoop()
if __name__ == "__main__":
sys.exit(main())
......@@ -322,36 +322,3 @@ class Flirt(object):
pass
class Pnm(props.HasProperties):
"""
Pnm is the data model for PnmGui
"""
physioFile = props.FilePath(required=True, exists=True)
imgVolume = 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')
))
sliceOrder = props.Choice(choices=list(sliceOrderChoices.keys()))
sliceDir = props.Choice(choices=list(scannerSliceDirChoices.keys()))
idxCardiac = props.Int(minval=0, slider=False)
idxResp = props.Int(minval=0, slider=False)
idxTrig = props.Int(minval=0, slider=False)
def __init__(
self,
**kwargs
):
super().__init__(**kwargs)
---
# the pnm view layout specification
appName: PNM
windowSize:
width: 800
height: 840
layout:
- 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}
- group_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}
- row:
- button: {label: Run, propName: runButton}
\ No newline at end of file
......@@ -11,7 +11,7 @@ Author: Taylor Hanayik, hanayik@gmail.com
"""
from os.path import dirname, join, abspath
import wx
import wx.lib.scrolledpanel as scrolled
......@@ -277,48 +277,12 @@ class FlirtView(wx.Panel):
# lastly set the sizer with all widgets added
self.SetSizer(sizer)
PnmView={
'appName': 'PNM',
'windowSize':
{
'width': 800,
'height' : 600
},
'column' :
[
{'filePicker' : {'value' : 'Physio file'}},
{'filePicker' :{'value' : 'Image volume (4D)'}},
{'row' :
[
{'checkBox' : {'label' : 'one'}},
{'checkBox' :{'label' : 'two'}},
]
},
],
'row' :
[
{'checkBox' : {'label' : 'three'}},
{'checkBox' :{'label' : 'four'}},
]
}
class Column(wx.Panel):
"""
a wx.Panel with a vertical sizer
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.SetSizer(wx.BoxSizer(wx.VERTICAL))
class Row(wx.Panel):
"""
a wx.Panel with a horizontal sizer
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
VIEW_PATH = join(abspath(dirname(__file__)), 'view_specs')
pnm = join(VIEW_PATH, 'pnm_spec.yaml')
class FslView():
......
......@@ -13,12 +13,103 @@ 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
import fsl.gui.exceptions as fslerrs
props.initGUI()
#class CheckboxProp(props.HasProperties):
# checked = props.Boolean()
#class FilePathProp(props.HasProperties):
# filepath = props.FilePath(required=True, exists=True)
class column(wx.Panel):
"""
a wx.Panel with a vertical sizer
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.SetSizer(wx.BoxSizer(wx.VERTICAL))
# self.SetBackgroundColour((0, 255, 0))
class row(wx.Panel):
"""
a wx.Panel with a horizontal sizer
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
# self.SetBackgroundColour((255, 0, 0))
def button(parent, propobj, **kwargs):
if kwargs.get('label') == None:
raise fslerrs.MissingRequiredKey("A 'label' key is required")
if kwargs.get('propName') == None:
raise fslerrs.MissingRequiredKey("A 'propName' key is required")
btn = props.build._createButton(parent, getattr(propobj, kwargs['propName']), propobj, parent)
return btn
def group(parent, label):
"""
Uses a wx.StaticBoxSizer to group items visually
"""
panel = wx.Panel(parent)
bsizer = wx.StaticBoxSizer(wx.VERTICAL, panel, label)
panel.SetSizer(bsizer)
return panel
def filepath(parent, propobj, **kwargs):
if kwargs.get('label') == None:
raise fslerrs.MissingRequiredKey("A 'label' key must be provided to a FilePath")
panel = wx.Panel(parent)
panel.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
st = wx.StaticText(panel, label=kwargs['label'])
panel.GetSizer().Add(st, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=2)
f = props.widgets.makeWidget(panel, propobj, kwargs['propName'])
panel.GetSizer().Add(f, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
return panel
def checkbox(parent, propobj, **kwargs):
if kwargs.get('label') == None:
raise fslerrs.MissingRequiredKey("A 'label' key must be provided to a Checkbox")
panel = wx.Panel(parent)
panel.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
st = wx.StaticText(panel, label=kwargs['label'])
panel.GetSizer().Add(st, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=2)
ch = props.widgets.makeWidget(panel, propobj, kwargs['propName'])
panel.GetSizer().Add(ch, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
return panel
def number(parent, propobj, **kwargs):
if kwargs.get('label') == None:
raise fslerrs.MissingRequiredKey("A 'label' key must be provided to a Checkbox")