Skip to content

Commit

Permalink
Auto load ST on startup and version bump (#64)
Browse files Browse the repository at this point in the history
* Open the plugin on startup

* Remove default scanner constraint file

* Add fsleyes layout and launch it on startup

* Add double quotes to strings in the install script

* Fix typo

* Call the function rather than simulate a click

* Add the tests

* Add quadprog

* Add skimage

* bump version

* Add fieldmap test

* Add B0shim tab test

* Create files for tests of specific tabs

* Bump ST version

* Add multiple fieldmap and b0shim tests

* Update files from code review

* Upgrade fsleyes to 1.9.0

* Install prelude in the CI
  • Loading branch information
po09i authored Oct 11, 2023
1 parent 5c32fe6 commit 001fb6d
Show file tree
Hide file tree
Showing 20 changed files with 691 additions and 158 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ jobs:
run: |
echo "$HOME/shimming-toolbox/bin" >> $GITHUB_PATH
- name: prelude Ubuntu
if: contains(matrix.os, 'ubuntu')
run: |
echo "Download prelude"
st_download_data prelude
sudo install prelude/prelude /usr/local/bin
# - name: Run unit tests Mac
# if: contains(matrix.os, 'macos')
# run: |
Expand Down
16 changes: 10 additions & 6 deletions fsleyes_plugin_shimming_toolbox/components/dropdown_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ def __init__(self, panel, dropdown_metadata, label, option_name, list_components
}
label (str): Label of the button describing the dropdown
option_name (str): Name of the options of the dropdown, set to 'no_arg' is not an option
option_name (str): Name of the options of the dropdown, start with 'no_arg' if it is not an option
list_components (list): list of Components
info_text (str): Info message displayed when hovering over the "i" icon. Leave blank to auto fill using option_name
cli (function): CLI function used by the dropdown
component_to_dropdown_choice (list): Tells which component associates with which dropdown selection.
If None, assumes 1:1.
component_to_dropdown_choice (list): Tells which component associates with which dropdown selection.
If None, assumes 1:1.
"""
super().__init__(panel, list_components)

self.choice_box_sizer = None
self.choice_box = None

self.dropdown_metadata = dropdown_metadata
self.label = label
self.info_text = info_text
Expand Down Expand Up @@ -98,7 +102,7 @@ def unshow_choice_box_sizers(self):
sizer.Show(False)

def create_choice_box(self):
self.choice_box = wx.Choice(self.panel, choices=self.dropdown_choices)
self.choice_box = wx.Choice(self.panel, choices=self.dropdown_choices, name=self.option_name)
self.choice_box.Bind(wx.EVT_CHOICE, self.on_choice)
button = wx.Button(self.panel, -1, label=self.label)
self.choice_box_sizer = wx.BoxSizer(wx.HORIZONTAL)
Expand All @@ -114,7 +118,7 @@ def on_choice(self, event, is_propagating_up=True):
is_propagating_up tells the dropdown to propagate the on_choice() command to the parent drop down until
there is no more parent. Moreover, on each call of on_choice(), the propagation will also go down. This
allows to recalculate each dropdown. THere is some redundency since we would really just want the most
allows to recalculate each dropdown. There is some redundancy since we would really just want the most
parent dropdown to send the down propagation, but this will do for now.
"""
# Get the selection from the choice box widget
Expand Down Expand Up @@ -168,6 +172,6 @@ def find_index(self, label):
return 0

def create_sizer(self):
"""Create the a sizer containing tab-specific functionality."""
"""Create a sizer containing tab-specific functionality."""
sizer = wx.BoxSizer(wx.VERTICAL)
return sizer
9 changes: 5 additions & 4 deletions fsleyes_plugin_shimming_toolbox/components/run_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
from fsleyes_plugin_shimming_toolbox.events import EVT_RESULT, EVT_LOG
from fsleyes_plugin_shimming_toolbox.worker_thread import WorkerThread

# Load icon resources
play_icon = wx.Bitmap(os.path.join(__DIR_ST_PLUGIN_IMG__, 'play.png'), wx.BITMAP_TYPE_PNG)


class RunComponent(Component):
"""Component which contains input and run button.
Expand Down Expand Up @@ -57,6 +54,7 @@ def add_button_run(self):
"""Add the run button which will call the ``Shimming Toolbox`` CLI."""
button_run = wx.Button(self.panel, -1, label="Run", size=(85, 48))
button_run.Bind(wx.EVT_BUTTON, self.button_run_on_click)
play_icon = wx.Bitmap(os.path.join(__DIR_ST_PLUGIN_IMG__, 'play.png'), wx.BITMAP_TYPE_PNG)
button_run.SetBitmap(play_icon, dir=wx.LEFT)
self.sizer.Add(button_run, 0, wx.CENTRE)
self.sizer.AddSpacer(10)
Expand Down Expand Up @@ -150,6 +148,9 @@ def button_run_on_click(self, event):
Calls the relevant ``Shimming Toolbox`` CLI command (``st_function``) in a thread
"""
self.run()

def run(self):
if not self.worker:
try:
command, msg = self.get_run_args(self.st_function)
Expand Down Expand Up @@ -190,7 +191,7 @@ def get_run_args(self, st_function):
for component in self.list_components:
for name, input_text_box_list in component.input_text_boxes.items():

if name == "no_arg":
if name.startswith('no_arg'):
continue

for input_text_box in input_text_box_list:
Expand Down
10 changes: 10 additions & 0 deletions fsleyes_plugin_shimming_toolbox/st_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import fsleyes.controls.controlpanel as ctrlpanel
import fsleyes.views.canvaspanel as canvaspanel
import textwrap
import wx

from fsleyes_plugin_shimming_toolbox.tabs.b0shim_tab import B0ShimTab
Expand All @@ -26,6 +27,15 @@
from fsleyes_plugin_shimming_toolbox.tabs.fieldmap_tab import FieldMapTab
from fsleyes_plugin_shimming_toolbox.tabs.mask_tab import MaskTab

STLayout = textwrap.dedent(
"""
fsleyes.views.orthopanel.OrthoPanel
layout2|name=OrthoPanel 1;caption=Ortho View 1;state=67376064;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|dock_size(5,0,0)=22|
fsleyes.controls.orthotoolbar.OrthoToolBar,fsleyes.controls.overlaydisplaytoolbar.OverlayDisplayToolBar,fsleyes.controls.overlaylistpanel.OverlayListPanel,fsleyes.controls.locationpanel.LocationPanel,fsleyes_plugin_shimming_toolbox.st_plugin.STControlPanel;syncLocation=True,syncOverlayOrder=True,syncOverlayDisplay=True,syncOverlayVolume=True,movieRate=400,movieAxis=3;showCursor=True,bgColour=#000000ff,fgColour=#ffffffff,cursorColour=#00ff00ff,cursorGap=False,showColourBar=False,colourBarLocation=top,colourBarLabelSide=top-left,showXCanvas=True,showYCanvas=True,showZCanvas=True,showLabels=True,labelSize=12,layout=horizontal,xzoom=847.0127756479341,yzoom=847.0127756479341,zzoom=100.0
layout2|name=Panel;caption=;state=768;dir=5;layer=0;row=0;pos=0;prop=100000;bestw=-1;besth=-1;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OrthoToolBar;caption=Ortho view toolbar;state=67382012;dir=1;layer=10;row=0;pos=0;prop=100000;bestw=572;besth=35;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayDisplayToolBar;caption=Display toolbar;state=67382012;dir=1;layer=11;row=0;pos=0;prop=100000;bestw=953;besth=56;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=-1;floath=-1;notebookid=-1;transparent=255|name=OverlayListPanel;caption=Overlay list;state=67373052;dir=3;layer=0;row=0;pos=0;prop=100000;bestw=197;besth=80;minw=1;minh=1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=197;floath=99;notebookid=-1;transparent=255|name=LocationPanel;caption=Location;state=67373052;dir=3;layer=0;row=0;pos=1;prop=100000;bestw=391;besth=111;minw=1;minh=1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=391;floath=130;notebookid=-1;transparent=255|name=STControlPanel;caption=Shimming Toolbox;state=67373052;dir=1;layer=0;row=0;pos=0;prop=100000;bestw=600;besth=400;minw=1;minh=1;maxw=-1;maxh=-1;floatx=-1;floaty=-1;floatw=600;floath=419;notebookid=-1;transparent=255|dock_size(5,0,0)=22|dock_size(3,0,0)=176|dock_size(1,10,0)=37|dock_size(1,11,0)=58|dock_size(1,0,0)=384|
"""
)


# We need to create a ctrlpanel.ControlPanel instance so that it can be recognized as a plugin by FSLeyes
# Class hierarchy: wx.Panel > fslpanel.FSLeyesPanel > ctrlpanel.ControlPanel
Expand Down
61 changes: 42 additions & 19 deletions fsleyes_plugin_shimming_toolbox/tabs/b0shim_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
import wx

from fsleyes_plugin_shimming_toolbox import __CURR_DIR__, __ST_DIR__
from fsleyes_plugin_shimming_toolbox import __CURR_DIR__
from fsleyes_plugin_shimming_toolbox.tabs.tab import Tab
from fsleyes_plugin_shimming_toolbox.components.dropdown_component import DropdownComponent
from fsleyes_plugin_shimming_toolbox.components.input_component import InputComponent
Expand All @@ -18,6 +18,19 @@
class B0ShimTab(Tab):
def __init__(self, parent, title="B0 Shim"):

self.dropdown_coil_format_rt = None
self.dropdown_slice_rt = None
self.dropdown_opt_rt = None
self.dropdown_scanner_order_rt = None
self.dropdown_coil_format_dyn = None
self.dropdown_slice_dyn = None
self.dropdown_opt_dyn = None
self.dropdown_scanner_order_dyn = None
self.choice_box = None
self.run_component_mi = None
self.run_component_rt = None
self.run_component_dyn = None

description = "Perform B0 shimming.\n\n" \
"Select the shimming algorithm from the dropdown list."
super().__init__(parent, title, description)
Expand Down Expand Up @@ -101,7 +114,7 @@ def unshow_choice_box_sizers(self):
sizer_item.Show(False)

def create_choice_box(self):
self.choice_box = wx.Choice(self, choices=self.dropdown_choices)
self.choice_box = wx.Choice(self, choices=self.dropdown_choices, name="b0shim_algorithms")
self.choice_box.Bind(wx.EVT_CHOICE, self.on_choice)
self.sizer_run.Add(self.choice_box)
self.sizer_run.AddSpacer(10)
Expand All @@ -114,7 +127,7 @@ def create_sizer_dynamic_shim(self, metadata=None):
{
"button_label": "Number of Custom Coils",
"button_function": "add_input_coil_boxes_dyn",
"name": "no_arg",
"name": "no_arg_ncoils_dyn",
"info_text": "Number of phase NIfTI files to be used. Must be an integer > 0.",
}
]
Expand Down Expand Up @@ -172,7 +185,6 @@ def create_sizer_dynamic_shim(self, metadata=None):
"button_label": "Scanner constraints",
"button_function": "select_file",
"name": "scanner-coil-constraints",
"default_text": f"{os.path.join(__ST_DIR__, 'coil_config.json')}",
},
]
component_scanner1 = InputComponent(self, input_text_box_metadata_scanner, cli=dynamic_cli)
Expand Down Expand Up @@ -288,7 +300,8 @@ def create_sizer_dynamic_shim(self, metadata=None):
"name": 'regularization-factor',
}
]
component_reg_factor = InputComponent(self, reg_factor_metadata, cli=dynamic_cli)
component_reg_factor_lsq = InputComponent(self, reg_factor_metadata, cli=dynamic_cli)
component_reg_factor_qp = InputComponent(self, reg_factor_metadata, cli=dynamic_cli)

criteria_dropdown_metadata = [
{
Expand Down Expand Up @@ -318,15 +331,20 @@ def create_sizer_dynamic_shim(self, metadata=None):
"label": "Pseudo Inverse",
"option_value": "pseudo_inverse"
},
{
"label": "Quad Prog",
"option_value": "quad_prog"
}
]

self.dropdown_opt_dyn = DropdownComponent(
panel=self,
dropdown_metadata=dropdown_opt_metadata,
label="Optimizer",
option_name='optimizer-method',
list_components=[dropdown_crit, component_reg_factor, self.create_empty_component()],
component_to_dropdown_choice=[0, 0, 1],
list_components=[dropdown_crit, component_reg_factor_lsq,
self.create_empty_component(), component_reg_factor_qp],
component_to_dropdown_choice=[0, 0, 1, 2],
cli=dynamic_cli
)

Expand Down Expand Up @@ -428,7 +446,7 @@ def create_sizer_dynamic_shim(self, metadata=None):
dropdown_fatsat1.add_dropdown_parent(self.dropdown_coil_format_dyn)
dropdown_fatsat2.add_dropdown_parent(self.dropdown_coil_format_dyn)

run_component = RunComponent(
self.run_component_dyn = RunComponent(
panel=self,
list_components=[self.component_coils_dyn, component_inputs, self.dropdown_opt_dyn, self.dropdown_slice_dyn,
self.dropdown_scanner_order_dyn,
Expand All @@ -437,7 +455,7 @@ def create_sizer_dynamic_shim(self, metadata=None):
output_paths=["fieldmap_calculated_shim_masked.nii.gz",
"fieldmap_calculated_shim.nii.gz"]
)
sizer = run_component.sizer
sizer = self.run_component_dyn.sizer
return sizer

def create_sizer_realtime_shim(self, metadata=None):
Expand All @@ -448,7 +466,7 @@ def create_sizer_realtime_shim(self, metadata=None):
{
"button_label": "Number of Custom Coils",
"button_function": "add_input_coil_boxes_rt",
"name": "no_arg",
"name": "no_arg_ncoils_rt",
"info_text": "Number of phase NIfTI files to be used. Must be an integer > 0.",
}
]
Expand Down Expand Up @@ -497,7 +515,6 @@ def create_sizer_realtime_shim(self, metadata=None):
"button_label": "Scanner constraints",
"button_function": "select_file",
"name": "scanner-coil-constraints",
"default_text": f"{os.path.join(__ST_DIR__, 'coil_config.json')}",
},
]
component_scanner1 = InputComponent(self, input_text_box_metadata_scanner, cli=realtime_cli)
Expand Down Expand Up @@ -625,7 +642,8 @@ def create_sizer_realtime_shim(self, metadata=None):
"name": 'regularization-factor',
}
]
component_reg_factor = InputComponent(self, reg_factor_metadata, cli=dynamic_cli)
component_reg_factor_lsq = InputComponent(self, reg_factor_metadata, cli=dynamic_cli)
component_reg_factor_qp = InputComponent(self, reg_factor_metadata, cli=dynamic_cli)

criteria_dropdown_metadata = [
{
Expand Down Expand Up @@ -655,15 +673,20 @@ def create_sizer_realtime_shim(self, metadata=None):
"label": "Pseudo Inverse",
"option_value": "pseudo_inverse"
},
{
"label": "Quad Prog",
"option_value": "quad_prog"
}
]

self.dropdown_opt_rt = DropdownComponent(
panel=self,
dropdown_metadata=dropdown_opt_metadata,
label="Optimizer",
option_name = 'optimizer-method',
list_components= [dropdown_crit, component_reg_factor, self.create_empty_component()],
component_to_dropdown_choice=[0, 0, 1],
option_name='optimizer-method',
list_components=[dropdown_crit, component_reg_factor_lsq,
self.create_empty_component(), component_reg_factor_qp],
component_to_dropdown_choice=[0, 0, 1, 2],
cli=realtime_cli
)

Expand Down Expand Up @@ -746,7 +769,7 @@ def create_sizer_realtime_shim(self, metadata=None):

dropdown_fatsat.add_dropdown_parent(self.dropdown_coil_format_rt)

run_component = RunComponent(
self.run_component_rt = RunComponent(
panel=self,
list_components=[self.component_coils_rt, component_inputs, self.dropdown_opt_rt, self.dropdown_slice_rt,
self.dropdown_scanner_order_rt,
Expand All @@ -755,7 +778,7 @@ def create_sizer_realtime_shim(self, metadata=None):
# TODO: output paths
output_paths=[]
)
sizer = run_component.sizer
sizer = self.run_component_rt.sizer
return sizer

def create_sizer_max_intensity(self, metadata=None):
Expand All @@ -781,11 +804,11 @@ def create_sizer_max_intensity(self, metadata=None):
]
component_inputs = InputComponent(self, inputs_metadata, cli=max_intensity_cli)

run_component = RunComponent(
self.run_component_mi = RunComponent(
panel=self,
list_components=[component_inputs],
st_function="st_b0shim max-intensity",
output_paths=[]
)
sizer = run_component.sizer
sizer = self.run_component_mi.sizer
return sizer
2 changes: 1 addition & 1 deletion fsleyes_plugin_shimming_toolbox/tabs/b1shim_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def unshow_choice_box_sizers(self):
sizer_item.Show(False)

def create_choice_box(self):
self.choice_box = wx.Choice(self, choices=self.dropdown_choices)
self.choice_box = wx.Choice(self, choices=self.dropdown_choices, name="b1shim_algorithms")
self.choice_box.Bind(wx.EVT_CHOICE, self.on_choice)
self.sizer_run.Add(self.choice_box)
self.sizer_run.AddSpacer(10)
Expand Down
5 changes: 3 additions & 2 deletions fsleyes_plugin_shimming_toolbox/tabs/dicom_to_nifti_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(self, parent, title="Dicom to Nifti"):
super().__init__(parent, title, description)

self.sizer_run = self.create_sizer_run()
self.run_component = None
sizer = self.create_dicom_to_nifti_sizer()
self.sizer_run.Add(sizer, 0, wx.EXPAND)

Expand Down Expand Up @@ -52,5 +53,5 @@ def create_dicom_to_nifti_sizer(self):
}
]
component = InputComponent(self, input_text_box_metadata, cli=dicom_to_nifti_cli)
run_component = RunComponent(panel=self, list_components=[component], st_function="st_dicom_to_nifti")
return run_component.sizer
self.run_component = RunComponent(panel=self, list_components=[component], st_function="st_dicom_to_nifti")
return self.run_component.sizer
Loading

0 comments on commit 001fb6d

Please sign in to comment.