From 3613cd9a004a9ee1e7cb51174d35a9e6ab8d8ef7 Mon Sep 17 00:00:00 2001 From: rmatsuda Date: Mon, 7 Oct 2024 09:19:45 +0300 Subject: [PATCH 1/3] ADD: import eeg coordinates plugin --- .gitignore | 2 + import_eeg_coordinates/__init__.py | 0 import_eeg_coordinates/gui.py | 94 ++++++++++++++++++++++++++++++ import_eeg_coordinates/main.py | 8 +++ import_eeg_coordinates/plugin.json | 5 ++ 5 files changed, 109 insertions(+) create mode 100644 import_eeg_coordinates/__init__.py create mode 100644 import_eeg_coordinates/gui.py create mode 100644 import_eeg_coordinates/main.py create mode 100644 import_eeg_coordinates/plugin.json diff --git a/.gitignore b/.gitignore index d9e92d7..8f22fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,5 @@ Sessionx.vim [._]*.un~ # End of https://www.gitignore.io/api/vim,tags,python + +.idea/ \ No newline at end of file diff --git a/import_eeg_coordinates/__init__.py b/import_eeg_coordinates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/import_eeg_coordinates/gui.py b/import_eeg_coordinates/gui.py new file mode 100644 index 0000000..e9e08cb --- /dev/null +++ b/import_eeg_coordinates/gui.py @@ -0,0 +1,94 @@ +import os +import wx +from pubsub import pub as Publisher +import numpy as np +import invesalius.constants as const +import invesalius.data.imagedata_utils as img_utils +from invesalius.data.markers.marker import MarkerType +import invesalius.data.slice_ as sl +import invesalius.gui.dialogs as dlgs + + +class Window(wx.Dialog): + def __init__(self, parent): + super().__init__( + parent, + title="Import eeg coordinates", + style=wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT, + ) + + self._init_gui() + + def _init_gui(self): + self.position_list = [] + self.label_list = [] + wildcard: str = "(*.csv)|*.csv" + # Default system path + current_dir = os.path.abspath(".") + + dlg_message = "Import Csv EEG" + dlg_style = wx.FD_OPEN | wx.FD_CHANGE_DIR + + if sl.Slice().has_affine(): + dlg = dlgs.FileSelectionDialog( + title=dlg_message, wildcard=wildcard, default_dir=current_dir + ) + conversion_radio_box = wx.RadioBox( + dlg, + -1, + _("File csv eeg"), + choices=const.SURFACE_SPACE_CHOICES, + style=wx.RA_SPECIFY_ROWS, + ) + dlg.sizer.Add(conversion_radio_box, 0, wx.LEFT) + dlg.FitSizers() + else: + dlg = wx.FileDialog( + None, + message=dlg_message, + wildcard=wildcard, + default_dir=current_dir, + style=dlg_style, + ) + # stl filter is default + dlg.SetFilterIndex(0) + conversion_radio_box = None + + # Show the dialog and retrieve the user response. If it is the OK response, + # process the data. + filename = None + try: + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + if conversion_radio_box is not None: + data = np.genfromtxt(fname=filename, delimiter=',', encoding=None, dtype=None, skip_header=0) + for i in data: + position_world = [i[1], -i[2], i[3]] + convert_to_inv = conversion_radio_box.GetSelection() == const.SURFACE_SPACE_WORLD + if convert_to_inv: + affine = sl.Slice().affine.copy() + affine = np.linalg.inv(affine) + position = img_utils.convert_world_to_voxel(position_world, affine)[0].tolist() + position[1] += 2 + else: + position = position_world + + Publisher.sendMessage( + "Create marker", + marker_type=MarkerType.LANDMARK, + position=position, + label=i[4], + ) + except: + wx.MessageBox("Invalid EEG coordinates file. \nFile should be structured as ´´electrode,x,y,z,label´´", "InVesalius 3") + self._init_gui() + + dlg.Destroy() + self.Destroy() + + +if __name__ == "__main__": + app = wx.App() + window = Window(None) + window.Show() + app.MainLoop() diff --git a/import_eeg_coordinates/main.py b/import_eeg_coordinates/main.py new file mode 100644 index 0000000..72a79c5 --- /dev/null +++ b/import_eeg_coordinates/main.py @@ -0,0 +1,8 @@ +import wx + +from . import gui + + +def load(): + w = gui.Window(wx.GetApp().GetTopWindow()) + w.Show() diff --git a/import_eeg_coordinates/plugin.json b/import_eeg_coordinates/plugin.json new file mode 100644 index 0000000..02f2617 --- /dev/null +++ b/import_eeg_coordinates/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "Import EEG coordinates", + "description": "Import EEG coordinates", + "enable-startup": false +} From 074cf2717acfcfaf6940036fea800d30b2756b73 Mon Sep 17 00:00:00 2001 From: rmatsuda Date: Tue, 8 Oct 2024 15:52:07 +0300 Subject: [PATCH 2/3] FIX: coordinates transformation --- import_eeg_coordinates/gui.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/import_eeg_coordinates/gui.py b/import_eeg_coordinates/gui.py index e9e08cb..95b0d81 100644 --- a/import_eeg_coordinates/gui.py +++ b/import_eeg_coordinates/gui.py @@ -63,20 +63,21 @@ def _init_gui(self): if conversion_radio_box is not None: data = np.genfromtxt(fname=filename, delimiter=',', encoding=None, dtype=None, skip_header=0) for i in data: - position_world = [i[1], -i[2], i[3]] + position_world = [i[1], i[2], i[3]] convert_to_inv = conversion_radio_box.GetSelection() == const.SURFACE_SPACE_WORLD if convert_to_inv: affine = sl.Slice().affine.copy() affine = np.linalg.inv(affine) position = img_utils.convert_world_to_voxel(position_world, affine)[0].tolist() - position[1] += 2 + position_voxel = img_utils.convert_invesalius_to_voxel(position).tolist() else: - position = position_world + position_voxel = position_world + position_voxel[2] = -position_world[2] Publisher.sendMessage( "Create marker", marker_type=MarkerType.LANDMARK, - position=position, + position=position_voxel, label=i[4], ) except: From 9a6cb938deb65a3fa4e1e74d20eb72e99c3d15e1 Mon Sep 17 00:00:00 2001 From: rmatsuda Date: Tue, 8 Oct 2024 16:03:29 +0300 Subject: [PATCH 3/3] ADD: import world coordinates into invesalius --- import_world_coordinates/__init__.py | 0 import_world_coordinates/gui.py | 233 +++++++++++++++++++++++++++ import_world_coordinates/main.py | 8 + import_world_coordinates/plugin.json | 5 + 4 files changed, 246 insertions(+) create mode 100644 import_world_coordinates/__init__.py create mode 100644 import_world_coordinates/gui.py create mode 100644 import_world_coordinates/main.py create mode 100644 import_world_coordinates/plugin.json diff --git a/import_world_coordinates/__init__.py b/import_world_coordinates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/import_world_coordinates/gui.py b/import_world_coordinates/gui.py new file mode 100644 index 0000000..53b1e07 --- /dev/null +++ b/import_world_coordinates/gui.py @@ -0,0 +1,233 @@ +import wx +from pubsub import pub as Publisher +import numpy as np + +import invesalius.data.coordinates as dco +import invesalius.data.imagedata_utils as img_utils +from invesalius.data.markers.marker import MarkerType +import invesalius.data.slice_ as sl + + +class Window(wx.Dialog): + def __init__( + self, + parent, + title="Import World Coordinates into InVesalius", + style=wx.DEFAULT_DIALOG_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.STAY_ON_TOP, + ): + wx.Dialog.__init__(self, parent, -1, title=title, style=style) + + self.x = -48.48 + self.y = -28.06 + self.z = 60.10 + self.rx = -2.44 + self.ry = -0.14 + self.rz = -1.48 + + self.arx = 0 + self.ary = 180 + self.arz = 0 + + self._init_gui() + + self.set_original_coordinates( + self.x, self.y, self.z, self.rx, self.ry, self.rz, self.arx, self.ary, self.arz + ) + + self._bind_events() + + def set_original_coordinates(self, sx, sy, sz, srx, sry, srz, arx, ary, arz): + self.x = sx + self.y = sy + self.z = sz + self.rx = srx + self.ry = sry + self.rz = srz + + self.apply_rx = arx + self.apply_ry = ary + self.apply_rz = arz + + self.txt_x.ChangeValue(str(sx)) + self.txt_y.ChangeValue(str(sy)) + self.txt_z.ChangeValue(str(sz)) + self.txt_rx.ChangeValue(str(srx)) + self.txt_ry.ChangeValue(str(sry)) + self.txt_rz.ChangeValue(str(srz)) + + self.txt_apply_rx.ChangeValue(str(arx)) + self.txt_apply_ry.ChangeValue(str(ary)) + self.txt_apply_rz.ChangeValue(str(arz)) + + def _init_gui(self): + self.txt_x = wx.TextCtrl(self, -1) + self.txt_y = wx.TextCtrl(self, -1) + self.txt_z = wx.TextCtrl(self, -1) + self.txt_rx = wx.TextCtrl(self, -1) + self.txt_ry = wx.TextCtrl(self, -1) + self.txt_rz = wx.TextCtrl(self, -1) + + self.txt_apply_rx = wx.TextCtrl(self, -1) + self.txt_apply_ry = wx.TextCtrl(self, -1) + self.txt_apply_rz = wx.TextCtrl(self, -1) + + self.check_rotation = wx.CheckBox(self, -1, "Rotation") + self.check_rotation.SetValue(True) + + self.button_create_marker = wx.Button(self, wx.ID_OK, label="Create Marker") + self.button_close = wx.Button(self, wx.ID_CLOSE) + + button_sizer = wx.StdDialogButtonSizer() + button_sizer.AddButton(self.button_create_marker) + button_sizer.AddButton(self.button_close) + button_sizer.Realize() + + sizer_original = wx.FlexGridSizer(3, 2, 5, 5) + sizer_original.AddMany( + ( + (wx.StaticText(self, -1, "X"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_x, 0, wx.EXPAND), + (wx.StaticText(self, -1, "Y"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_y, 0, wx.EXPAND), + (wx.StaticText(self, -1, "Z"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_z, 0, wx.EXPAND), + ) + ) + + original_sbox = wx.StaticBoxSizer(wx.HORIZONTAL, self, "Translation") + original_sbox.Add(sizer_original, 0, wx.EXPAND | wx.ALL, 5) + + sizer_new = wx.FlexGridSizer(3, 2, 5, 5) + sizer_new.AddMany( + ( + (wx.StaticText(self, -1, "RX"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_rx, 0, wx.ALIGN_CENTER_VERTICAL), + (wx.StaticText(self, -1, "RY"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_ry, 0, wx.ALIGN_CENTER_VERTICAL), + (wx.StaticText(self, -1, "RZ"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_rz, 0, wx.ALIGN_CENTER_VERTICAL), + ) + ) + new_sbox = wx.StaticBoxSizer(wx.HORIZONTAL, self, "Orientation (rad)") + new_sbox.Add(sizer_new, 0, wx.EXPAND | wx.ALL, 5) + + content_sizer = wx.BoxSizer(wx.HORIZONTAL) + content_sizer.Add(original_sbox, 1, wx.EXPAND | wx.ALL, 5) + content_sizer.Add(new_sbox, 1, wx.EXPAND | wx.ALL, 5) + + sizer_apply = wx.FlexGridSizer(2, 3, 5, 5) + sizer_apply.AddMany( + ( + (wx.StaticText(self, -1, "RX"), 0, wx.ALIGN_CENTER_VERTICAL), + (wx.StaticText(self, -1, "RY"), 0, wx.ALIGN_CENTER_VERTICAL), + (wx.StaticText(self, -1, "RZ"), 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_apply_rx, 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_apply_ry, 0, wx.ALIGN_CENTER_VERTICAL), + (self.txt_apply_rz, 0, wx.ALIGN_CENTER_VERTICAL), + ) + ) + new_abox = wx.StaticBoxSizer(wx.HORIZONTAL, self, "Apply rotation (degree)") + new_abox.Add(sizer_apply, 0, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(content_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 5) + main_sizer.Add(self.check_rotation, 0, wx.LEFT | wx.ALIGN_LEFT, 7) + main_sizer.Add(new_abox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 7) + main_sizer.Add(button_sizer, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + self.SetSizer(main_sizer) + main_sizer.Fit(self) + self.Layout() + + def _bind_events(self): + self.button_create_marker.Bind(wx.EVT_BUTTON, self.OnCreateMarker) + self.button_close.Bind(wx.EVT_BUTTON, self.OnClose) + self.Bind(wx.EVT_CLOSE, self.OnClose) + self.check_rotation.Bind(wx.EVT_CHECKBOX, self.OnEnableRotation) + + def OnCreateMarker(self, evt): + try: + x = float(self.txt_x.GetValue()) + except ValueError: + x = self.x + + try: + y = float(self.txt_y.GetValue()) + except ValueError: + y = self.y + + try: + z = float(self.txt_z.GetValue()) + except ValueError: + z = self.z + + try: + rx = float(self.txt_rx.GetValue()) + except ValueError: + rx = self.rx + + try: + ry = float(self.txt_ry.GetValue()) + except ValueError: + ry = self.ry + + try: + rz = float(self.txt_rz.GetValue()) + except ValueError: + rz = self.rz + + m_coord = dco.coordinates_to_transformation_matrix( + position=[x, y, z], + orientation=[np.rad2deg(rx), np.rad2deg(ry), np.rad2deg(rz)], + axes="sxyz", + ) + affine = sl.Slice().affine.copy() + m_coord_transformed = affine @ m_coord + position, orientation = dco.transformation_matrix_to_coordinates( + m_coord_transformed, + axes="sxyz", + ) + + if self.check_rotation.GetValue(): + try: + arx = float(self.txt_apply_rx.GetValue()) + except ValueError: + arx = self.apply_rx + try: + ary = float(self.txt_apply_ry.GetValue()) + except ValueError: + ary = self.apply_ry + try: + arz = float(self.txt_apply_rz.GetValue()) + except ValueError: + arz = self.apply_rz + m_displacement = dco.coordinates_to_transformation_matrix( + position=[0, 0, 0], + orientation=[arx, ary, arz], + axes="sxyz", + ) + position, orientation = dco.transformation_matrix_to_coordinates( + m_coord_transformed @ m_displacement, + axes="sxyz", + ) + + position_voxel = img_utils.convert_invesalius_to_voxel(position) + Publisher.sendMessage( + "Create marker", + marker_type=MarkerType.COIL_TARGET, + position=position_voxel.tolist(), + orientation=orientation.tolist(), + ) + + def OnEnableRotation(self, evt): + if evt.IsChecked(): + self.txt_apply_rx.Enable() + self.txt_apply_ry.Enable() + self.txt_apply_rz.Enable() + else: + self.txt_apply_rx.Disable() + self.txt_apply_ry.Disable() + self.txt_apply_rz.Disable() + + def OnClose(self, evt): + self.Destroy() diff --git a/import_world_coordinates/main.py b/import_world_coordinates/main.py new file mode 100644 index 0000000..72a79c5 --- /dev/null +++ b/import_world_coordinates/main.py @@ -0,0 +1,8 @@ +import wx + +from . import gui + + +def load(): + w = gui.Window(wx.GetApp().GetTopWindow()) + w.Show() diff --git a/import_world_coordinates/plugin.json b/import_world_coordinates/plugin.json new file mode 100644 index 0000000..e2ba2e6 --- /dev/null +++ b/import_world_coordinates/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "Import world coordinates", + "description": "Import world coordinates into invesalius coordinate system", + "enable-startup": false +}