Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding an automatic fill feature to the center button in mask tab #47

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ source /Users/vagrant/.bashrc
To convert the install to an editable developer install:
```
source /Users/vagrant/shimming_toolbox/python/etc/profile.d/conda.sh
conda activate pst_venv_1267b18e73341ad94da34474
sudo pip3 install -e .
```

Expand Down
36 changes: 36 additions & 0 deletions fsleyes_plugin_shimming_toolbox/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import wx

result_event_type = wx.NewEventType()
EVT_RESULT = wx.PyEventBinder(result_event_type, 1)

log_event_type = wx.NewEventType()
EVT_LOG = wx.PyEventBinder(log_event_type, 1)


class ResultEvent(wx.PyCommandEvent):
def __init__(self, evtType, id, name):
wx.PyCommandEvent.__init__(self, evtType, id)
self.data = ""
self.name = name

def set_data(self, data):
self.data = data

def get_data(self):
return self.data


class LogEvent(wx.PyCommandEvent):
def __init__(self, evtType, id, name):
wx.PyCommandEvent.__init__(self, evtType, id)
self.data = ""
self.name = name

def set_data(self, data):
self.data = data

def get_data(self):
return self.data
133 changes: 109 additions & 24 deletions fsleyes_plugin_shimming_toolbox/st_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

---------------------------------------------------------------------------------------
Copyright (c) 2021 Polytechnique Montreal <www.neuro.polymtl.ca>
Authors: Alexandre D'Astous, Ainsleigh Hill, Gaspard Cereza, Julien Cohen-Adad
Authors: Alexandre D'Astous, Ainsleigh Hill, Gaspard Cereza, Arnaud Bréhéret, Julien Cohen-Adad
"""

import abc
Expand All @@ -26,24 +26,24 @@
import nibabel as nib
import numpy as np
import os
from pathlib import Path
import webbrowser
import wx

from pathlib import Path
from fsleyes_plugin_shimming_toolbox.utils import run_subprocess
from fsleyes_plugin_shimming_toolbox.events import result_event_type, EVT_RESULT, ResultEvent
from fsleyes_plugin_shimming_toolbox.events import log_event_type, EVT_LOG, LogEvent
from fsleyes_plugin_shimming_toolbox.worker_thread import WorkerThread
from shimmingtoolbox.cli.b0shim import dynamic_cli, realtime_cli
from shimmingtoolbox.cli.b1shim import b1shim_cli
from shimmingtoolbox.cli.dicom_to_nifti import dicom_to_nifti_cli
from shimmingtoolbox.cli.mask import box, rect, threshold
from shimmingtoolbox.cli.prepare_fieldmap import prepare_fieldmap_cli


logger = logging.getLogger(__name__)

HOME_DIR = str(Path.home())
CURR_DIR = os.getcwd()
ST_DIR = f"{HOME_DIR}/shimming-toolbox"

DIR = os.path.dirname(__file__)

VERSION = "0.1.1"
Expand Down Expand Up @@ -106,18 +106,19 @@ def __init__(self, parent, overlayList, displayCtx, ctrlPanel):

class NotebookTerminal(wx.Notebook):
"""Notebook class with an extra terminal attribute"""

def __init__(self, parent):
super().__init__(parent)
self.terminal_component = Terminal(parent)


class Tab(wx.ScrolledWindow):
def __init__(self, parent, title, description):
super().__init__(parent)
self.title = title
self.sizer_info = InfoSection(self, description).sizer
self.terminal_component = parent.terminal_component
self.SetScrollbars(1, 1, 1, 1)
self.SetScrollbars(1, 4, 1, 1)

def create_sizer(self):
"""Create the parent sizer for the tab.
Expand All @@ -138,6 +139,7 @@ def create_empty_component(self):

class Terminal:
"""Create the terminal where messages are logged to the user."""

def __init__(self, panel):
self.panel = panel
self.terminal = wx.TextCtrl(self.panel, wx.ID_ANY, style=wx.TE_MULTILINE | wx.TE_READONLY)
Expand Down Expand Up @@ -383,8 +385,13 @@ def __init__(self, panel, st_function, list_components=[], output_paths=[]):
self.st_function = st_function
self.sizer = self.create_sizer()
self.add_button_run()
self.output = ""
self.output_paths_original = output_paths
self.output_paths = output_paths.copy()
self.worker = None

self.panel.Bind(EVT_RESULT, self.on_result)
self.panel.Bind(EVT_LOG, self.log)

def create_sizer(self):
"""Create the centre sizer containing tab-specific functionality."""
Expand All @@ -403,20 +410,27 @@ def add_button_run(self):
self.sizer.Add(button_run, 0, wx.CENTRE)
self.sizer.AddSpacer(10)

def button_run_on_click(self, event):
"""Function called when the ``Run`` button is clicked.
def log(self, event):
"""Log to the terminal the when there is a log event"""

# Since the log events get broadcated to all the RunComponents of the Tab, we check that the event name
# corresponds to our function i.e self.st_function
if event.name == self.st_function:
msg = event.get_data()
self.panel.terminal_component.log_to_terminal(msg)
else:
event.Skip()

1. Calls the relevant ``Shimming Toolbox`` CLI command (``st_function``)
2. Logs the output to the terminal in the GUI.
3. Sends the output files to the overlay list if applicable.
def on_result(self, event):
# Since the log events get broadcated to all the RunComponents of the Tab, we check that the event name
# corresponds to our function i.e self.st_function
if event.name != self.st_function:
event.Skip()
return

"""
try:
command, msg = self.get_run_args(self.st_function)
self.panel.terminal_component.log_to_terminal(msg, level="INFO")
output_log = run_subprocess(command)
self.panel.terminal_component.log_to_terminal(output_log)
msg = f"Run {self.st_function} completed successfully"
data = event.get_data()
if data == 0:
msg = f"Run {self.st_function} completed successfully\n"
self.panel.terminal_component.log_to_terminal(msg, level="INFO")

# Get the directory of the output if it is a file or already a directory
Expand Down Expand Up @@ -447,7 +461,15 @@ def button_run_on_click(self, event):
"Could not fetch subject and/or path to load to overlay"
)
self.send_output_to_overlay()
except Exception as err:

self.output_paths.clear()
self.output_paths = self.output_paths_original.copy()

elif type(data) == Exception:
msg = f"Run {self.st_function} errored out\n"
err = data

self.panel.terminal_component.log_to_terminal(msg, level="ERROR")
if len(err.args) == 1:
# Pretty output
a_string = ""
Expand All @@ -458,8 +480,23 @@ def button_run_on_click(self, event):
else:
self.panel.terminal_component.log_to_terminal(str(err), level="ERROR")

self.output_paths.clear()
self.output_paths = self.output_paths_original.copy()
else:
# The error message should already be displayed
self.panel.terminal_component.log_to_terminal("")

self.worker = None
event.Skip()

def button_run_on_click(self, event):
"""Function called when the ``Run`` button is clicked.

Calls the relevant ``Shimming Toolbox`` CLI command (``st_function``) in a thread

"""
if not self.worker:
command, msg = self.get_run_args(self.st_function)
self.panel.terminal_component.log_to_terminal(msg, level="INFO")
self.worker = WorkerThread(self.panel, command, name=self.st_function)

def send_output_to_overlay(self):
for output_path in self.output_paths:
Expand Down Expand Up @@ -1639,6 +1676,7 @@ def create_sizer_rect(self):
},
{
"button_label": "Center",
"button_function": "add_current_position",
"name": "center",
"n_text_boxes": 2,
"info_text": f"{rect.params[3].help}"
Expand Down Expand Up @@ -1679,6 +1717,7 @@ def create_sizer_box(self):
},
{
"button_label": "Center",
"button_function": "add_current_position",
"name": "center",
"n_text_boxes": 3,
"info_text": f"{box.params[3].help}"
Expand Down Expand Up @@ -1832,6 +1871,10 @@ def create(self):
function = lambda event, panel=self.panel, ctrl=self.textctrl_list[i], index=2: \
add_input_coil_boxes(event, panel, ctrl, index)
self.textctrl_list[i].Bind(wx.EVT_TEXT, function)
elif button_function == "add_current_position":
function = lambda event, panel=self.panel, ctrl=self.textctrl_list: \
add_current_position(event, panel, ctrl)
button.Bind(wx.EVT_BUTTON, function)

text_with_button_box.Add(button, 0, wx.ALIGN_LEFT | wx.RIGHT, 10)

Expand Down Expand Up @@ -1921,7 +1964,6 @@ def select_from_overlay(event, tab, ctrl, focus=False):
focus (bool): Tells whether the ctrl must be in focus.
"""
if focus:
# Skip allows to handle other events
focused = wx.Window.FindFocus()
if ctrl != focused:
if focused == tab:
Expand All @@ -1931,6 +1973,7 @@ def select_from_overlay(event, tab, ctrl, focus=False):
)
# If its the tab, don't handle the other events so that the log message is only once
return
# Skip allows to handle other events,
event.Skip()
return

Expand Down Expand Up @@ -2182,3 +2225,45 @@ def load_png_image_from_path(fsl_panel, image_path, is_mask=False, add_to_overla
opts.cmap = colormap

return img_overlay


def add_current_position(event, tab, ctrl):
"""Insert current crosshair position in center text boxes, defaults to 0 if no position is available

Args:
-event (wx.Event): when the ``Number of Echoes`` button is clicked.
-tab (Mask): tab class instance for ``Mask``.
-ctrl (wx.TextCtrl): the list if text boxes containing the number of phase boxes to add. Must be an
integer > 0.

Return
void
"""

# This is messy and won't work if we change any class hierarchy. Using GetTopLevelParent() only
# works if the pane is not floating
# Get the displayCtx class initialized in STControlPanel
window = tab.GetGrandParent().GetParent()
selected_overlay = window.displayCtx.getSelectedOverlay()

# Set position to zero if no image has been imported
if selected_overlay is None:
tab.terminal_component.log_to_terminal(
"No voxel positions available, please import an image",
level="INFO"
)
vox_locations = [0 for _ in range(len(ctrl))]

# Calculates voxel location from world locations (coordinates)
else:
wloc = window.displayCtx.worldLocation
opt = window.displayCtx.getOpts(selected_overlay)
vox_locations = opt.transformCoords(wloc, 'world', 'voxel')

# Inserts voxel locations in text boxes
for text_box, value in zip(ctrl, vox_locations):
# np.round is used in combination with int() to make sure the float value is rounded properly
text_box.SetValue(str(int(np.round(value))))

# Skip allows to handle other events
event.Skip()
44 changes: 0 additions & 44 deletions fsleyes_plugin_shimming_toolbox/utils.py

This file was deleted.

Loading