From bed72ec34ae1e89713f64d9abd4f96f8b85d644b Mon Sep 17 00:00:00 2001 From: Victor Hugo Souza Date: Thu, 2 Feb 2017 15:17:10 +0200 Subject: [PATCH] Neuronavigation module update and improvement (#65) --- .gitignore | 4 +++- invesalius/constants.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- invesalius/control.py | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------- invesalius/data/bases.py | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------ invesalius/data/co_registration.py | 65 ----------------------------------------------------------------- invesalius/data/coordinates.py | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/data/coregistration.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/data/imagedata_utils.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- invesalius/data/slice_.py | 6 ++++-- invesalius/data/styles.py | 4 +--- invesalius/data/trackers.py | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/data/trigger.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/data/viewer_slice.py | 18 ++++++++---------- invesalius/data/viewer_volume.py | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------- invesalius/gui/default_tasks.py | 3 ++- invesalius/gui/dialogs.py | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------- invesalius/gui/frame.py | 22 ++++++++++++++-------- invesalius/gui/task_importer.py | 4 ++-- invesalius/gui/task_navigator.py | 1074 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ invesalius/reader/analyze_reader.py | 43 ------------------------------------------- invesalius/reader/others_reader.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/CalibrationFiles/BumbleBee_8090380.calib | Bin 0 -> 2765436 bytes navigation/mtc_files/CalibrationFiles/_README.txt | 1 + navigation/mtc_files/Markers/1Probe | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/1b | 38 ++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/2Coil | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/2b | 38 ++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/3Coil | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/4Coil | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/5Ref | 38 ++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/COOLCARD | 37 +++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/Pointer Template | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/Ref | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/TTblock | 37 +++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/a | 37 +++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/coil | 38 ++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/probe | 39 +++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/sptr | 38 ++++++++++++++++++++++++++++++++++++++ navigation/mtc_files/Markers/try4 | 38 ++++++++++++++++++++++++++++++++++++++ 39 files changed, 2773 insertions(+), 825 deletions(-) delete mode 100644 invesalius/data/co_registration.py create mode 100644 invesalius/data/coordinates.py create mode 100644 invesalius/data/coregistration.py create mode 100644 invesalius/data/trackers.py create mode 100644 invesalius/data/trigger.py delete mode 100644 invesalius/reader/analyze_reader.py create mode 100644 invesalius/reader/others_reader.py create mode 100644 navigation/mtc_files/CalibrationFiles/BumbleBee_8090380.calib create mode 100644 navigation/mtc_files/CalibrationFiles/_README.txt create mode 100644 navigation/mtc_files/Markers/1Probe create mode 100644 navigation/mtc_files/Markers/1b create mode 100644 navigation/mtc_files/Markers/2Coil create mode 100644 navigation/mtc_files/Markers/2b create mode 100644 navigation/mtc_files/Markers/3Coil create mode 100644 navigation/mtc_files/Markers/4Coil create mode 100644 navigation/mtc_files/Markers/5Ref create mode 100644 navigation/mtc_files/Markers/COOLCARD create mode 100644 navigation/mtc_files/Markers/Pointer Template create mode 100644 navigation/mtc_files/Markers/Ref create mode 100644 navigation/mtc_files/Markers/TTblock create mode 100644 navigation/mtc_files/Markers/a create mode 100644 navigation/mtc_files/Markers/coil create mode 100644 navigation/mtc_files/Markers/probe create mode 100644 navigation/mtc_files/Markers/sptr create mode 100644 navigation/mtc_files/Markers/try4 diff --git a/.gitignore b/.gitignore index 4ad10a2..92b5689 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ invesalius/*.swo invesalius/*.swp invesalius/data/*.log invesalius/data/*.pyc +invesalius/data/*.pyd invesalius/gui/*.log invesalius/gui/*.pyc invesalius/gui/widgets/*.log @@ -21,6 +22,7 @@ invesalius/reader/*.pyc tags *.c +.idea build *.patch *.tgz @@ -29,4 +31,4 @@ build *.cpp *.diff -*.directory +*.directory \ No newline at end of file diff --git a/invesalius/constants.py b/invesalius/constants.py index 3fd2663..70218e4 100644 --- a/invesalius/constants.py +++ b/invesalius/constants.py @@ -487,8 +487,9 @@ VTK_WARNING = 0 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE, ID_PROJECT_CLOSE, ID_PROJECT_INFO, ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET, -ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_ANALYZE_IMPORT, ID_PREFERENCES, -ID_DICOM_NETWORK, ID_TIFF_JPG_PNG, ID_VIEW_INTERPOLATED] = [wx.NewId() for number in range(15)] +ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_PREFERENCES, ID_DICOM_NETWORK, +ID_TIFF_JPG_PNG, ID_VIEW_INTERPOLATED, ID_ANALYZE_IMPORT, ID_NIFTI_IMPORT, +ID_PARREC_IMPORT] = [wx.NewId() for number in range(17)] ID_EXIT = wx.ID_EXIT ID_ABOUT = wx.ID_ABOUT @@ -637,3 +638,53 @@ BOOLEAN_UNION = 1 BOOLEAN_DIFF = 2 BOOLEAN_AND = 3 BOOLEAN_XOR = 4 + +#------------ Navigation defaults ------------------- + +SELECT = 0 +MTC = 1 +FASTRAK = 2 +ISOTRAKII = 3 +PATRIOT = 4 +DEBUGTRACK = 5 +DISCTRACK = 6 +DEFAULT_TRACKER = SELECT + +TRACKER = [_("Select tracker:"), _("Claron MicronTracker"), + _("Polhemus FASTRAK"), _("Polhemus ISOTRAK II"), + _("Polhemus PATRIOT"), _("Debug tracker"), + _("Disconnect tracker")] + +STATIC_REF = 0 +DYNAMIC_REF = 1 +DEFAULT_REF_MODE = DYNAMIC_REF +REF_MODE = [_("Static ref."), _("Dynamic ref.")] + +IR1 = wx.NewId() +IR2 = wx.NewId() +IR3 = wx.NewId() +TR1 = wx.NewId() +TR2 = wx.NewId() +TR3 = wx.NewId() +SET = wx.NewId() + +BTNS_IMG = {IR1: {0: _('LEI')}, + IR2: {1: _('REI')}, + IR3: {2: _('NAI')}} + +TIPS_IMG = [wx.ToolTip(_("Select left ear in image")), + wx.ToolTip(_("Select right ear in image")), + wx.ToolTip(_("Select nasion in image"))] + +BTNS_TRK = {TR1: {3: _('LET')}, + TR2: {4: _('RET')}, + TR3: {5: _('NAT')}, + SET: {6: _('SET')}} + +TIPS_TRK = [wx.ToolTip(_("Select left ear with spatial tracker")), + wx.ToolTip(_("Select right ear with spatial tracker")), + wx.ToolTip(_("Select nasion with spatial tracker")), + wx.ToolTip(_("Show set coordinates in image"))] + +CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles')) +MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers')) diff --git a/invesalius/control.py b/invesalius/control.py index a123ac8..cd617e2 100644 --- a/invesalius/control.py +++ b/invesalius/control.py @@ -16,11 +16,9 @@ # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais # detalhes. #-------------------------------------------------------------------------- -import math import os import plistlib import wx -import numpy from wx.lib.pubsub import pub as Publisher import invesalius.constants as const @@ -32,10 +30,10 @@ import invesalius.data.surface as srf import invesalius.data.volume as volume import invesalius.gui.dialogs as dialog import invesalius.project as prj -import invesalius.reader.analyze_reader as analyze import invesalius.reader.dicom_grouper as dg import invesalius.reader.dicom_reader as dcm import invesalius.reader.bitmap_reader as bmp +import invesalius.reader.others_reader as oth import invesalius.session as ses @@ -65,6 +63,8 @@ class Controller(): Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') Publisher.subscribe(self.OnShowDialogImportDirectory, 'Show import directory dialog') + Publisher.subscribe(self.OnShowDialogImportOtherFiles, + 'Show import other files dialog') Publisher.subscribe(self.OnShowDialogOpenProject, 'Show open project dialog') @@ -77,7 +77,9 @@ class Controller(): Publisher.subscribe(self.OnOpenDicomGroup, 'Open DICOM group') Publisher.subscribe(self.OnOpenBitmapFiles, - 'Open bitmap files') + 'Open bitmap files') + Publisher.subscribe(self.OnOpenOtherFiles, + 'Open other files') Publisher.subscribe(self.Progress, "Update dicom load") Publisher.subscribe(self.Progress, "Update bitmap load") Publisher.subscribe(self.OnLoadImportPanel, "End dicom load") @@ -88,7 +90,6 @@ class Controller(): Publisher.subscribe(self.OnShowDialogCloseProject, 'Close Project') Publisher.subscribe(self.OnOpenProject, 'Open project') Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project') - Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog') Publisher.subscribe(self.OnShowBitmapFile, 'Show bitmap dialog') Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog') @@ -116,6 +117,10 @@ class Controller(): def OnShowDialogImportDirectory(self, pubsub_evt): self.ShowDialogImportDirectory() + def OnShowDialogImportOtherFiles(self, pubsub_evt): + id_type = pubsub_evt.data + self.ShowDialogImportOtherFiles(id_type) + def OnShowDialogOpenProject(self, pubsub_evt): self.ShowDialogOpenProject() @@ -126,15 +131,6 @@ class Controller(): def OnShowDialogCloseProject(self, pubsub_evt): self.ShowDialogCloseProject() - def OnShowAnalyzeFile(self, pubsub_evt): - dirpath = dialog.ShowOpenAnalyzeDialog() - imagedata = analyze.ReadAnalyze(dirpath) - if imagedata: - self.CreateAnalyzeProject(imagedata) - - self.LoadProject() - Publisher.sendMessage("Enable state project", True) - def OnShowBitmapFile(self, pubsub_evt): self.ShowDialogImportBitmapFile() ########################### @@ -163,7 +159,6 @@ class Controller(): self.StartImportBitmapPanel(dirpath) # Publisher.sendMessage("Load data to import panel", dirpath) - def ShowDialogImportDirectory(self): # Offer to save current project if necessary session = ses.Session() @@ -186,6 +181,40 @@ class Controller(): self.StartImportPanel(dirpath) Publisher.sendMessage("Load data to import panel", dirpath) + def ShowDialogImportOtherFiles(self, id_type): + # Offer to save current project if necessary + session = ses.Session() + st = session.project_status + if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE): + filename = session.project_path[1] + answer = dialog.SaveChangesDialog2(filename) + if answer: + self.ShowDialogSaveProject() + self.CloseProject() + # Publisher.sendMessage("Enable state project", False) + Publisher.sendMessage('Set project name') + Publisher.sendMessage("Stop Config Recording") + Publisher.sendMessage("Set slice interaction style", const.STATE_DEFAULT) + + # Warning for limited support to Analyze format + if id_type == const.ID_ANALYZE_IMPORT: + dialog.ImportAnalyzeWarning() + + # Import project treating compressed nifti exception + suptype = ('hdr', 'nii', 'nii.gz', 'par') + filepath = dialog.ShowImportOtherFilesDialog(id_type) + name = filepath.rpartition('\\')[-1].split('.') + + if name[-1] == 'gz': + name[1] = 'nii.gz' + + filetype = name[1].lower() + + if filetype in suptype: + Publisher.sendMessage("Open other files", filepath) + else: + dialog.ImportInvalidFiles() + def ShowDialogOpenProject(self): # Offer to save current project if necessary session = ses.Session() @@ -281,8 +310,6 @@ class Controller(): else: dialog.InexistentPath(filepath) - - def OpenProject(self, filepath): Publisher.sendMessage('Begin busy cursor') path = os.path.abspath(filepath) @@ -342,7 +369,7 @@ class Controller(): def StartImportPanel(self, path): - # retrieve DICOM files splited into groups + # retrieve DICOM files split into groups reader = dcm.ProgressDicomReader() reader.SetWindowEvent(self.frame) reader.SetDirectoryPath(path) @@ -410,21 +437,33 @@ class Controller(): self.ImportMedicalImages(directory) def ImportMedicalImages(self, directory): - # OPTION 1: DICOM? patients_groups = dcm.GetDicomGroups(directory) + name = directory.rpartition('\\')[-1].split('.') + print "patients: ", patients_groups + if len(patients_groups): + # OPTION 1: DICOM group = dcm.SelectLargerDicomGroup(patients_groups) - matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0,0],gui=True) + matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=True) self.CreateDicomProject(dicom, matrix, matrix_filename) - # OPTION 2: ANALYZE? else: - imagedata = analyze.ReadDirectory(directory) - if imagedata: - self.CreateAnalyzeProject(imagedata) - # OPTION 3: Nothing... + # OPTION 2: NIfTI, Analyze or PAR/REC + if name[-1] == 'gz': + name[1] = 'nii.gz' + + suptype = ('hdr', 'nii', 'nii.gz', 'par') + filetype = name[1].lower() + + if filetype in suptype: + group = oth.ReadOthers(directory) else: utils.debug("No medical images found on given directory") return + + matrix, matrix_filename = self.OpenOtherFiles(group) + self.CreateOtherProject(str(name[0]), matrix, matrix_filename) + # OPTION 4: Nothing... + self.LoadProject() Publisher.sendMessage("Enable state project", True) @@ -493,43 +532,6 @@ class Controller(): Publisher.sendMessage('End busy cursor') - def CreateAnalyzeProject(self, imagedata): - header = imagedata.get_header() - proj = prj.Project() - proj.imagedata = None - proj.name = _("Untitled") - proj.SetAcquisitionModality("MRI") - #TODO: Verify if all Analyse are in AXIAL orientation - - # To get Z, X, Y (used by InVesaliu), not X, Y, Z - matrix, matrix_filename = image_utils.analyze2mmap(imagedata) - if header['orient'] == 0: - proj.original_orientation = const.AXIAL - elif header['orient'] == 1: - proj.original_orientation = const.CORONAL - elif header['orient'] == 2: - proj.original_orientation = const.SAGITAL - else: - proj.original_orientation = const.SAGITAL - - proj.threshold_range = (int(header['glmin']), - int(header['glmax'])) - proj.window = proj.threshold_range[1] - proj.threshold_range[0] - proj.level = (0.5 * (proj.threshold_range[1] + proj.threshold_range[0])) - proj.spacing = header['pixdim'][1:4] - proj.matrix_shape = matrix.shape - - self.Slice = sl.Slice() - self.Slice.matrix = matrix - self.Slice.matrix_filename = matrix_filename - - self.Slice.window_level = proj.level - self.Slice.window_width = proj.window - self.Slice.spacing = header.get_zooms()[:3] - - Publisher.sendMessage('Update threshold limits list', - proj.threshold_range) - def CreateDicomProject(self, dicom, matrix, matrix_filename): name_to_const = {"AXIAL":const.AXIAL, "CORONAL":const.CORONAL, @@ -604,6 +606,40 @@ class Controller(): dirpath = session.CreateProject(filename) + def CreateOtherProject(self, name, matrix, matrix_filename): + name_to_const = {"AXIAL": const.AXIAL, + "CORONAL": const.CORONAL, + "SAGITTAL": const.SAGITAL} + + proj = prj.Project() + proj.name = name + proj.modality = 'MRI' + proj.SetAcquisitionModality('MRI') + proj.matrix_shape = matrix.shape + proj.matrix_dtype = matrix.dtype.name + proj.matrix_filename = matrix_filename + + # Orientation must be CORONAL in order to as_closes_canonical and + # swap axis in img2memmap to work in a standardized way. + # TODO: Create standard import image for all acquisition orientations + orientation = 'CORONAL' + + proj.original_orientation =\ + name_to_const[orientation] + + proj.window = self.Slice.window_width + proj.level = self.Slice.window_level + proj.threshold_range = int(matrix.min()), int(matrix.max()) + proj.spacing = self.Slice.spacing + + ###### + session = ses.Session() + filename = proj.name + ".inv3" + + filename = filename.replace("/", "") # Fix problem case other/Skull_DICOM + + dirpath = session.CreateProject(filename) + def OnOpenBitmapFiles(self, pubsub_evt): rec_data = pubsub_evt.data bmp_data = bmp.BitmapData() @@ -688,6 +724,26 @@ class Controller(): self.LoadProject() Publisher.sendMessage("Enable state project", True) + def OnOpenOtherFiles(self, pubsub_evt): + filepath = pubsub_evt.data + name = filepath.rpartition('\\')[-1].split('.') + + if name[-1] == 'gz': + name[1] = 'nii.gz' + + suptype = ('hdr', 'nii', 'nii.gz', 'par') + filetype = name[1].lower() + + if filetype in suptype: + group = oth.ReadOthers(filepath) + else: + dialog.ImportInvalidFiles() + + matrix, matrix_filename = self.OpenOtherFiles(group) + self.CreateOtherProject(str(name[0]), matrix, matrix_filename) + self.LoadProject() + Publisher.sendMessage("Enable state project", True) + def OpenDicomGroup(self, dicom_group, interval, file_range, gui=True): # Retrieve general DICOM headers dicom = dicom_group.GetDicomSample() @@ -773,6 +829,30 @@ class Controller(): return self.matrix, self.filename, dicom + def OpenOtherFiles(self, group): + # Retreaving matrix from image data + self.matrix, scalar_range, self.filename = image_utils.img2memmap(group) + + hdr = group.header + hdr.set_data_dtype('int16') + dims = hdr.get_zooms() + dimsf = tuple([float(s) for s in dims]) + + wl = float((scalar_range[0] + scalar_range[1]) * 0.5) + ww = float((scalar_range[1] - scalar_range[0])) + + self.Slice = sl.Slice() + self.Slice.matrix = self.matrix + self.Slice.matrix_filename = self.filename + + self.Slice.spacing = dimsf + self.Slice.window_level = wl + self.Slice.window_width = ww + + scalar_range = int(scalar_range[0]), int(scalar_range[1]) + Publisher.sendMessage('Update threshold limits list', scalar_range) + return self.matrix, self.filename + def LoadImagedataInfo(self): proj = prj.Project() diff --git a/invesalius/data/bases.py b/invesalius/data/bases.py index 9e2c3fd..f31a3e6 100644 --- a/invesalius/data/bases.py +++ b/invesalius/data/bases.py @@ -1,85 +1,135 @@ -from numpy import * from math import sqrt +import numpy as np -class Bases: - - def __init__(self, p1, p2, p3): - - self.p1 = array([p1[0], p1[1], p1[2]]) - self.p2 = array([p2[0], p2[1], p2[2]]) - self.p3 = array([p3[0], p3[1], p3[2]]) - - print "p1: ", self.p1 - print "p2: ", self.p2 - print "p3: ", self.p3 - self.sub1 = self.p2 - self.p1 - self.sub2 = self.p3 - self.p1 - - def Basecreation(self): - #g1 - g1 = self.sub1 - - #g2 - lamb1 = g1[0]*self.sub2[0] + g1[1]*self.sub2[1] + g1[2]*self.sub2[2] - lamb2 = dot(g1, g1) - lamb = lamb1/lamb2 - - #Ponto q - q = self.p1 + lamb*self.sub1 - - #g1 e g2 com origem em q - g1 = self.p1 - q - g2 = self.p3 - q - - #testa se o g1 nao eh um vetor nulo - if g1.any() == False: - g1 = self.p2 - q - - #g3 - Produto vetorial NumPy - g3 = cross(g2, g1) - - #normalizacao dos vetores - g1 = g1/sqrt(lamb2) - g2 = g2/sqrt(dot(g2, g2)) - g3 = g3/sqrt(dot(g3, g3)) - - M = matrix([[g1[0],g1[1],g1[2]], [g2[0],g2[1],g2[2]], [g3[0],g3[1],g3[2]]]) - q.shape = (3, 1) - q = matrix(q.copy()) - print"M: ", M - print - print"q: ", q - print - Minv = M.I - - return M, q, Minv - -def FlipX(point): - - point = matrix(point + (0,)) - - #inverter o eixo z - ## possivel explicacaoo -- origem do eixo do imagedata esta no canto - ## superior esquerdo e origem da superfice eh no canto inferior esquerdo - ## ou a ordem de empilhamento das fatias - - point[0, 2] = -point[0, 2] - - #Flip em y - Mrot = matrix([[1.0, 0.0, 0.0, 0.0], - [0.0, -1.0, 0.0, 0.0], - [0.0, 0.0, -1.0, 0.0], - [0.0, 0.0, 0.0, 1.0]]) - Mtrans = matrix([[1.0, 0, 0, -point[0, 0]], - [0.0, 1.0, 0, -point[0, 1]], - [0.0, 0.0, 1.0, -point[0, 2]], - [0.0, 0.0, 0.0, 1.0]]) - Mtrans_return = matrix([[1.0, 0, 0, point[0, 0]], +def angle_calculation(ap_axis, coil_axis): + """ + Calculate angle between two given axis (in degrees) + + :param ap_axis: anterior posterior axis represented + :param coil_axis: tms coil axis + :return: angle between the two given axes + """ + + ap_axis = np.array([ap_axis[0], ap_axis[1]]) + coil_axis = np.array([float(coil_axis[0]), float(coil_axis[1])]) + angle = np.rad2deg(np.arccos((np.dot(ap_axis, coil_axis))/( + np.linalg.norm(ap_axis)*np.linalg.norm(coil_axis)))) + + return float(angle) + + +def base_creation(fiducials): + """ + Calculate the origin and matrix for coordinate system + transformation. + q: origin of coordinate system + g1, g2, g3: orthogonal vectors of coordinate system + + :param fiducials: array of 3 rows (p1, p2, p3) and 3 columns (x, y, z) with fiducials coordinates + :return: matrix and origin for base transformation + """ + + p1 = fiducials[0, :] + p2 = fiducials[1, :] + p3 = fiducials[2, :] + + sub1 = p2 - p1 + sub2 = p3 - p1 + lamb = (sub1[0]*sub2[0]+sub1[1]*sub2[1]+sub1[2]*sub2[2])/np.dot(sub1, sub1) + + q = p1 + lamb*sub1 + g1 = p1 - q + g2 = p3 - q + + if not g1.any(): + g1 = p2 - q + + g3 = np.cross(g2, g1) + + g1 = g1/sqrt(np.dot(g1, g1)) + g2 = g2/sqrt(np.dot(g2, g2)) + g3 = g3/sqrt(np.dot(g3, g3)) + + m = np.matrix([[g1[0], g1[1], g1[2]], + [g2[0], g2[1], g2[2]], + [g3[0], g3[1], g3[2]]]) + + q.shape = (3, 1) + q = np.matrix(q.copy()) + m_inv = m.I + + # print"M: ", m + # print"q: ", q + + return m, q, m_inv + + +def calculate_fre(fiducials, minv, n, q1, q2): + """ + Calculate the Fiducial Registration Error for neuronavigation. + + :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates + :param minv: inverse matrix given by base creation + :param n: base change matrix given by base creation + :param q1: origin of first base + :param q2: origin of second base + :return: float number of fiducial registration error + """ + + img = np.zeros([3, 3]) + dist = np.zeros([3, 1]) + + p1 = np.mat(fiducials[3, :]).reshape(3, 1) + p2 = np.mat(fiducials[4, :]).reshape(3, 1) + p3 = np.mat(fiducials[5, :]).reshape(3, 1) + + img[0, :] = np.asarray((q1 + (minv * n) * (p1 - q2)).reshape(1, 3)) + img[1, :] = np.asarray((q1 + (minv * n) * (p2 - q2)).reshape(1, 3)) + img[2, :] = np.asarray((q1 + (minv * n) * (p3 - q2)).reshape(1, 3)) + + dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) + dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2))) + dist[2] = np.sqrt(np.sum(np.power((img[2, :] - fiducials[2, :]), 2))) + + return float(np.sqrt(np.sum(dist ** 2) / 3)) + + +def flip_x(point): + """ + Flip coordinates of a vector according to X axis + Coronal Images do not require this transformation - 1 tested + and for this case, at navigation, the z axis is inverted + + It's necessary to multiply the z coordinate by (-1). Possibly + because the origin of coordinate system of imagedata is + located in superior left corner and the origin of VTK scene coordinate + system (polygonal surface) is in the interior left corner. Second + possibility is the order of slice stacking + + :param point: list of coordinates x, y and z + :return: flipped coordinates + """ + + # TODO: check if the Flip function is related to the X or Y axis + + point = np.matrix(point + (0,)) + point[0, 2] = -point[0, 2] + + m_rot = np.matrix([[1.0, 0.0, 0.0, 0.0], + [0.0, -1.0, 0.0, 0.0], + [0.0, 0.0, -1.0, 0.0], + [0.0, 0.0, 0.0, 1.0]]) + m_trans = np.matrix([[1.0, 0, 0, -point[0, 0]], + [0.0, 1.0, 0, -point[0, 1]], + [0.0, 0.0, 1.0, -point[0, 2]], + [0.0, 0.0, 0.0, 1.0]]) + m_trans_return = np.matrix([[1.0, 0, 0, point[0, 0]], [0.0, 1.0, 0, point[0, 1]], [0.0, 0.0, 1.0, point[0, 2]], [0.0, 0.0, 0.0, 1.0]]) - point_rot = point*Mtrans*Mrot*Mtrans_return - x, y, z = point_rot.tolist()[0][:3] - return x, y, z + point_rot = point*m_trans*m_rot*m_trans_return + x, y, z = point_rot.tolist()[0][:3] + + return x, y, z diff --git a/invesalius/data/co_registration.py b/invesalius/data/co_registration.py deleted file mode 100644 index 0468748..0000000 --- a/invesalius/data/co_registration.py +++ /dev/null @@ -1,65 +0,0 @@ -import threading - -import serial -import wx -from wx.lib.pubsub import pub as Publisher - -from numpy import * -from math import sqrt -from time import sleep - -class Corregister(threading.Thread): - - def __init__(self, bases, flag): - threading.Thread.__init__(self) - self.Minv = bases[0] - self.N = bases[1] - self.q1 = bases[2] - self.q2 = bases[3] - self.flag = flag - self._pause_ = 0 - self.start() - - def stop(self): - # Stop neuronavigation - self._pause_ = 1 - - def Coordinates(self): - #Get Polhemus points for neuronavigation - ser = serial.Serial(0) - ser.write("Y") - ser.write("P") - str = ser.readline() - ser.write("Y") - str = str.replace("\r\n","") - str = str.replace("-"," -") - aostr = [s for s in str.split()] - #aoflt -> 0:letter 1:x 2:y 3:z - aoflt = [float(aostr[1]), float(aostr[2]), float(aostr[3]), - float(aostr[4]), float(aostr[5]), float(aostr[6])] - ser.close() - - #Unit change: inches to millimeters - x = 25.4 - y = 25.4 - z = -25.4 - - coord = (aoflt[0]*x, aoflt[1]*y, aoflt[2]*z) - return coord - - def run(self): - while self.flag == True: - #Neuronavigation with Polhemus - trck = self.Coordinates() - tracker = matrix([[trck[0]], [trck[1]], [trck[2]]]) - img = self.q1 + (self.Minv*self.N)*(tracker - self.q2) - coord = [float(img[0]), float(img[1]), float(img[2])] - coord_cam = float(img[0]), float(img[1]), float(img[2]) - Publisher.sendMessage('Set ball reference position based on bound', coord_cam) - Publisher.sendMessage('Set camera in volume', coord_cam) - wx.CallAfter(Publisher.sendMessage, 'Render volume viewer') - wx.CallAfter(Publisher.sendMessage, 'Co-registered Points', coord) - sleep(0.005) - - if self._pause_: - return diff --git a/invesalius/data/coordinates.py b/invesalius/data/coordinates.py new file mode 100644 index 0000000..daa0f0e --- /dev/null +++ b/invesalius/data/coordinates.py @@ -0,0 +1,277 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- + +from math import sin, cos +import numpy as np + +from random import uniform + + +def GetCoordinates(trck_init, trck_id, ref_mode): + + """ + Read coordinates from spatial tracking devices using + + :param trck_init: Initialization variable of tracking device and connection type. See tracker.py. + :param trck_id: ID of tracking device. + :param ref_mode: Single or dynamic reference mode of tracking. + :return: array of six coordinates (x, y, z, alpha, beta, gamma) + """ + + coord = None + if trck_id: + getcoord = {1: ClaronCoord, + 2: PolhemusCoord, + 3: PolhemusCoord, + 4: PolhemusCoord, + 5: DebugCoord} + coord = getcoord[trck_id](trck_init, trck_id, ref_mode) + else: + print "Select Tracker" + + return coord + + +def ClaronCoord(trck_init, trck_id, ref_mode): + trck = trck_init[0] + scale = 10. * np.array([1., 1.0, -1.0]) + coord = None + k = 0 + # TODO: try to replace while and use some Claron internal computation + if ref_mode: + while k < 20: + try: + trck.Run() + probe = np.array([trck.PositionTooltipX1 * scale[0], trck.PositionTooltipY1 * scale[1], + trck.PositionTooltipZ1 * scale[2], trck.AngleX1, trck.AngleY1, trck.AngleZ1]) + reference = np.array([trck.PositionTooltipX2 * scale[0], trck.PositionTooltipY2 * scale[1], + trck.PositionTooltipZ2 * scale[2], trck.AngleX2, trck.AngleY2, trck.AngleZ2]) + k = 30 + except AttributeError: + k += 1 + print "wait, collecting coordinates ..." + if k == 30: + coord = dynamic_reference(probe, reference) + else: + while k < 20: + try: + trck.Run() + coord = np.array([trck.PositionTooltipX1 * scale[0], trck.PositionTooltipY1 * scale[1], + trck.PositionTooltipZ1 * scale[2], trck.AngleX1, trck.AngleY1, trck.AngleZ1]) + k = 30 + except AttributeError: + k += 1 + print "wait, collecting coordinates ..." + + return coord + + +def PolhemusCoord(trck, trck_id, ref_mode): + coord = None + + if trck[1] == 'serial': + coord = PolhemusSerialCoord(trck[0], trck_id, ref_mode) + + elif trck[1] == 'usb': + coord = PolhemusUSBCoord(trck[0], trck_id, ref_mode) + + elif trck[1] == 'wrapper': + coord = PolhemusWrapperCoord(trck[0], trck_id, ref_mode) + + return coord + + +def PolhemusWrapperCoord(trck, trck_id, ref_mode): + if trck_id == 2: + scale = 10. * np.array([1., 1.0, -1.0]) + else: + scale = 25.4 * np.array([1., 1.0, -1.0]) + coord = None + + if ref_mode: + trck.Run() + probe = np.array([float(trck.PositionTooltipX1) * scale[0], float(trck.PositionTooltipY1) * scale[1], + float(trck.PositionTooltipZ1) * scale[2], float(trck.AngleX1), float(trck.AngleY1), + float(trck.AngleZ1)]) + reference = np.array([float(trck.PositionTooltipX2) * scale[0], float(trck.PositionTooltipY2) * scale[1], + float(trck.PositionTooltipZ2) * scale[2], float(trck.AngleX2), float(trck.AngleY2), + float(trck.AngleZ2)]) + + if probe.all() and reference.all(): + coord = dynamic_reference(probe, reference) + + else: + trck.Run() + coord = np.array([float(trck.PositionTooltipX1) * scale[0], float(trck.PositionTooltipY1) * scale[1], + float(trck.PositionTooltipZ1) * scale[2], float(trck.AngleX1), float(trck.AngleY1), + float(trck.AngleZ1)]) + + return coord + + +def PolhemusUSBCoord(trck, trck_id, ref_mode): + endpoint = trck[0][(0, 0)][0] + # Tried to write some settings to Polhemus in trackers.py while initializing the device. + # TODO: Check if it's working properly. + trck.write(0x02, "P") + if trck_id == 2: + scale = 10. * np.array([1., 1.0, -1.0]) + else: + scale = 25.4 * np.array([1., 1.0, -1.0]) + coord = None + + if ref_mode: + + data = trck.read(endpoint.bEndpointAddress, 2 * endpoint.wMaxPacketSize) + data = str2float(data.tostring()) + + # six coordinates of first and second sensor: x, y, z and alfa, beta and gama + # jump one element for reference to avoid the sensor ID returned by Polhemus + probe = data[0] * scale[0], data[1] * scale[1], data[2] * scale[2], \ + data[3], data[4], data[5], data[6] + reference = data[7] * scale[0], data[8] * scale[1], data[9] * scale[2], data[10], \ + data[11], data[12], data[13] + + if probe.all() and reference.all(): + coord = dynamic_reference(probe, reference) + + return coord + + else: + data = trck.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize) + coord = str2float(data.tostring()) + + coord = np.array((coord[0] * scale[0], coord[1] * scale[1], coord[2] * scale[2], + coord[3], coord[4], coord[5])) + + return coord + + +def PolhemusSerialCoord(trck_init, trck_id, ref_mode): + # mudanca para fastrak - ref 1 tem somente x, y, z + # aoflt -> 0:letter 1:x 2:y 3:z + # this method is not optimized to work with all trackers, only with ISOTRAK + # serial connection is obsolete, remove in future + trck_init.write("P") + lines = trck_init.readlines() + + coord = None + + if lines[0][0] != '0': + print "The Polhemus is not connected!" + else: + for s in lines: + if s[1] == '1': + data = s + elif s[1] == '2': + data = s + + # single ref mode + if not ref_mode: + data = data.replace('-', ' -') + data = [s for s in data.split()] + j = 0 + while j == 0: + try: + plh1 = [float(s) for s in data[1:len(data)]] + j = 1 + except ValueError: + trck_init.write("P") + data = trck_init.readline() + data = data.replace('-', ' -') + data = [s for s in data.split()] + print "Trying to fix the error!!" + + coord = data[0:6] + return coord + + +def DebugCoord(trk_init, trck_id, ref_mode): + """ + Method to simulate a tracking device for debug and error check. Generate a random + x, y, z, alfa, beta and gama coordinates in interval [1, 200[ + :param trk_init: tracker initialization instance + :param ref_mode: flag for singular of dynamic reference + :param trck_id: id of tracking device + :return: six coordinates x, y, z, alfa, beta and gama + """ + + if ref_mode: + probe = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), + uniform(1, 200), uniform(1, 200), uniform(1, 200)]) + reference = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), + uniform(1, 200), uniform(1, 200), uniform(1, 200)]) + + coord = dynamic_reference(probe, reference) + + else: + coord = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), + uniform(1, 200), uniform(1, 200), uniform(1, 200)]) + + return coord + + +def dynamic_reference(probe, reference): + """ + Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama + rotation angles of reference to rotate the probe coordinate and returns the x, y, z + difference between probe and reference. Angles sequences and equation was extracted from + Polhemus manual and Attitude matrix in Wikipedia. + General equation is: + coord = Mrot * (probe - reference) + :param probe: sensor one defined as probe + :param reference: sensor two defined as reference + :return: rotated and translated coordinates + """ + a, b, g = np.radians(reference[3:6]) + + vet = probe[0:3] - reference[0:3] + vet = np.mat(vet.reshape(3, 1)) + + # Attitude Matrix given by Patriot Manual + Mrot = np.mat([[cos(a) * cos(b), sin(b) * sin(g) * cos(a) - cos(g) * sin(a), + cos(a) * sin(b) * cos(g) + sin(a) * sin(g)], + [cos(b) * sin(a), sin(b) * sin(g) * sin(a) + cos(g) * cos(a), + cos(g) * sin(b) * sin(a) - sin(g) * cos(a)], + [-sin(b), sin(g) * cos(b), cos(b) * cos(g)]]) + + coord_rot = Mrot.T * vet + coord_rot = np.squeeze(np.asarray(coord_rot)) + + return coord_rot[0], coord_rot[1], coord_rot[2], probe[3], probe[4], probe[5] + + +def str2float(data): + """ + Converts string detected wth Polhemus device to float array of coordinates. THis method applies + a correction for the minus sign in string that raises error while splitting the string into coordinates. + :param data: string of coordinates read with Polhemus + :return: six float coordinates x, y, z, alfa, beta and gama + """ + + count = 0 + for i, j in enumerate(data): + if j == '-': + data = data[:i + count] + ' ' + data[i + count:] + count += 1 + + data = [s for s in data.split()] + data = [float(s) for s in data[1:len(data)]] + + return data diff --git a/invesalius/data/coregistration.py b/invesalius/data/coregistration.py new file mode 100644 index 0000000..c82df2a --- /dev/null +++ b/invesalius/data/coregistration.py @@ -0,0 +1,81 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- + +import threading +from time import sleep + +from numpy import mat +import wx +from wx.lib.pubsub import pub as Publisher + +import invesalius.data.coordinates as dco + +# TODO: Optimize navigation thread. Remove the infinite loop and optimize sleep. + + +class Coregistration(threading.Thread): + """ + Thread to update the coordinates with the fiducial points + co-registration method while the Navigation Button is pressed. + Sleep function in run method is used to avoid blocking GUI and + for better real-time navigation + """ + + def __init__(self, bases, nav_id, trck_info): + threading.Thread.__init__(self) + self.bases = bases + self.nav_id = nav_id + self.trck_info = trck_info + self._pause_ = False + self.start() + + def stop(self): + self._pause_ = True + + def run(self): + m_inv = self.bases[0] + n = self.bases[1] + q1 = self.bases[2] + q2 = self.bases[3] + trck_init = self.trck_info[0] + trck_id = self.trck_info[1] + trck_mode = self.trck_info[2] + + while self.nav_id: + trck_coord = dco.GetCoordinates(trck_init, trck_id, trck_mode) + trck_xyz = mat([[trck_coord[0]], [trck_coord[1]], [trck_coord[2]]]) + + img = q1 + (m_inv*n)*(trck_xyz - q2) + + coord = (float(img[0]), float(img[1]), float(img[2]), trck_coord[3], + trck_coord[4], trck_coord[5]) + + # Tried several combinations and different locations to send the messages, + # however only this one does not block the GUI during navigation. + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', coord[0:3]) + wx.CallAfter(Publisher.sendMessage, 'Set camera in volume', coord[0:3]) + + # TODO: Optimize the value of sleep for each tracking device. + # Debug tracker is not working with 0.175 so changed to 0.2 + # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. + sleep(.3) + # sleep(0.175) + + if self._pause_: + return diff --git a/invesalius/data/imagedata_utils.py b/invesalius/data/imagedata_utils.py index dc24c51..80978d6 100644 --- a/invesalius/data/imagedata_utils.py +++ b/invesalius/data/imagedata_utils.py @@ -592,41 +592,55 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage): return matrix, scalar_range, temp_file -def analyze2mmap(analyze): - data = analyze.get_data() - header = analyze.get_header() + +def img2memmap(group): + """ + From a nibabel image data creates a memmap file in the temp folder and + returns it and its related filename. + """ + temp_file = tempfile.mktemp() - # Sagital - if header['orient'] == 2: - print "Orientation Sagital" - shape = tuple([data.shape[i] for i in (1, 2, 0)]) - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape) - for n, slice in enumerate(data): - matrix[:,:, n] = slice - - # Coronal - elif header['orient'] == 1: - print "Orientation coronal" - shape = tuple([data.shape[i] for i in (1, 0, 2)]) - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape) - for n, slice in enumerate(data): - matrix[:,n,:] = slice - - # AXIAL - elif header['orient'] == 0: - print "no orientation" - shape = tuple([data.shape[i] for i in (0, 1, 2)]) - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape) - for n, slice in enumerate(data): - matrix[n] = slice + data = group.get_data() + # Normalize image pixel values and convert to int16 + data = imgnormalize(data) - else: - print "Orientation Sagital" - shape = tuple([data.shape[i] for i in (1, 2, 0)]) - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape) - for n, slice in enumerate(data): - matrix[:,:, n] = slice + # Convert RAS+ to default InVesalius orientation ZYX + data = numpy.swapaxes(data, 0, 2) + data = numpy.fliplr(data) + matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=data.shape) + matrix[:] = data[:] matrix.flush() - return matrix, temp_file + + scalar_range = numpy.amin(matrix), numpy.amax(matrix) + + return matrix, scalar_range, temp_file + + +def imgnormalize(data, srange=(0, 255)): + """ + Normalize image pixel intensity for int16 gray scale values. + + :param data: image matrix + :param srange: range for normalization, default is 0 to 255 + :return: normalized pixel intensity matrix + """ + + dataf = numpy.asarray(data) + rangef = numpy.asarray(srange) + faux = numpy.ravel(dataf).astype(float) + minimum = numpy.min(faux) + maximum = numpy.max(faux) + lower = rangef[0] + upper = rangef[1] + + if minimum == maximum: + datan = numpy.ones(dataf.shape)*(upper + lower) / 2. + else: + datan = (faux-minimum)*(upper-lower) / (maximum-minimum) + lower + + datan = numpy.reshape(datan, dataf.shape) + datan = datan.astype(numpy.int16) + + return datan \ No newline at end of file diff --git a/invesalius/data/slice_.py b/invesalius/data/slice_.py index 65433bb..09f731b 100644 --- a/invesalius/data/slice_.py +++ b/invesalius/data/slice_.py @@ -531,10 +531,12 @@ class Slice(object): image = self.do_colour_image(ww_wl_image) if self.current_mask and self.current_mask.is_shown: if self.buffer_slices[orientation].vtk_mask: - print "Getting from buffer" + # Prints that during navigation causes delay in update + # print "Getting from buffer" mask = self.buffer_slices[orientation].vtk_mask else: - print "Do not getting from buffer" + # Prints that during navigation causes delay in update + # print "Do not getting from buffer" n_mask = self.get_mask_slice(orientation, slice_number) mask = converters.to_vtk(n_mask, self.spacing, slice_number, orientation) mask = self.do_colour_mask(mask, self.opacity) diff --git a/invesalius/data/styles.py b/invesalius/data/styles.py index 8cef65b..07d09a3 100644 --- a/invesalius/data/styles.py +++ b/invesalius/data/styles.py @@ -230,10 +230,8 @@ class CrossInteractorStyle(DefaultInteractorStyle): coord = self.viewer.calcultate_scroll_position(px, py) Publisher.sendMessage('Update cross position', (wx, wy, wz)) self.ScrollSlice(coord) - Publisher.sendMessage('Set ball reference position based on bound', - (wx, wy, wz)) + Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) Publisher.sendMessage('Set camera in volume', (wx, wy, wz)) - Publisher.sendMessage('Render volume viewer') iren.Render() diff --git a/invesalius/data/trackers.py b/invesalius/data/trackers.py new file mode 100644 index 0000000..2496ded --- /dev/null +++ b/invesalius/data/trackers.py @@ -0,0 +1,222 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- + +# TODO: Disconnect tracker when a new one is connected +# TODO: Test if there are too many prints when connection fails + + +def TrackerConnection(tracker_id, action): + """ + Initialize spatial trackers for coordinate detection during navigation. + + :param tracker_id: ID of tracking device. + :param action: string with to decide whether connect or disconnect the selected device. + :return spatial tracker initialization instance or None if could not open device. + """ + + if action == 'connect': + trck_fcn = {1: ClaronTracker, + 2: PolhemusTracker, # FASTRAK + 3: PolhemusTracker, # ISOTRAK + 4: PolhemusTracker, # PATRIOT + 5: DebugTracker} + + trck_init = trck_fcn[tracker_id](tracker_id) + + elif action == 'disconnect': + trck_init = DisconnectTracker(tracker_id) + + return trck_init + + +def DefaultTracker(tracker_id): + trck_init = None + try: + # import spatial tracker library + print 'Connect to default tracking device.' + + except: + print 'Could not connect to default tracker.' + + # return tracker initialization variable and type of connection + return trck_init, 'wrapper' + + +def ClaronTracker(tracker_id): + import invesalius.constants as const + + trck_init = None + try: + import pyclaron + + lib_mode = 'wrapper' + trck_init = pyclaron.pyclaron() + trck_init.CalibrationDir = const.CAL_DIR + trck_init.MarkerDir = const.MAR_DIR + trck_init.NumberFramesProcessed = 10 + trck_init.FramesExtrapolated = 0 + trck_init.Initialize() + + if trck_init.GetIdentifyingCamera(): + trck_init.Run() + print "MicronTracker camera identified." + else: + trck_init = None + + except ImportError: + lib_mode = 'error' + print 'The ClaronTracker library is not installed.' + + return trck_init, lib_mode + + +def PolhemusTracker(tracker_id): + trck_init = None + try: + trck_init = PlhWrapperConnection() + lib_mode = 'wrapper' + if not trck_init: + print 'Could not connect with Polhemus wrapper, trying USB connection...' + trck_init = PlhUSBConnection(tracker_id) + lib_mode = 'usb' + if not trck_init: + print 'Could not connect with Polhemus USB, trying serial connection...' + trck_init = PlhSerialConnection(tracker_id) + lib_mode = 'serial' + except: + lib_mode = 'error' + print 'Could not connect to Polhemus.' + + return trck_init, lib_mode + + +def DebugTracker(tracker_id): + trck_init = True + print 'Debug device started.' + return trck_init, 'debug' + + +def PlhWrapperConnection(): + trck_init = None + try: + import polhemus + + trck_init = polhemus.polhemus() + trck_check = trck_init.Initialize() + + if trck_check: + # First run is necessary to discard the first coord collection + trck_init.Run() + except: + print 'Could not connect to Polhemus via wrapper.' + + return trck_init + + +def PlhSerialConnection(tracker_id): + trck_init = None + try: + import serial + + trck_init = serial.Serial(0, baudrate=115200, timeout=0.2) + + if tracker_id == 2: + # Polhemus FASTRAK needs configurations first + trck_init.write(0x02, "u") + trck_init.write(0x02, "F") + elif tracker_id == 3: + # Polhemus ISOTRAK needs to set tracking point from + # center to tip. + trck_init.write("Y") + + trck_init.write('P') + data = trck_init.readlines() + + if not data: + trck_init = None + + except: + print 'Could not connect to Polhemus serial.' + + return trck_init + + +def PlhUSBConnection(tracker_id): + trck_init = None + try: + import usb.core as uc + trck_init = uc.find(idVendor=0x0F44, idProduct=0x0003) + cfg = trck_init.get_active_configuration() + for i in cfg: + for x in i: + # TODO: try better code + x = x + trck_init.set_configuration() + endpoint = trck_init[0][(0, 0)][0] + if tracker_id == 2: + # Polhemus FASTRAK needs configurations first + # TODO: Check configurations to standardize initialization for all Polhemus devices + trck_init.write(0x02, "u") + trck_init.write(0x02, "F") + # First run to confirm that everything is working + trck_init.write(0x02, "P") + data = trck_init.read(endpoint.bEndpointAddress, + endpoint.wMaxPacketSize) + if not data: + trck_init = None + + except uc.USBError as err: + print 'Could not set configuration %s' % err + else: + print 'Could not connect to Polhemus USB.' + + return trck_init + + +def DisconnectTracker(tracker_id): + """ + Disconnect current spatial tracker + + :param tracker_id: ID of tracking device. + """ + trck_init = None + # TODO: create individual functions to disconnect each other device, e.g. Polhemus. + if tracker_id == 1: + try: + import pyclaron + pyclaron.pyclaron().Close() + lib_mode = 'wrapper' + except ImportError: + lib_mode = 'error' + print 'The ClaronTracker library is not installed.' + + elif tracker_id == 4: + try: + import polhemus + polhemus.polhemus().Close() + lib_mode = 'wrapper' + except ImportError: + lib_mode = 'error' + print 'The polhemus library is not installed.' + + elif tracker_id == 5: + print 'Debug tracker disconnected.' + lib_mode = 'debug' + + return trck_init, lib_mode \ No newline at end of file diff --git a/invesalius/data/trigger.py b/invesalius/data/trigger.py new file mode 100644 index 0000000..638ea52 --- /dev/null +++ b/invesalius/data/trigger.py @@ -0,0 +1,64 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- + +import threading +from time import sleep + +import wx +from wx.lib.pubsub import pub as Publisher + + +class Trigger(threading.Thread): + """ + Thread created to use external trigger to interact with software during neuronavigation + """ + + def __init__(self, nav_id): + threading.Thread.__init__(self) + self.trigger_init = None + try: + import serial + + self.trigger_init = serial.Serial('COM1', baudrate=115200, timeout=0) + self.nav_id = nav_id + self._pause_ = False + self.start() + + except ImportError: + print 'PySerial library not installed. Please install to use Trigger option.' + + except serial.serialutil.SerialException: + print 'Connection with port COM1 failed.' + + def stop(self): + if self.trigger_init: + self.trigger_init.close() + self._pause_ = True + + def run(self): + while self.nav_id: + lines = self.trigger_init.readlines() + # Following lines can simulate a trigger in 3 sec repetitions + # sleep(3) + # lines = True + if lines: + wx.CallAfter(Publisher.sendMessage, 'Create marker') + sleep(0.175) + if self._pause_: + return diff --git a/invesalius/data/viewer_slice.py b/invesalius/data/viewer_slice.py index 4526602..f92bd30 100755 --- a/invesalius/data/viewer_slice.py +++ b/invesalius/data/viewer_slice.py @@ -911,17 +911,15 @@ class Viewer(wx.Panel): if self.slice_data.cursor: self.slice_data.cursor.SetColour(colour_vtk) - def Navigation(self, pubsub_evt): + def UpdateSlicesNavigation(self, pubsub_evt): # Get point from base change - x, y, z = pubsub_evt.data - coord_cross = x, y, z - position = self.slice_data.actor.GetInput().FindPoint(x, y, z) - coord_cross = self.slice_data.actor.GetInput().GetPoint(position) - coord = self.calcultate_scroll_position(position) - Publisher.sendMessage('Update cross position', coord_cross) + wx, wy, wz = pubsub_evt.data + px, py = self.get_slice_pixel_coord_by_world_pos(wx, wy, wz) + coord = self.calcultate_scroll_position(px, py) + self.cross.SetFocalPoint((wx, wy, wz)) self.ScrollSlice(coord) - self.interactor.Render() + Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) def ScrollSlice(self, coord): if self.orientation == "AXIAL": @@ -1165,8 +1163,8 @@ class Viewer(wx.Panel): self.orientation)) Publisher.subscribe(self.__update_cross_position, 'Update cross position') - Publisher.subscribe(self.Navigation, - 'Co-registered Points') + Publisher.subscribe(self.UpdateSlicesNavigation, + 'Co-registered points') ### # Publisher.subscribe(self.ChangeBrushColour, # 'Add mask') diff --git a/invesalius/data/viewer_volume.py b/invesalius/data/viewer_volume.py index 3924b24..5fa6bca 100755 --- a/invesalius/data/viewer_volume.py +++ b/invesalius/data/viewer_volume.py @@ -22,7 +22,8 @@ import sys -import numpy +import numpy as np +from numpy.core.umath_tests import inner1d import wx import vtk from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor @@ -45,10 +46,11 @@ class Viewer(wx.Panel): self.interaction_style = st.StyleStateManager() - self.ball_reference = None - self.initial_foco = None + self.initial_focus = None - style = vtk.vtkInteractorStyleTrackballCamera() + self.staticballs = [] + + style = vtk.vtkInteractorStyleTrackballCamera() self.style = style interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize()) @@ -111,6 +113,9 @@ class Viewer(wx.Panel): self.repositioned_coronal_plan = 0 self.added_actor = 0 + self.camera_state = True + + self.ball_actor = None self._mode_cross = False self._to_show_ball = 0 self._ball_ref_visibility = False @@ -153,6 +158,7 @@ class Viewer(wx.Panel): Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range') Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') + Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') Publisher.subscribe(self.OnEnableStyle, 'Enable style') Publisher.subscribe(self.OnDisableStyle, 'Disable style') @@ -174,23 +180,24 @@ class Viewer(wx.Panel): Publisher.subscribe(self.OnStartSeed,'Create surface by seeding - start') Publisher.subscribe(self.OnEndSeed,'Create surface by seeding - end') - Publisher.subscribe(self.ActivateBallReference, - 'Activate ball reference') - Publisher.subscribe(self.DeactivateBallReference, - 'Deactivate ball reference') - Publisher.subscribe(self.SetBallReferencePosition, - 'Set ball reference position') - Publisher.subscribe(self.SetBallReferencePositionBasedOnBound, - 'Set ball reference position based on bound') Publisher.subscribe(self.SetStereoMode, 'Set stereo mode') Publisher.subscribe(self.Reposition3DPlane, 'Reposition 3D Plane') Publisher.subscribe(self.RemoveVolume, 'Remove Volume') + Publisher.subscribe(self.SetBallReferencePosition, + 'Set ball reference position') Publisher.subscribe(self._check_ball_reference, 'Enable style') Publisher.subscribe(self._uncheck_ball_reference, 'Disable style') + # Related to marker creation in navigation tools + Publisher.subscribe(self.AddMarker, 'Add marker') + Publisher.subscribe(self.HideAllMarkers, 'Hide all markers') + Publisher.subscribe(self.ShowAllMarkers, 'Show all markers') + Publisher.subscribe(self.RemoveAllMarkers, 'Remove all markers') + Publisher.subscribe(self.RemoveMarker, 'Remove marker') + def SetStereoMode(self, pubsub_evt): mode = pubsub_evt.data ren_win = self.interactor.GetRenderWindow() @@ -220,43 +227,6 @@ class Viewer(wx.Panel): self.interactor.Render() - def CreateBallReference(self): - MRAD = 3.0 - proj = prj.Project() - s = proj.spacing - # The sphere's radius will be MRAD times bigger than the media of the - # spacing values. - r = (s[0] + s[1] + s[2]) / 3.0 * MRAD - self.ball_reference = vtk.vtkSphereSource() - self.ball_reference.SetRadius(r) - - mapper = vtk.vtkPolyDataMapper() - mapper.SetInputConnection(self.ball_reference.GetOutputPort()) - - p = vtk.vtkProperty() - p.SetColor(1, 0, 0) - - self.ball_actor = vtk.vtkActor() - self.ball_actor.SetMapper(mapper) - self.ball_actor.SetProperty(p) - - def RemoveBallReference(self): - self._ball_ref_visibility = False - if self.ball_reference: - self.ren.RemoveActor(self.ball_actor) - - def ActivateBallReference(self, pubsub_evt): - self._mode_cross = True - self._ball_ref_visibility = True - if self._to_show_ball: - if not self.ball_reference: - self.CreateBallReference() - self.ren.AddActor(self.ball_actor) - - def DeactivateBallReference(self, pubsub_evt): - self._mode_cross = False - self.RemoveBallReference() - def _check_ball_reference(self, pubsub_evt): st = pubsub_evt.data if st == const.SLICE_STATE_CROSS: @@ -279,19 +249,6 @@ class Viewer(wx.Panel): self._to_show_ball -= 1 self._check_and_set_ball_visibility() - def SetBallReferencePosition(self, pubsub_evt): - x, y, z = pubsub_evt.data - self.ball_reference.SetCenter(x, y, z) - - def SetBallReferencePositionBasedOnBound(self, pubsub_evt): - if self._to_show_ball: - self.ActivateBallReference(None) - coord = pubsub_evt.data - x, y, z = bases.FlipX(coord) - self.ball_reference.SetCenter(x, y, z) - else: - self.DeactivateBallReference(None) - def OnStartSeed(self, pubsub_evt): index = pubsub_evt.data self.seed_points = [] @@ -402,7 +359,6 @@ class Viewer(wx.Panel): actor.PickableOff() self.ren.AddActor(actor) - self.points_reference.append(actor) def RemoveAllPointsReference(self): @@ -418,6 +374,113 @@ class Viewer(wx.Panel): actor = self.points_reference.pop(point) self.ren.RemoveActor(actor) + def AddMarker(self, pubsub_evt): + """ + Markers create by navigation tools and + rendered in volume viewer. + """ + self.ball_id = pubsub_evt.data[0] + ballsize = pubsub_evt.data[1] + ballcolour = pubsub_evt.data[2] + coord = pubsub_evt.data[3] + x, y, z = bases.flip_x(coord) + + ball_ref = vtk.vtkSphereSource() + ball_ref.SetRadius(ballsize) + ball_ref.SetCenter(x, y, z) + + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(ball_ref.GetOutputPort()) + + prop = vtk.vtkProperty() + prop.SetColor(ballcolour) + + #adding a new actor for the present ball + self.staticballs.append(vtk.vtkActor()) + + self.staticballs[self.ball_id].SetMapper(mapper) + self.staticballs[self.ball_id].SetProperty(prop) + + self.ren.AddActor(self.staticballs[self.ball_id]) + self.ball_id = self.ball_id + 1 + self.UpdateRender() + + def HideAllMarkers(self, pubsub_evt): + ballid = pubsub_evt.data + for i in range(0, ballid): + self.staticballs[i].SetVisibility(0) + self.UpdateRender() + + def ShowAllMarkers(self, pubsub_evt): + ballid = pubsub_evt.data + for i in range(0, ballid): + self.staticballs[i].SetVisibility(1) + self.UpdateRender() + + def RemoveAllMarkers(self, pubsub_evt): + ballid = pubsub_evt.data + for i in range(0, ballid): + self.ren.RemoveActor(self.staticballs[i]) + self.staticballs = [] + self.UpdateRender() + + def RemoveMarker(self, pubsub_evt): + index = pubsub_evt.data + self.ren.RemoveActor(self.staticballs[index]) + del self.staticballs[index] + self.ball_id = self.ball_id - 1 + self.UpdateRender() + + def CreateBallReference(self): + """ + Red sphere on volume visualization to reference center of + cross in slice planes. + The sphere's radius will be scale times bigger than the average of + image spacing values. + """ + scale = 3.0 + proj = prj.Project() + s = proj.spacing + r = (s[0] + s[1] + s[2]) / 3.0 * scale + + ball_source = vtk.vtkSphereSource() + ball_source.SetRadius(r) + + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputConnection(ball_source.GetOutputPort()) + + self.ball_actor = vtk.vtkActor() + self.ball_actor.SetMapper(mapper) + self.ball_actor.GetProperty().SetColor(1, 0, 0) + + self.ren.AddActor(self.ball_actor) + + def ActivateBallReference(self): + self._mode_cross = True + self._ball_ref_visibility = True + if self._to_show_ball: + if not self.ball_actor: + self.CreateBallReference() + + def RemoveBallReference(self): + self._mode_cross = False + self._ball_ref_visibility = False + if self.ball_actor: + self.ren.RemoveActor(self.ball_actor) + self.ball_actor = None + + def SetBallReferencePosition(self, pubsub_evt): + if self._to_show_ball: + if not self.ball_actor: + self.ActivateBallReference() + + coord = pubsub_evt.data + x, y, z = bases.flip_x(coord) + self.ball_actor.SetPosition(x, y, z) + + else: + self.RemoveBallReference() + def __bind_events_wx(self): #self.Bind(wx.EVT_SIZE, self.OnSize) pass @@ -580,30 +643,38 @@ class Viewer(wx.Panel): self.ren.ResetCamera() self.ren.ResetCameraClippingRange() + def SetVolumeCameraState(self, pubsub_evt): + self.camera_state = pubsub_evt.data + def SetVolumeCamera(self, pubsub_evt): - - coord_camera = pubsub_evt.data - coord_camera = numpy.array(bases.FlipX(coord_camera)) - - cam = self.ren.GetActiveCamera() - - if self.initial_foco is None: - self.initial_foco = numpy.array(cam.GetFocalPoint()) - - cam_initialposition = numpy.array(cam.GetPosition()) - cam_initialfoco = numpy.array(cam.GetFocalPoint()) - - cam_sub = cam_initialposition - cam_initialfoco - cam_sub_norm = numpy.linalg.norm(cam_sub) - vet1 = cam_sub/cam_sub_norm - - cam_sub_novo = coord_camera - self.initial_foco - cam_sub_novo_norm = numpy.linalg.norm(cam_sub_novo) - vet2 = cam_sub_novo/cam_sub_novo_norm - vet2 = vet2*cam_sub_norm + coord_camera - - cam.SetFocalPoint(coord_camera) - cam.SetPosition(vet2) + if self.camera_state: + #TODO: exclude dependency on initial focus + cam_focus = np.array(bases.flip_x(pubsub_evt.data)) + cam = self.ren.GetActiveCamera() + + if self.initial_focus is None: + self.initial_focus = np.array(cam.GetFocalPoint()) + + cam_pos0 = np.array(cam.GetPosition()) + cam_focus0 = np.array(cam.GetFocalPoint()) + + v0 = cam_pos0 - cam_focus0 + v0n = np.sqrt(inner1d(v0, v0)) + + v1 = (cam_focus - self.initial_focus) + v1n = np.sqrt(inner1d(v1, v1)) + if not v1n: + v1n = 1.0 + cam_pos = (v1/v1n)*v0n + cam_focus + + cam.SetFocalPoint(cam_focus) + cam.SetPosition(cam_pos) + + # It works without doing the reset. Check with trackers if there is any difference. + # Need to be outside condition for sphere marker position update + # self.ren.ResetCameraClippingRange() + # self.ren.ResetCamera() + self.interactor.Render() def OnExportSurface(self, pubsub_evt): filename, filetype = pubsub_evt.data @@ -706,19 +777,18 @@ class Viewer(wx.Panel): self.interactor.Render() self._to_show_ball -= 1 self._check_and_set_ball_visibility() - + def RemoveAllActor(self, pubsub_evt): utils.debug("RemoveAllActor") self.ren.RemoveAllProps() Publisher.sendMessage('Render volume viewer') - def LoadSlicePlane(self, pubsub_evt): self.slice_plane = SlicePlane() def LoadVolume(self, pubsub_evt): self.raycasting_volume = True - #self._to_show_ball += 1 + self._to_show_ball += 1 volume = pubsub_evt.data[0] colour = pubsub_evt.data[1] @@ -895,14 +965,16 @@ class Viewer(wx.Panel): self.repositioned_coronal_plan = 1 def _check_and_set_ball_visibility(self): + #TODO: When creating Raycasting volume and cross is pressed, it is not + # automatically creating the ball reference. if self._mode_cross: if self._to_show_ball > 0 and not self._ball_ref_visibility: - self.ActivateBallReference(None) + self.ActivateBallReference() self.interactor.Render() elif not self._to_show_ball and self._ball_ref_visibility: self.RemoveBallReference() self.interactor.Render() - + class SlicePlane: def __init__(self): diff --git a/invesalius/gui/default_tasks.py b/invesalius/gui/default_tasks.py index 86a22f2..328037f 100644 --- a/invesalius/gui/default_tasks.py +++ b/invesalius/gui/default_tasks.py @@ -250,7 +250,8 @@ class UpperTaskPanel(wx.Panel): tasks = [(_("Load data"), importer.TaskPanel), (_("Select region of interest"), slice_.TaskPanel), (_("Configure 3D surface"), surface.TaskPanel), - (_("Utilize navigation system"), navigator.TaskPanel)] + (_("Export data"), exporter.TaskPanel), + (_("Navigation system"), navigator.TaskPanel)] for i in xrange(len(tasks)): (name, panel) = tasks[i] diff --git a/invesalius/gui/dialogs.py b/invesalius/gui/dialogs.py index 451a4c1..34bc7c5 100644 --- a/invesalius/gui/dialogs.py +++ b/invesalius/gui/dialogs.py @@ -22,8 +22,11 @@ import os import random import sys +import vtk import wx import wx.combo + +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor from wx.lib import masked from wx.lib.agw import floatspin from wx.lib.wordwrap import wordwrap @@ -206,48 +209,27 @@ class ProgressDialog(object): self.dlg.Destroy() - -#--------- -WILDCARD_OPEN = "InVesalius 3 project (*.inv3)|*.inv3|"\ +# --------- +WILDCARD_OPEN = "InVesalius 3 project (*.inv3)|*.inv3|" \ "All files (*.*)|*.*" -WILDCARD_ANALYZE = "Analyze (*.hdr)|*.hdr|"\ - "All files (*.*)|*.*" +WILDCARD_ANALYZE = "Analyze 7.5 (*.hdr)|*.hdr|" \ + "All files (*.*)|*.*" -def ShowOpenProjectDialog(): - # Default system path - current_dir = os.path.abspath(".") - dlg = wx.FileDialog(None, message=_("Open InVesalius 3 project..."), - defaultDir="", - defaultFile="", wildcard=WILDCARD_OPEN, - style=wx.FD_OPEN|wx.FD_CHANGE_DIR) +WILDCARD_NIFTI = "NIfTI 1 (*.nii)|*.nii|" \ + "Compressed NIfTI (*.nii.gz)|*.nii.gz|" \ + "All files (*.*)|*.*" - # inv3 filter is default - dlg.SetFilterIndex(0) +WILDCARD_PARREC = "PAR/REC (*.par)|*.par|" \ + "All files (*.*)|*.*" - # Show the dialog and retrieve the user response. If it is the OK response, - # process the data. - filepath = None - try: - if dlg.ShowModal() == wx.ID_OK: - # This returns a Python list of files that were selected. - filepath = dlg.GetPath() - except(wx._core.PyAssertionError): #FIX: win64 - filepath = dlg.GetPath() - # Destroy the dialog. Don't do this until you are done with it! - # BAD things can happen otherwise! - dlg.Destroy() - os.chdir(current_dir) - return filepath - - -def ShowOpenAnalyzeDialog(): +def ShowOpenProjectDialog(): # Default system path current_dir = os.path.abspath(".") - dlg = wx.FileDialog(None, message=_("Open Analyze file"), + dlg = wx.FileDialog(None, message=_("Open InVesalius 3 project..."), defaultDir="", - defaultFile="", wildcard=WILDCARD_ANALYZE, + defaultFile="", wildcard=WILDCARD_OPEN, style=wx.FD_OPEN|wx.FD_CHANGE_DIR) # inv3 filter is default @@ -260,7 +242,7 @@ def ShowOpenAnalyzeDialog(): if dlg.ShowModal() == wx.ID_OK: # This returns a Python list of files that were selected. filepath = dlg.GetPath() - except(wx._core.PyAssertionError): #FIX: win64 + except(wx._core.PyAssertionError): # FIX: win64 filepath = dlg.GetPath() # Destroy the dialog. Don't do this until you are done with it! @@ -353,6 +335,47 @@ def ShowImportBitmapDirDialog(): return path +def ShowImportOtherFilesDialog(id_type): + # Default system path + current_dir = os.path.abspath(".") + dlg = wx.FileDialog(None, message=_("Import Analyze 7.5 file"), + defaultDir="", + defaultFile="", wildcard=WILDCARD_ANALYZE, + style=wx.FD_OPEN | wx.FD_CHANGE_DIR) + + if id_type == const.ID_NIFTI_IMPORT: + dlg.SetMessage(_("Import NIFTi 1 file")) + dlg.SetWildcard(WILDCARD_NIFTI) + elif id_type == const.ID_PARREC_IMPORT: + dlg.SetMessage(_("Import PAR/REC file")) + dlg.SetWildcard(WILDCARD_PARREC) + + # inv3 filter is default + dlg.SetFilterIndex(0) + + # 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: + # GetPath returns in unicode, if a path has non-ascii characters a + # UnicodeEncodeError is raised. To avoid this, path is encoded in utf-8 + if sys.platform == "win32": + filename = dlg.GetPath() + else: + filename = dlg.GetPath().encode('utf-8') + + except(wx._core.PyAssertionError): # TODO: error win64 + if (dlg.GetPath()): + filename = dlg.GetPath() + + # Destroy the dialog. Don't do this until you are done with it! + # BAD things can happen otherwise! + dlg.Destroy() + os.chdir(current_dir) + return filename + + def ShowSaveAsProjectDialog(default_filename=None): current_dir = os.path.abspath(".") dlg = wx.FileDialog(None, @@ -380,10 +403,70 @@ def ShowSaveAsProjectDialog(default_filename=None): if filename.split(".")[-1] != extension: filename = filename + "." + extension + os.chdir(current_dir) + return filename + + +# Dialog for neuronavigation markers +def ShowSaveMarkersDialog(default_filename=None): + current_dir = os.path.abspath(".") + dlg = wx.FileDialog(None, + _("Save markers as..."), # title + "", # last used directory + default_filename, + _("Markers (*.txt)|*.txt"), + wx.SAVE | wx.OVERWRITE_PROMPT) + # dlg.SetFilterIndex(0) # default is VTI + + filename = None + try: + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + ok = 1 + else: + ok = 0 + except(wx._core.PyAssertionError): # TODO: fix win64 + filename = dlg.GetPath() + ok = 1 + + if (ok): + extension = "txt" + if sys.platform != 'win32': + if filename.split(".")[-1] != extension: + filename = filename + "." + extension os.chdir(current_dir) return filename + +def ShowLoadMarkersDialog(): + current_dir = os.path.abspath(".") + + dlg = wx.FileDialog(None, message=_("Load markers"), + defaultDir="", + defaultFile="", + style=wx.OPEN|wx.CHANGE_DIR) + + # inv3 filter is default + dlg.SetFilterIndex(0) + + # Show the dialog and retrieve the user response. If it is the OK response, + # process the data. + filepath = None + try: + if dlg.ShowModal() == wx.ID_OK: + # This returns a Python list of files that were selected. + filepath = dlg.GetPath() + except(wx._core.PyAssertionError): # FIX: win64 + filepath = dlg.GetPath() + + # Destroy the dialog. Don't do this until you are done with it! + # BAD things can happen otherwise! + dlg.Destroy() + os.chdir(current_dir) + return filepath + + class MessageDialog(wx.Dialog): def __init__(self, message): pre = wx.PreDialog() @@ -509,6 +592,7 @@ def ImportEmptyDirectory(dirpath): dlg.ShowModal() dlg.Destroy() + def ImportInvalidFiles(ftype="DICOM"): if ftype == "Bitmap": msg = _("There are no Bitmap, JPEG, PNG or TIFF files in the selected folder.") @@ -524,6 +608,20 @@ def ImportInvalidFiles(ftype="DICOM"): dlg.ShowModal() dlg.Destroy() + +def ImportAnalyzeWarning(): + msg1 = _("Warning! InVesalius has limited support to Analyze format.\n") + msg2 = _("Slices may be wrongly oriented and functions may not work properly.") + if sys.platform == 'darwin': + dlg = wx.MessageDialog(None, "", msg1 + msg2, + wx.ICON_INFORMATION | wx.OK) + else: + dlg = wx.MessageDialog(None, msg1 + msg2, "InVesalius 3", + wx.ICON_INFORMATION | wx.OK) + dlg.ShowModal() + dlg.Destroy() + + def InexistentMask(): msg = _("A mask is needed to create a surface.") if sys.platform == 'darwin': @@ -593,6 +691,86 @@ def SurfaceSelectionRequiredForDuplication(): dlg.ShowModal() dlg.Destroy() + +# Dialogs for neuronavigation mode +def InvalidFiducials(): + msg = _("Fiducials are invalid. Select six coordinates.") + if sys.platform == 'darwin': + dlg = wx.MessageDialog(None, "", msg, + wx.ICON_INFORMATION | wx.OK) + else: + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", + wx.ICON_INFORMATION | wx.OK) + dlg.ShowModal() + dlg.Destroy() + + +def NavigationTrackerWarning(trck_id, lib_mode): + """ + Spatial Tracker connection error + """ + trck = {1: 'Claron MicronTracker', + 2: 'Polhemus FASTRAK', + 3: 'Polhemus ISOTRAK', + 4: 'Polhemus PATRIOT', + 5: 'Debug tracker device'} + + if lib_mode == 'choose': + msg = _('No tracking device selected') + elif lib_mode == 'error': + msg = trck[trck_id] + _(' is not installed.') + elif lib_mode == 'disconnect': + msg = trck[trck_id] + _(' disconnected.') + else: + msg = trck[trck_id] + _(' is not connected.') + + if sys.platform == 'darwin': + dlg = wx.MessageDialog(None, "", msg, + wx.ICON_INFORMATION | wx.OK) + else: + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", + wx.ICON_INFORMATION | wx.OK) + + dlg.ShowModal() + dlg.Destroy() + + +def InvalidMarkersFile(): + msg = _("The TXT file is invalid.") + if sys.platform == 'darwin': + dlg = wx.MessageDialog(None, "", msg, + wx.ICON_INFORMATION | wx.OK) + else: + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", + wx.ICON_INFORMATION | wx.OK) + dlg.ShowModal() + dlg.Destroy() + + +def NoMarkerSelected(): + msg = _("No data selected") + if sys.platform == 'darwin': + dlg = wx.MessageDialog(None, "", msg, + wx.ICON_INFORMATION | wx.OK) + else: + dlg = wx.MessageDialog(None,msg, "InVesalius 3 - Neuronavigator", + wx.ICON_INFORMATION | wx.OK) + dlg.ShowModal() + dlg.Destroy() + + +def EnterMarkerID(default): + msg = _("Edit marker ID") + if sys.platform == 'darwin': + dlg = wx.TextEntryDialog(None, "", msg, defaultValue=default) + else: + dlg = wx.TextEntryDialog(None, msg, "InVesalius 3", defaultValue=default) + dlg.ShowModal() + result = dlg.GetValue() + dlg.Destroy() + return result + + class NewMask(wx.Dialog): def __init__(self, parent=None, @@ -828,6 +1006,8 @@ def ShowAboutDialog(parent): info.Developers = ["Paulo Henrique Junqueira Amorim", "Thiago Franco de Moraes", "Jorge Vicente Lopes da Silva", + "Victor Hugo de Oliveira e Souza (navigator)", + "Renan Hiroshi Matsuda (navigator)" "Tatiana Al-Chueyr (former)", "Guilherme Cesar Soares Ruppert (former)", "Fabio de Souza Azevedo (former)", diff --git a/invesalius/gui/frame.py b/invesalius/gui/frame.py index b6c4bab..11c2f57 100644 --- a/invesalius/gui/frame.py +++ b/invesalius/gui/frame.py @@ -396,7 +396,11 @@ class Frame(wx.Frame): elif id == const.ID_PROJECT_OPEN: self.ShowOpenProject() elif id == const.ID_ANALYZE_IMPORT: - self.ShowAnalyzeImporter() + self.ShowImportOtherFiles(id) + elif id == const.ID_NIFTI_IMPORT: + self.ShowImportOtherFiles(id) + elif id == const.ID_PARREC_IMPORT: + self.ShowImportOtherFiles(id) elif id == const.ID_TIFF_JPG_PNG: self.ShowBitmapImporter() elif id == const.ID_PROJECT_SAVE: @@ -538,6 +542,12 @@ class Frame(wx.Frame): Show import DICOM panel. as dicom """ Publisher.sendMessage('Show import directory dialog') + def ShowImportOtherFiles(self, id_file): + """ + Show import Analyze, NiFTI1 or PAR/REC dialog. + """ + Publisher.sendMessage('Show import other files dialog', id_file) + def ShowRetrieveDicomPanel(self): Publisher.sendMessage('Show retrieve dicom panel') @@ -553,12 +563,6 @@ class Frame(wx.Frame): """ Publisher.sendMessage('Show save dialog', True) - def ShowAnalyzeImporter(self): - """ - Show save as dialog. - """ - Publisher.sendMessage('Show analyze dialog', True) - def ShowBitmapImporter(self): """ Tiff, BMP, JPEG and PNG @@ -672,7 +676,9 @@ class MenuBar(wx.MenuBar): #Import Others Files others_file_menu = wx.Menu() - others_file_menu.Append(const.ID_ANALYZE_IMPORT, "Analyze") + others_file_menu.Append(const.ID_ANALYZE_IMPORT, _("Analyze 7.5")) + others_file_menu.Append(const.ID_NIFTI_IMPORT, _("NIfTI 1")) + others_file_menu.Append(const.ID_PARREC_IMPORT, _("PAR/REC")) others_file_menu.Append(const.ID_TIFF_JPG_PNG, u"TIFF,BMP,JPG or PNG (\xb5CT)") # FILE diff --git a/invesalius/gui/task_importer.py b/invesalius/gui/task_importer.py index 7fddd26..ecd1903 100644 --- a/invesalius/gui/task_importer.py +++ b/invesalius/gui/task_importer.py @@ -64,8 +64,8 @@ class InnerTaskPanel(wx.Panel): self.float_hyper_list = [] # Fixed hyperlink items - tooltip = wx.ToolTip(_("Select DICOM files to be reconstructed")) - link_import_local = hl.HyperLinkCtrl(self, -1, _("Import DICOM images...")) + tooltip = wx.ToolTip(_("Select DICOM, Analyze, NIfTI or REC/PAR files to be reconstructed")) + link_import_local = hl.HyperLinkCtrl(self, -1, _("Import medical images...")) link_import_local.SetUnderlines(False, False, False) link_import_local.SetBold(True) link_import_local.SetColours("BLACK", "BLACK", "BLACK") diff --git a/invesalius/gui/task_navigator.py b/invesalius/gui/task_navigator.py index 95e150c..ad8f8eb 100644 --- a/invesalius/gui/task_navigator.py +++ b/invesalius/gui/task_navigator.py @@ -17,433 +17,727 @@ # detalhes. #-------------------------------------------------------------------------- -import os +from functools import partial import sys -import serial +import numpy as np import wx import wx.lib.hyperlink as hl import wx.lib.masked.numctrl -import wx.lib.platebtn as pbtn from wx.lib.pubsub import pub as Publisher +import invesalius.constants as const import invesalius.data.bases as db -import invesalius.data.co_registration as dcr -import invesalius.project as project -IR1 = wx.NewId() -IR2 = wx.NewId() -IR3 = wx.NewId() -PR1 = wx.NewId() -PR2 = wx.NewId() -PR3 = wx.NewId() -Neuronavigate = wx.NewId() -Corregistration = wx.NewId() -GetPoint = wx.NewId() +import invesalius.data.coordinates as dco +import invesalius.data.coregistration as dcr +import invesalius.data.trackers as dt +import invesalius.data.trigger as trig +import invesalius.gui.dialogs as dlg +import invesalius.gui.widgets.foldpanelbar as fpb +import invesalius.gui.widgets.colourselect as csel + class TaskPanel(wx.Panel): - """ - This panel works as a "frame", drawing a white margin arround - the panel that really matters (InnerTaskPanel). - """ def __init__(self, parent): - # note: don't change this class!!! wx.Panel.__init__(self, parent) inner_panel = InnerTaskPanel(self) sizer = wx.BoxSizer(wx.HORIZONTAL) - sizer.Add(inner_panel, 1, wx.EXPAND | wx.GROW | wx.BOTTOM | wx.RIGHT | - wx.LEFT, 8) + sizer.Add(inner_panel, 1, wx.EXPAND|wx.GROW|wx.BOTTOM|wx.RIGHT | + wx.LEFT, 7) sizer.Fit(self) self.SetSizer(sizer) self.Update() self.SetAutoLayout(1) + class InnerTaskPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + default_colour = self.GetBackgroundColour() + background_colour = wx.Colour(255,255,255) + self.SetBackgroundColour(background_colour) + + txt_nav = wx.StaticText(self, -1, _('Select fiducials and navigate'), + size=wx.Size(90, 20)) + txt_nav.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.BOLD)) + + # Create horizontal sizer to represent lines in the panel + txt_sizer = wx.BoxSizer(wx.HORIZONTAL) + txt_sizer.Add(txt_nav, 1, wx.EXPAND|wx.GROW, 5) + + # Fold panel which contains navigation configurations + fold_panel = FoldPanel(self) + fold_panel.SetBackgroundColour(default_colour) + + + # Add line sizer into main sizer + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(txt_sizer, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 5) + main_sizer.Add(fold_panel, 1, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 5) + main_sizer.AddSpacer(5) + main_sizer.Fit(self) + + self.SetSizerAndFit(main_sizer) + self.Update() + self.SetAutoLayout(1) + self.sizer = main_sizer + + +class FoldPanel(wx.Panel): def __init__(self, parent): - wx.Panel.__init__(self, parent, size=wx.Size(320,300)) - self.SetBackgroundColour(wx.Colour(221, 221, 221, 255)) + wx.Panel.__init__(self, parent) + + inner_panel = InnerFoldPanel(self) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(inner_panel, 0, wx.EXPAND|wx.GROW) + sizer.Fit(self) + + self.SetSizerAndFit(sizer) + self.Update() self.SetAutoLayout(1) - self.__bind_events() - self.aux_img_ref1 = 0 - self.aux_img_ref2 = 0 - self.aux_img_ref3 = 0 - self.flagpoint = 0 - self.aux_plh_ref1 = 1 - self.aux_plh_ref2 = 1 - self.aux_plh_ref3 = 1 - self.a = 0, 0, 0 - self.coord1a = (0, 0, 0) - self.coord2a = (0, 0, 0) - self.coord3a = (0, 0, 0) - self.coord1b = (0, 0, 0) - self.coord2b = (0, 0, 0) - self.coord3b = (0, 0, 0) - self.correg = None - - self.button_img_ref1 = wx.ToggleButton(self, IR1, label = 'TEI', size = wx.Size(30,23)) - self.button_img_ref1.Bind(wx.EVT_TOGGLEBUTTON, self.Img_Ref_ToggleButton1) - - self.button_img_ref2 = wx.ToggleButton(self, IR2, label = 'TDI', size = wx.Size(30,23)) - self.button_img_ref2.Bind(wx.EVT_TOGGLEBUTTON, self.Img_Ref_ToggleButton2) - - self.button_img_ref3 = wx.ToggleButton(self, IR3, label = 'FNI', size = wx.Size(30,23)) - self.button_img_ref3.Bind(wx.EVT_TOGGLEBUTTON, self.Img_Ref_ToggleButton3) - - self.button_plh_ref1 = wx.Button(self, PR1, label = 'TEP', size = wx.Size(30,23)) - self.button_plh_ref2 = wx.Button(self, PR2, label = 'TDP', size = wx.Size(30,23)) - self.button_plh_ref3 = wx.Button(self, PR3, label = 'FNP', size = wx.Size(30,23)) - self.button_crg = wx.Button(self, Corregistration, label = 'Corregistrate') - self.button_getpoint = wx.Button(self, GetPoint, label = 'GP', size = wx.Size(23,23)) - self.Bind(wx.EVT_BUTTON, self.Buttons) - - self.button_neuronavigate = wx.ToggleButton(self, Neuronavigate, "Neuronavigate") - self.button_neuronavigate.Bind(wx.EVT_TOGGLEBUTTON, self.Neuronavigate_ToggleButton) - - self.numCtrl1a = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1a', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2a = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2a', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3a = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3a', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl1b = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1b', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2b = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2b', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3b = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3b', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl1c = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1c', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2c = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2c', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3c = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3c', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl1d = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1d', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2d = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2d', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3d = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3d', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl1e = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1e', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2e = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2e', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3e = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3e', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl1f = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1f', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2f = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2f', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3f = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3f', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl1g = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl1g', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl2g = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl2g', parent=self, integerWidth = 4, fractionWidth = 1) - self.numCtrl3g = wx.lib.masked.numctrl.NumCtrl( - name='numCtrl3g', parent=self, integerWidth = 4, fractionWidth = 1) - - RefImg_sizer1 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - RefImg_sizer1.AddMany([ (self.button_img_ref1), - (self.numCtrl1a), - (self.numCtrl2a), - (self.numCtrl3a)]) - - RefImg_sizer2 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - RefImg_sizer2.AddMany([ (self.button_img_ref2), - (self.numCtrl1b), - (self.numCtrl2b), - (self.numCtrl3b)]) - - RefImg_sizer3 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - RefImg_sizer3.AddMany([ (self.button_img_ref3), - (self.numCtrl1c), - (self.numCtrl2c), - (self.numCtrl3c)]) - - RefPlh_sizer1 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - RefPlh_sizer1.AddMany([ (self.button_plh_ref1, 0, wx.GROW|wx.EXPAND), - (self.numCtrl1d, wx.RIGHT), - (self.numCtrl2d), - (self.numCtrl3d, wx.LEFT)]) - - RefPlh_sizer2 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - RefPlh_sizer2.AddMany([ (self.button_plh_ref2, 0, wx.GROW|wx.EXPAND), - (self.numCtrl1e, 0, wx.RIGHT), - (self.numCtrl2e), - (self.numCtrl3e, 0, wx.LEFT)]) - - RefPlh_sizer3 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - RefPlh_sizer3.AddMany([ (self.button_plh_ref3, 0, wx.GROW|wx.EXPAND), - (self.numCtrl1f, wx.RIGHT), - (self.numCtrl2f), - (self.numCtrl3f, wx.LEFT)]) - - Buttons_sizer4 = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5) - Buttons_sizer4.AddMany([ (self.button_crg, wx.RIGHT), - (self.button_neuronavigate, wx.LEFT)]) - - GetPoint_sizer5 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5) - GetPoint_sizer5.AddMany([ (self.button_getpoint, 0, wx.GROW|wx.EXPAND), - (self.numCtrl1g, wx.RIGHT), - (self.numCtrl2g), - (self.numCtrl3g, wx.LEFT)]) - - text = wx.StaticText(self, -1, 'Neuronavigator') +class InnerFoldPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) + self.SetBackgroundColour(default_colour) + + # Fold panel and its style settings + # FIXME: If we dont insert a value in size or if we set wx.DefaultSize, + # the fold_panel doesnt show. This means that, for some reason, Sizer + # is not working properly in this panel. It might be on some child or + # parent panel. Perhaps we need to insert the item into the sizer also... + # Study this. + + fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, + (10, 293), 0, fpb.FPB_SINGLE_FOLD) + + # Fold panel style + style = fpb.CaptionBarStyle() + style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) + style.SetFirstColour(default_colour) + style.SetSecondColour(default_colour) + + # Fold 1 - Navigation panel + item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True) + ntw = NeuronavigationPanel(item) + + fold_panel.ApplyCaptionStyle(item, style) + fold_panel.AddFoldPanelWindow(item, ntw, Spacing= 0, + leftSpacing=0, rightSpacing=0) + fold_panel.Expand(fold_panel.GetFoldPanel(0)) + + # Fold 2 - Markers panel + item = fold_panel.AddFoldPanel(_("Extra tools"), collapsed=True) + mtw = MarkersPanel(item) + + fold_panel.ApplyCaptionStyle(item, style) + fold_panel.AddFoldPanelWindow(item, mtw, Spacing= 0, + leftSpacing=0, rightSpacing=0) + - Ref_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5) - Ref_sizer.AddGrowableCol(0, 1) - Ref_sizer.AddGrowableRow(0, 1) - Ref_sizer.AddGrowableRow(1, 1) - Ref_sizer.AddGrowableRow(2, 1) - Ref_sizer.AddGrowableRow(3, 1) - Ref_sizer.AddGrowableRow(4, 1) - Ref_sizer.AddGrowableRow(5, 1) - Ref_sizer.AddGrowableRow(6, 1) - Ref_sizer.AddGrowableRow(7, 1) - Ref_sizer.AddGrowableRow(8, 1) - Ref_sizer.SetFlexibleDirection(wx.BOTH) - Ref_sizer.AddMany([ (text, 0, wx.ALIGN_CENTER_HORIZONTAL), - (RefImg_sizer1, 0, wx.ALIGN_CENTER_HORIZONTAL), - (RefImg_sizer2, 0, wx.ALIGN_CENTER_HORIZONTAL), - (RefImg_sizer3, 0, wx.ALIGN_CENTER_HORIZONTAL), - (RefPlh_sizer1, 0, wx.ALIGN_CENTER_HORIZONTAL), - (RefPlh_sizer2, 0, wx.ALIGN_CENTER_HORIZONTAL), - (RefPlh_sizer3, 0, wx.ALIGN_CENTER_HORIZONTAL), - (Buttons_sizer4, 0, wx.ALIGN_CENTER_HORIZONTAL), - (GetPoint_sizer5, 0, wx.ALIGN_CENTER_HORIZONTAL)]) + # Check box for camera update in volume rendering during navigation + tooltip = wx.ToolTip(_("Update camera in volume")) + checkcamera = wx.CheckBox(self, -1, _('Volume camera')) + checkcamera.SetToolTip(tooltip) + checkcamera.SetValue(True) + checkcamera.Bind(wx.EVT_CHECKBOX, partial(self.UpdateVolumeCamera, ctrl=checkcamera)) + + # Check box for camera update in volume rendering during navigation + tooltip = wx.ToolTip(_("Enable external trigger for creating markers")) + checktrigger = wx.CheckBox(self, -1, _('External trigger')) + checktrigger.SetToolTip(tooltip) + checktrigger.SetValue(False) + checktrigger.Bind(wx.EVT_CHECKBOX, partial(self.UpdateExternalTrigger, ctrl=checktrigger)) + + if sys.platform != 'win32': + checkcamera.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + checktrigger.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + + line_sizer = wx.BoxSizer(wx.HORIZONTAL) + line_sizer.Add(checkcamera, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5) + line_sizer.Add(checktrigger, 1,wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5) + line_sizer.Fit(self) + + # Panel sizer to expand fold panel + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(fold_panel, 0, wx.GROW|wx.EXPAND) + sizer.Add(line_sizer, 1, wx.GROW | wx.EXPAND) + sizer.Fit(self) + + self.SetSizer(sizer) + self.Update() + self.SetAutoLayout(1) + def UpdateExternalTrigger(self, evt, ctrl): + Publisher.sendMessage('Update trigger state', ctrl.GetValue()) + + def UpdateVolumeCamera(self, evt, ctrl): + Publisher.sendMessage('Update volume camera state', ctrl.GetValue()) + + + + +class NeuronavigationPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) + self.SetBackgroundColour(default_colour) + + self.SetAutoLayout(1) + + self.__bind_events() + + # Initialize global variables + self.fiducials = np.full([6, 3], np.nan) + self.correg = None + self.current_coord = 0, 0, 0 + self.trk_init = None + self.trigger = None + self.trigger_state = False + + self.tracker_id = const.DEFAULT_TRACKER + self.ref_mode_id = const.DEFAULT_REF_MODE + + # Initialize list of buttons and numctrls for wx objects + self.btns_coord = [None] * 7 + self.numctrls_coord = [list(), list(), list(), list(), list(), list(), list()] + + # ComboBox for spatial tracker device selection + tooltip = wx.ToolTip(_("Choose the tracking device")) + choice_trck = wx.ComboBox(self, -1, "", + choices=const.TRACKER, style=wx.CB_DROPDOWN|wx.CB_READONLY) + choice_trck.SetToolTip(tooltip) + choice_trck.SetSelection(const.DEFAULT_TRACKER) + choice_trck.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceTracker, ctrl=choice_trck)) + + # ComboBox for tracker reference mode + tooltip = wx.ToolTip(_("Choose the navigation reference mode")) + choice_ref = wx.ComboBox(self, -1, "", + choices=const.REF_MODE, style=wx.CB_DROPDOWN|wx.CB_READONLY) + choice_ref.SetSelection(const.DEFAULT_REF_MODE) + choice_ref.SetToolTip(tooltip) + choice_ref.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceRefMode, ctrl=choice_trck)) + + # Toggle buttons for image fiducials + btns_img = const.BTNS_IMG + tips_img = const.TIPS_IMG + + for k in btns_img: + n = btns_img[k].keys()[0] + lab = btns_img[k].values()[0] + self.btns_coord[n] = wx.ToggleButton(self, k, label=lab, size=wx.Size(30, 23)) + self.btns_coord[n].SetToolTip(tips_img[n]) + self.btns_coord[n].Bind(wx.EVT_TOGGLEBUTTON, self.OnImageFiducials) + + # Push buttons for tracker fiducials + btns_trk = const.BTNS_TRK + tips_trk = const.TIPS_TRK + + for k in btns_trk: + n = btns_trk[k].keys()[0] + lab = btns_trk[k].values()[0] + self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(30, 23)) + self.btns_coord[n].SetToolTip(tips_trk[n-3]) + # Excepetion for event of button that set image coordinates + if n == 6: + self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnSetImageCoordinates) + else: + self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnTrackerFiducials) + + # TODO: Find a better allignment between FRE, text and navigate button + txt_fre = wx.StaticText(self, -1, _('FRE:')) + + # Fiducial registration error text box + tooltip = wx.ToolTip(_("Fiducial registration error")) + txtctrl_fre = wx.TextCtrl(self, value="", size=wx.Size(60, -1), style=wx.TE_CENTRE) + txtctrl_fre.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.BOLD)) + txtctrl_fre.SetBackgroundColour('WHITE') + txtctrl_fre.SetEditable(0) + txtctrl_fre.SetToolTip(tooltip) + + # Toggle button for neuronavigation + tooltip = wx.ToolTip(_("Start navigation")) + btn_nav = wx.ToggleButton(self, -1, _("Navigate"), size=wx.Size(80, -1)) + btn_nav.SetToolTip(tooltip) + btn_nav.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnNavigate, btn=(btn_nav, choice_trck, choice_ref, txtctrl_fre))) + + # Image and tracker coordinates number controls + for m in range(0, 7): + for n in range(0, 3): + self.numctrls_coord[m].append( + wx.lib.masked.numctrl.NumCtrl(parent=self, integerWidth=4, fractionWidth=1)) + + # Sizers to group all GUI objects + choice_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5) + choice_sizer.AddMany([(choice_trck, wx.LEFT), + (choice_ref, wx.RIGHT)]) + + coord_sizer = wx.GridBagSizer(hgap=5, vgap=5) + + for m in range(0, 7): + coord_sizer.Add(self.btns_coord[m], pos=wx.GBPosition(m, 0)) + for n in range(0, 3): + coord_sizer.Add(self.numctrls_coord[m][n], pos=wx.GBPosition(m, n+1)) + if m in range(1, 6): + self.numctrls_coord[m][n].SetEditable(False) + + nav_sizer = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5) + nav_sizer.AddMany([(txt_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), + (txtctrl_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), + (btn_nav, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) + + group_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5) + group_sizer.AddGrowableCol(0, 1) + group_sizer.AddGrowableRow(0, 1) + group_sizer.AddGrowableRow(1, 1) + group_sizer.AddGrowableRow(2, 1) + group_sizer.SetFlexibleDirection(wx.BOTH) + group_sizer.AddMany([(choice_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL), + (coord_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL), + (nav_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)]) + main_sizer = wx.BoxSizer(wx.HORIZONTAL) - main_sizer.Add(Ref_sizer, 1, wx.ALIGN_CENTER_HORIZONTAL, 10) + main_sizer.Add(group_sizer, 1, wx.ALIGN_CENTER_HORIZONTAL, 10) self.sizer = main_sizer self.SetSizer(main_sizer) self.Fit() - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the image") - self.button_img_ref1.SetToolTip(tooltip) - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the image") - self.button_img_ref2.SetToolTip(tooltip) - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the image") - self.button_img_ref3.SetToolTip(tooltip) - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the space") - self.button_plh_ref1.SetToolTip(tooltip) - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the space") - self.button_plh_ref2.SetToolTip(tooltip) - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the space") - self.button_plh_ref3.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1a.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1b.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1c.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1d.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1e.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1f.SetToolTip(tooltip) - tooltip = wx.ToolTip("X Coordinate") - self.numCtrl1g.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2a.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2b.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2c.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2d.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2e.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2f.SetToolTip(tooltip) - tooltip = wx.ToolTip("Y Coordinate") - self.numCtrl2g.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3a.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3b.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3c.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3d.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3e.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3f.SetToolTip(tooltip) - tooltip = wx.ToolTip("Z Coordinate") - self.numCtrl3g.SetToolTip(tooltip) - tooltip = wx.ToolTip("Corregistration of the real position with the image position") - self.button_crg.SetToolTip(tooltip) - tooltip = wx.ToolTip("Neuronavigation") - self.button_neuronavigate.SetToolTip(tooltip) - tooltip = wx.ToolTip("Get Cross Center Coordinates") - self.button_getpoint.SetToolTip(tooltip) - def __bind_events(self): - Publisher.subscribe(self.__update_points_img, 'Update cross position') - Publisher.subscribe(self.__update_points_plh, 'Update plh position') - - def __update_points_img(self, pubsub_evt): - x, y, z = pubsub_evt.data[1] - self.a = x, y, z - if self.aux_img_ref1 == 0: - self.numCtrl1a.SetValue(x) - self.numCtrl2a.SetValue(y) - self.numCtrl3a.SetValue(z) - if self.aux_img_ref2 == 0: - self.numCtrl1b.SetValue(x) - self.numCtrl2b.SetValue(y) - self.numCtrl3b.SetValue(z) - if self.aux_img_ref3 == 0: - self.numCtrl1c.SetValue(x) - self.numCtrl2c.SetValue(y) - self.numCtrl3c.SetValue(z) + Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') + Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') + Publisher.subscribe(self.UpdateImageCoordinates, 'Set ball reference position') + + def LoadImageFiducials(self, pubsub_evt): + marker_id = pubsub_evt.data[0] + coord = pubsub_evt.data[1] + for n in const.BTNS_IMG: + btn_id = const.BTNS_IMG[n].keys()[0] + fid_id = const.BTNS_IMG[n].values()[0] + if marker_id == fid_id and not self.btns_coord[btn_id].GetValue(): + self.btns_coord[btn_id].SetValue(True) + self.fiducials[btn_id, :] = coord[0:3] + for m in [0, 1, 2]: + self.numctrls_coord[btn_id][m].SetValue(coord[m]) + + def UpdateImageCoordinates(self, pubsub_evt): + # TODO: Change from world coordinates to matrix coordinates. They are better for multi software communication. + self.current_coord = pubsub_evt.data + for m in [0, 1, 2, 6]: + if m == 6 and self.btns_coord[m].IsEnabled(): + for n in [0, 1, 2]: + self.numctrls_coord[m][n].SetValue(self.current_coord[n]) + elif m != 6 and not self.btns_coord[m].GetValue(): + # btn_state = self.btns_coord[m].GetValue() + # if not btn_state: + for n in [0, 1, 2]: + self.numctrls_coord[m][n].SetValue(self.current_coord[n]) + + def UpdateTriggerState (self, pubsub_evt): + self.trigger_state = pubsub_evt.data + + def OnChoiceTracker(self, evt, ctrl): + if evt: + choice = evt.GetSelection() + else: + choice = self.tracker_id + + if self.trk_init: + trck = self.trk_init[0] + else: + trck = None + + # Conditions check if click was on current selection and if any other tracker + # has been initialized before + if trck and choice != 6: + self.ResetTrackerFiducials() + self.trk_init = dt.TrackerConnection(self.tracker_id, 'disconnect') + self.tracker_id = choice + if not self.trk_init[0]: + self.trk_init = dt.TrackerConnection(self.tracker_id, 'connect') + if not self.trk_init[0]: + dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) + ctrl.SetSelection(0) + print "Tracker not connected!" + else: + ctrl.SetSelection(self.tracker_id) + print "Tracker connected!" + elif choice == 6: + if trck: + self.trk_init = dt.TrackerConnection(self.tracker_id, 'disconnect') + if not self.trk_init[0]: + dlg.NavigationTrackerWarning(self.tracker_id, 'disconnect') + self.tracker_id = 0 + ctrl.SetSelection(self.tracker_id) + print "Tracker disconnected!" + else: + print "Tracker still connected!" + else: + ctrl.SetSelection(self.tracker_id) + + else: + # If trk_init is None try to connect. If doesn't succeed show dialog. + if choice: + self.tracker_id = choice + self.trk_init = dt.TrackerConnection(self.tracker_id, 'connect') + if not self.trk_init[0]: + dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) + self.tracker_id = 0 + ctrl.SetSelection(self.tracker_id) + + def OnChoiceRefMode(self, evt, ctrl): + # When ref mode is changed the tracker coords are set to zero + self.ref_mode_id = evt.GetSelection() + self.ResetTrackerFiducials() + # Some trackers do not accept restarting within this time window + # TODO: Improve the restarting of trackers after changing reference mode + # self.OnChoiceTracker(None, ctrl) + print "Reference mode changed!" + + def OnSetImageCoordinates(self, evt): + # FIXME: Cross does not update in last clicked slice, only on the other two + btn_id = const.BTNS_TRK[evt.GetId()].keys()[0] + + wx, wy, wz = self.numctrls_coord[btn_id][0].GetValue(), \ + self.numctrls_coord[btn_id][1].GetValue(), \ + self.numctrls_coord[btn_id][2].GetValue() + + Publisher.sendMessage('Set ball reference position', (wx, wy, wz)) + Publisher.sendMessage('Set camera in volume', (wx, wy, wz)) + Publisher.sendMessage('Co-registered points', (wx, wy, wz)) + Publisher.sendMessage('Update cross position', (wx, wy, wz)) + + def OnImageFiducials(self, evt): + btn_id = const.BTNS_IMG[evt.GetId()].keys()[0] + marker_id = const.BTNS_IMG[evt.GetId()].values()[0] + + if self.btns_coord[btn_id].GetValue(): + coord = self.numctrls_coord[btn_id][0].GetValue(),\ + self.numctrls_coord[btn_id][1].GetValue(),\ + self.numctrls_coord[btn_id][2].GetValue() + + self.fiducials[btn_id, :] = coord[0:3] + Publisher.sendMessage('Create marker', (coord, marker_id)) + else: + for n in [0, 1, 2]: + self.numctrls_coord[btn_id][n].SetValue(self.current_coord[n]) + + self.fiducials[btn_id, :] = np.nan + Publisher.sendMessage('Delete fiducial marker', marker_id) + + def OnTrackerFiducials(self, evt): + btn_id = const.BTNS_TRK[evt.GetId()].keys()[0] + coord = None + + if self.trk_init and self.tracker_id: + coord = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) + else: + dlg.NavigationTrackerWarning(0, 'choose') + + # Update number controls with tracker coordinates + if coord is not None: + self.fiducials[btn_id, :] = coord[0:3] + for n in [0, 1, 2]: + self.numctrls_coord[btn_id][n].SetValue(float(coord[n])) + + def OnNavigate(self, evt, btn): + btn_nav = btn[0] + choice_trck = btn[1] + choice_ref = btn[2] + txtctrl_fre = btn[3] + + nav_id = btn_nav.GetValue() + if nav_id: + if np.isnan(self.fiducials).any(): + dlg.InvalidFiducials() + btn_nav.SetValue(False) + + else: + tooltip = wx.ToolTip(_("Stop neuronavigation")) + btn_nav.SetToolTip(tooltip) + + # Disable all navigation buttons + choice_ref.Enable(False) + choice_trck.Enable(False) + for btn_c in self.btns_coord: + btn_c.Enable(False) + + m, q1, minv = db.base_creation(self.fiducials[0:3, :]) + n, q2, ninv = db.base_creation(self.fiducials[3::, :]) + + tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id + # FIXME: FRE is taking long to calculate so it updates on GUI delayed to navigation - I think its fixed + # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok + fre = db.calculate_fre(self.fiducials, minv, n, q1, q2) + + txtctrl_fre.SetValue(str(round(fre, 2))) + if fre <= 3: + txtctrl_fre.SetBackgroundColour('GREEN') + else: + txtctrl_fre.SetBackgroundColour('RED') + + if self.trigger_state: + self.trigger = trig.Trigger(nav_id) + + self.correg = dcr.Coregistration((minv, n, q1, q2), nav_id, tracker_mode) + + else: + tooltip = wx.ToolTip(_("Start neuronavigation")) + btn_nav.SetToolTip(tooltip) + + # Enable all navigation buttons + choice_ref.Enable(True) + choice_trck.Enable(True) + for btn_c in self.btns_coord: + btn_c.Enable(True) + + if self.trigger_state: + self.trigger.stop() - - def __update_points_plh(self, pubsub_evt): - coord = pubsub_evt.data - if self.aux_plh_ref1 == 0: - self.numCtrl1d.SetValue(coord[0]) - self.numCtrl2d.SetValue(coord[1]) - self.numCtrl3d.SetValue(coord[2]) - self.aux_plh_ref1 = 1 - if self.aux_plh_ref2 == 0: - self.numCtrl1e.SetValue(coord[0]) - self.numCtrl2e.SetValue(coord[1]) - self.numCtrl3e.SetValue(coord[2]) - self.aux_plh_ref2 = 1 - if self.aux_plh_ref3 == 0: - self.numCtrl1f.SetValue(coord[0]) - self.numCtrl2f.SetValue(coord[1]) - self.numCtrl3f.SetValue(coord[2]) - self.aux_plh_ref3 = 1 - - def Buttons(self, evt): - id = evt.GetId() - x, y, z = self.a - if id == PR1: - self.aux_plh_ref1 = 0 - self.coord1b = self.Coordinates() - coord = self.coord1b - elif id == PR2: - self.aux_plh_ref2 = 0 - self.coord2b = self.Coordinates() - coord = self.coord2b - elif id == PR3: - self.aux_plh_ref3 = 0 - self.coord3b = self.Coordinates() - coord = self.coord3b - elif id == GetPoint: - x, y, z = self.a - self.numCtrl1g.SetValue(x) - self.numCtrl2g.SetValue(y) - self.numCtrl3g.SetValue(z) - info = self.a, self.flagpoint - self.SaveCoordinates(info) - self.flagpoint = 1 - elif id == Corregistration and self.aux_img_ref1 == 1 and self.aux_img_ref2 == 1 and self.aux_img_ref3 == 1: - print "Coordenadas Imagem: ", self.coord1a, self.coord2a, self.coord3a - print "Coordenadas Polhemus: ", self.coord1b, self.coord2b, self.coord3b - - self.M, self.q1, self.Minv = db.Bases(self.coord1a, self.coord2a, self.coord3a).Basecreation() - self.N, self.q2, self.Ninv = db.Bases(self.coord1b, self.coord2b, self.coord3b).Basecreation() - - if self.aux_plh_ref1 == 0 or self.aux_plh_ref2 == 0 or self.aux_plh_ref3 == 0: - Publisher.sendMessage('Update plh position', coord) - - def Coordinates(self): - #Get Polhemus points for base creation - ser = serial.Serial(0) - ser.write("Y") - ser.write("P") - str = ser.readline() - ser.write("Y") - str = str.replace("\r\n","") - str = str.replace("-"," -") - aostr = [s for s in str.split()] - #aoflt -> 0:letter 1:x 2:y 3:z - aoflt = [float(aostr[1]), float(aostr[2]), float(aostr[3]), - float(aostr[4]), float(aostr[5]), float(aostr[6])] - ser.close() - #Unit change: inches to millimeters - x = 25.4 - y = 25.4 - z = -25.4 - - coord = (aoflt[0]*x, aoflt[1]*y, aoflt[2]*z) - return coord - - def Img_Ref_ToggleButton1(self, evt): - id = evt.GetId() - flag1 = self.button_img_ref1.GetValue() - x, y, z = self.a - if flag1 == True: - self.coord1a = x, y, z - self.aux_img_ref1 = 1 - elif flag1 == False: - self.aux_img_ref1 = 0 - self.coord1a = (0, 0, 0) - self.numCtrl1a.SetValue(x) - self.numCtrl2a.SetValue(y) - self.numCtrl3a.SetValue(z) - - def Img_Ref_ToggleButton2(self, evt): - id = evt.GetId() - flag2 = self.button_img_ref2.GetValue() - x, y, z = self.a - if flag2 == True: - self.coord2a = x, y, z - self.aux_img_ref2 = 1 - elif flag2 == False: - self.aux_img_ref2 = 0 - self.coord2a = (0, 0, 0) - self.numCtrl1b.SetValue(x) - self.numCtrl2b.SetValue(y) - self.numCtrl3b.SetValue(z) - - def Img_Ref_ToggleButton3(self, evt): - id = evt.GetId() - flag3 = self.button_img_ref3.GetValue() - x, y, z = self.a - if flag3 == True: - self.coord3a = x, y, z - self.aux_img_ref3 = 1 - elif flag3 == False: - self.aux_img_ref3 = 0 - self.coord3a = (0, 0, 0) - self.numCtrl1c.SetValue(x) - self.numCtrl2c.SetValue(y) - self.numCtrl3c.SetValue(z) - - def Neuronavigate_ToggleButton(self, evt): - id = evt.GetId() - flag4 = self.button_neuronavigate.GetValue() - bases = self.Minv, self.N, self.q1, self.q2 - if flag4 == True: - self.correg = dcr.Corregister(bases, flag4) - elif flag4 == False: self.correg.stop() + + def ResetTrackerFiducials(self): + for m in range(3, 6): + for n in range(0, 3): + self.numctrls_coord[m][n].SetValue(0.0) + + +class MarkersPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent) + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) + self.SetBackgroundColour(default_colour) + + self.SetAutoLayout(1) + + self.__bind_events() + + self.current_coord = 0, 0, 0 + self.list_coord = [] + self.marker_ind = 0 + + self.marker_colour = (0.0, 0.0, 1.) + self.marker_size = 4 + + # Change marker size + spin_size = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23)) + spin_size.SetRange(1, 99) + spin_size.SetValue(self.marker_size) + spin_size.Bind(wx.EVT_TEXT, partial(self.OnSelectSize, ctrl=spin_size)) + spin_size.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectSize, ctrl=spin_size)) + + # Marker colour select + select_colour = csel.ColourSelect(self, -1, colour=[255*s for s in self.marker_colour], size=wx.Size(20, 23)) + select_colour.Bind(csel.EVT_COLOURSELECT, partial(self.OnSelectColour, ctrl=select_colour)) + + btn_create = wx.Button(self, -1, label=_('Create marker'), size=wx.Size(135, 23)) + btn_create.Bind(wx.EVT_BUTTON, self.OnCreateMarker) + + sizer_create = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5) + sizer_create.AddMany([(spin_size, 1), + (select_colour, 0), + (btn_create, 0)]) + + # Buttons to save and load markers and to change its visibility as well + btn_save = wx.Button(self, -1, label=_('Save'), size=wx.Size(65, 23)) + btn_save.Bind(wx.EVT_BUTTON, self.OnSaveMarkers) + + btn_load = wx.Button(self, -1, label=_('Load'), size=wx.Size(65, 23)) + btn_load.Bind(wx.EVT_BUTTON, self.OnLoadMarkers) + + btn_visibility = wx.ToggleButton(self, -1, _("Hide"), size=wx.Size(65, 23)) + btn_visibility.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnMarkersVisibility, ctrl=btn_visibility)) + + sizer_btns = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5) + sizer_btns.AddMany([(btn_save, 1, wx.RIGHT), + (btn_load, 0, wx.LEFT | wx.RIGHT), + (btn_visibility, 0, wx.LEFT)]) + + # Buttons to delete or remove markers + btn_delete_single = wx.Button(self, -1, label=_('Remove'), size=wx.Size(65, 23)) + btn_delete_single.Bind(wx.EVT_BUTTON, self.OnDeleteSingleMarker) + + btn_delete_all = wx.Button(self, -1, label=_('Delete all markers'), size=wx.Size(135, 23)) + btn_delete_all.Bind(wx.EVT_BUTTON, self.OnDeleteAllMarkers) + + sizer_delete = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5) + sizer_delete.AddMany([(btn_delete_single, 1, wx.RIGHT), + (btn_delete_all, 0, wx.LEFT)]) + + # List of markers + self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT, size=wx.Size(0,120)) + self.lc.InsertColumn(0, '#') + self.lc.InsertColumn(1, 'X') + self.lc.InsertColumn(2, 'Y') + self.lc.InsertColumn(3, 'Z') + self.lc.InsertColumn(4, 'ID') + self.lc.SetColumnWidth(0, 28) + self.lc.SetColumnWidth(1, 50) + self.lc.SetColumnWidth(2, 50) + self.lc.SetColumnWidth(3, 50) + self.lc.SetColumnWidth(4, 50) + self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnListEditMarkerId) + + # Add all lines into main sizer + group_sizer = wx.BoxSizer(wx.VERTICAL) + group_sizer.Add(sizer_create, 0, wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 5) + group_sizer.Add(sizer_btns, 0, wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 5) + group_sizer.Add(sizer_delete, 0, wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 5) + group_sizer.Add(self.lc, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 5) + group_sizer.Fit(self) + + self.SetSizer(group_sizer) + self.Update() + + def __bind_events(self): + Publisher.subscribe(self.UpdateCurrentCoord, 'Set ball reference position') + Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker') + Publisher.subscribe(self.OnCreateMarker, 'Create marker') + + def UpdateCurrentCoord(self, pubsub_evt): + self.current_coord = pubsub_evt.data + + def OnListEditMarkerId(self, evt): + menu_id = wx.Menu() + menu_id.Append(-1, _('Edit ID')) + menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerId) + self.PopupMenu(menu_id) + menu_id.Destroy() + + def OnMenuEditMarkerId(self, evt): + id_label = dlg.EnterMarkerID(self.lc.GetItemText(self.lc.GetFocusedItem(), 4)) + list_index = self.lc.GetFocusedItem() + self.lc.SetStringItem(list_index, 4, id_label) + # Add the new ID to exported list + self.list_coord[list_index][7] = str(id_label) + + def OnDeleteAllMarkers(self, pubsub_evt): + self.list_coord = [] + self.marker_ind = 0 + Publisher.sendMessage('Remove all markers', self.lc.GetItemCount()) + self.lc.DeleteAllItems() + + def OnDeleteSingleMarker(self, evt): + # OnDeleteSingleMarker is used for both pubsub and button click events + # Pubsub is used for fiducial handle and button click for all others + + if hasattr(evt, 'data'): + marker_id = evt.data + if self.lc.GetItemCount(): + for id_n in range(self.lc.GetItemCount()): + item = self.lc.GetItem(id_n, 4) + if item.GetText() == marker_id: + if marker_id == "LEI" or marker_id == "REI" or marker_id == "NAI": + self.lc.Focus(item.GetId()) + break + else: + if self.lc.GetFocusedItem() is not -1 and self.lc.GetItemCount(): + index = self.lc.GetFocusedItem() + del self.list_coord[index] + self.lc.DeleteItem(index) + for n in range(0, self.lc.GetItemCount()): + self.lc.SetStringItem(n, 0, str(n+1)) + self.marker_ind -= 1 + Publisher.sendMessage('Remove marker', index) + elif not self.lc.GetItemCount(): + pass + else: + dlg.NoMarkerSelected() + + def OnCreateMarker(self, evt): + # OnCreateMarker is used for both pubsub and button click events + # Pubsub is used for markers created with fiducial buttons, trigger and create marker button + if hasattr(evt, 'data'): + if evt.data: + self.CreateMarker(evt.data[0], (0.0, 1.0, 0.0), self.marker_size, evt.data[1]) + else: + self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size) + else: + self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size) + + def OnLoadMarkers(self, evt): + filepath = dlg.ShowLoadMarkersDialog() + + if filepath: + try: + content = [s.rstrip() for s in open(filepath)] + for data in content: + line = [s for s in data.split()] + coord = float(line[0]), float(line[1]), float(line[2]) + colour = float(line[3]), float(line[4]), float(line[5]) + size = float(line[6]) + + if len(line) == 8: + if line[7] == "LEI" or line[7] == "REI" or line[7] == "NAI": + Publisher.sendMessage('Load image fiducials', (line[7], coord)) + else: + line.append("") + self.CreateMarker(coord, colour, size, line[7]) + except: + dlg.InvalidMarkersFile() + + def OnMarkersVisibility(self, evt, ctrl): + + if ctrl.GetValue(): + Publisher.sendMessage('Hide all markers', self.lc.GetItemCount()) + ctrl.SetLabel('Show') + else: + Publisher.sendMessage('Show all markers', self.lc.GetItemCount()) + ctrl.SetLabel('Hide') + + def OnSaveMarkers(self, evt): + filename = dlg.ShowSaveMarkersDialog("markers.txt") + if filename: + if self.list_coord: + text_file = open(filename, "w") + list_slice1 = self.list_coord[0] + coord = str('%.3f' %self.list_coord[0][0]) + "\t" + str('%.3f' %self.list_coord[0][1]) + "\t" + str('%.3f' %self.list_coord[0][2]) + properties = str('%.3f' %list_slice1[3]) + "\t" + str('%.3f' %list_slice1[4]) + "\t" + str('%.3f' %list_slice1[5]) + "\t" + str('%.1f' %list_slice1[6]) + "\t" + list_slice1[7] + line = coord + "\t" + properties + "\n" + list_slice = self.list_coord[1:] + + for value in list_slice: + coord = str('%.3f' %value[0]) + "\t" + str('%.3f' %value[1]) + "\t" + str('%.3f' %value[2]) + properties = str('%.3f' %value[3]) + "\t" + str('%.3f' %value[4]) + "\t" + str('%.3f' %value[5]) + "\t" + str('%.1f' %value[6]) + "\t" + value[7] + line = line + coord + "\t" + properties + "\n" + + text_file.writelines(line) + text_file.close() - def SaveCoordinates(self, info): - #Create a file and write the points given by getpoint's button - x, y, z = info[0] - flag = info[1] - - if flag == 0: - text_file = open("points.txt", "w") - line = str('%.2f' %x) + "\t" + str('%.2f' %y) + "\t" + str('%.2f' %z) + "\n" - text_file.writelines(line) - text_file.close() + def OnSelectColour(self, evt, ctrl): + self.marker_colour = [colour/255.0 for colour in ctrl.GetValue()] + + def OnSelectSize(self, evt, ctrl): + self.marker_size = ctrl.GetValue() + + def CreateMarker(self, coord, colour, size, marker_id=""): + # TODO: Use matrix coordinates and not world coordinates as current method. + # This makes easier for inter-software comprehension. + + Publisher.sendMessage('Add marker', (self.marker_ind, size, colour, coord)) + + self.marker_ind += 1 + + # List of lists with coordinates and properties of a marker + line = [coord[0], coord[1], coord[2], colour[0], colour[1], colour[2], self.marker_size, marker_id] + + # Adding current line to a list of all markers already created + if not self.list_coord: + self.list_coord = [line] else: - text_file = open("points.txt", "r") - filedata = text_file.read() - line = filedata + str('%.2f' %x) + "\t" + str('%.2f' %y) + "\t" + str('%.2f' %z) + "\n" - text_file = open("points.txt", "w") - text_file.write(line) - text_file.close() - + self.list_coord.append(line) + + # Add item to list control in panel + num_items = self.lc.GetItemCount() + self.lc.InsertStringItem(num_items, str(num_items + 1)) + self.lc.SetStringItem(num_items, 1, str(round(coord[0], 2))) + self.lc.SetStringItem(num_items, 2, str(round(coord[1], 2))) + self.lc.SetStringItem(num_items, 3, str(round(coord[2], 2))) + self.lc.SetStringItem(num_items, 4, str(marker_id)) + self.lc.EnsureVisible(num_items) diff --git a/invesalius/reader/analyze_reader.py b/invesalius/reader/analyze_reader.py deleted file mode 100644 index 8546df7..0000000 --- a/invesalius/reader/analyze_reader.py +++ /dev/null @@ -1,43 +0,0 @@ -#-------------------------------------------------------------------------- -# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas -# Copyright: (C) 2001 Centro de Pesquisas Renato Archer -# Homepage: http://www.softwarepublico.gov.br -# Contact: invesalius@cti.gov.br -# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) -#-------------------------------------------------------------------------- -# Este programa e software livre; voce pode redistribui-lo e/ou -# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme -# publicada pela Free Software Foundation; de acordo com a versao 2 -# da Licenca. -# -# Este programa eh distribuido na expectativa de ser util, mas SEM -# QUALQUER GARANTIA; sem mesmo a garantia implicita de -# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM -# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais -# detalhes. -#-------------------------------------------------------------------------- - -import os -import multiprocessing -import tempfile - -import vtk - -from nibabel import AnalyzeImage, squeeze_image - -def ReadAnalyze(filename): - anlz = squeeze_image(AnalyzeImage.from_filename(filename)) - return anlz - -def ReadDirectory(dir_): - """ - Looking for analyze files in the given directory - """ - imagedata = None - for root, sub_folders, files in os.walk(dir_): - for file in files: - if file.split(".")[-1] == "hdr": - filename = os.path.join(root,file) - imagedata = ReadAnalyze(filename) - return imagedata - return imagedata diff --git a/invesalius/reader/others_reader.py b/invesalius/reader/others_reader.py new file mode 100644 index 0000000..9059e67 --- /dev/null +++ b/invesalius/reader/others_reader.py @@ -0,0 +1,51 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- + +import os + +import vtk +import nibabel as nib + +import invesalius.constants as const + + +def ReadOthers(dir_): + """ + Read the given Analyze, NIfTI, Compressed NIfTI or PAR/REC file, + remove singleton image dimensions and convert image orientation to + RAS+ canonical coordinate system. Analyze header does not support + affine transformation matrix, though cannot be converted automatically + to canonical orientation. + + :param dir_: file path + :return: imagedata object + """ + + if not const.VTK_WARNING: + log_path = os.path.join(const.LOG_FOLDER, 'vtkoutput.txt') + fow = vtk.vtkFileOutputWindow() + fow.SetFileName(log_path) + ow = vtk.vtkOutputWindow() + ow.SetInstance(fow) + + imagedata = nib.squeeze_image(nib.load(dir_)) + imagedata = nib.as_closest_canonical(imagedata) + imagedata.update_header() + + return imagedata \ No newline at end of file diff --git a/navigation/mtc_files/CalibrationFiles/BumbleBee_8090380.calib b/navigation/mtc_files/CalibrationFiles/BumbleBee_8090380.calib new file mode 100644 index 0000000..c11abba Binary files /dev/null and b/navigation/mtc_files/CalibrationFiles/BumbleBee_8090380.calib differ diff --git a/navigation/mtc_files/CalibrationFiles/_README.txt b/navigation/mtc_files/CalibrationFiles/_README.txt new file mode 100644 index 0000000..17efcf3 --- /dev/null +++ b/navigation/mtc_files/CalibrationFiles/_README.txt @@ -0,0 +1 @@ +This folder contains all the calibration files for the installed MicronTrackers \ No newline at end of file diff --git a/navigation/mtc_files/Markers/1Probe b/navigation/mtc_files/Markers/1Probe new file mode 100644 index 0000000..a505490 --- /dev/null +++ b/navigation/mtc_files/Markers/1Probe @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=1Probe + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-61.602408326043872 +EndPos(0,1)=4.2184025144074082e-016 +EndPos(0,2)=-4.556586682229099e-016 +EndPos(1,0)=61.602408326043872 +EndPos(1,1)=-9.6115500328270063e-017 +EndPos(1,2)=-1.1391466705572748e-016 + +[Facet1V2] +EndPos(0,0)=-61.59657019736504 +EndPos(0,1)=11.949604571287503 +EndPos(0,2)=1.1391466705572748e-016 +EndPos(1,0)=-61.602408326043872 +EndPos(1,1)=4.2184025144074082e-016 +EndPos(1,2)=-4.556586682229099e-016 + +[Tooltip2MarkerXf] +Scale=1. +S0=145.66478105979206 +R0,0=-8.4560706371216821e-002 +R1,0=-0.52109081782374 +R2,0=0.84930197604725266 +S1=-13.147426563284974 +R0,1=4.5008904130328986e-002 +R1,1=-0.85348236485506712 +R2,1=-0.5191743940434248 +S2=9.0630880541399712 +R0,2=0.99540126857813949 +R1,2=-5.6756022725552024e-003 +R2,2=9.5624798310161907e-002 diff --git a/navigation/mtc_files/Markers/1b b/navigation/mtc_files/Markers/1b new file mode 100644 index 0000000..5470171 --- /dev/null +++ b/navigation/mtc_files/Markers/1b @@ -0,0 +1,38 @@ + +[Marker] +FacetsCount=1 +Name=1b + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-13.977391161539257 +EndPos(0,1)=1.2440742880315269e-015 +EndPos(0,2)=-9.6300745155986073e-015 +EndPos(1,0)=13.977391161539257 +EndPos(1,1)=1.2515682934477468e-015 +EndPos(1,2)=-9.6789243286821155e-015 + +[Facet1V2] +EndPos(0,0)=13.977391161539257 +EndPos(0,1)=1.2515682934477468e-015 +EndPos(0,2)=-9.6789243286821155e-015 +EndPos(1,0)=14.019670530273789 +EndPos(1,1)=15.245637552880362 +EndPos(1,2)=-9.7055696812731187e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/2Coil b/navigation/mtc_files/Markers/2Coil new file mode 100644 index 0000000..cabcc35 --- /dev/null +++ b/navigation/mtc_files/Markers/2Coil @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=2Coil + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-8.9266116684264851 +EndPos(0,1)=-7.9898502660127542e-016 +EndPos(0,2)=1.0214990704373533e-015 +EndPos(1,0)=8.9266116684264833 +EndPos(1,1)=-6.360897244681129e-016 +EndPos(1,2)=2.6438799470143262e-015 + +[Facet1V2] +EndPos(0,0)=8.9266116684264833 +EndPos(0,1)=-6.360897244681129e-016 +EndPos(0,2)=2.6438799470143262e-015 +EndPos(1,0)=9.0134937152069554 +EndPos(1,1)=-12.059011297429047 +EndPos(1,2)=3.6653790174516793e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0.20000000000000079 +R0,0=0.96592582628906842 +R1,0=-1.457167719820518e-016 +R2,0=-0.25881904510252007 +S1=-14.79999999999999 +R0,1=7.9979483404574392e-002 +R1,1=0.95105651629515409 +R2,1=0.29848749562898386 +S2=99.400000000000091 +R0,2=0.24615153938604106 +R1,2=-0.30901699437494573 +R2,2=0.91865005134999966 diff --git a/navigation/mtc_files/Markers/2b b/navigation/mtc_files/Markers/2b new file mode 100644 index 0000000..fea1785 --- /dev/null +++ b/navigation/mtc_files/Markers/2b @@ -0,0 +1,38 @@ + +[Marker] +FacetsCount=1 +Name=2b + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-13.9690559296461 +EndPos(0,1)=-9.08090028293946e-017 +EndPos(0,2)=3.7518297647387081e-015 +EndPos(1,0)=13.969055929646105 +EndPos(1,1)=-1.1456717217429434e-016 +EndPos(1,2)=3.7783785761971358e-015 + +[Facet1V2] +EndPos(0,0)=-13.924452046559736 +EndPos(0,1)=-15.196209648340083 +EndPos(0,2)=3.7349350665378906e-015 +EndPos(1,0)=-13.9690559296461 +EndPos(1,1)=-9.08090028293946e-017 +EndPos(1,2)=3.7518297647387081e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/3Coil b/navigation/mtc_files/Markers/3Coil new file mode 100644 index 0000000..2701cb1 --- /dev/null +++ b/navigation/mtc_files/Markers/3Coil @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=3Coil + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-8.9434442564413246 +EndPos(0,1)=-6.1153268241647966e-016 +EndPos(0,2)=3.4944724709513123e-016 +EndPos(1,0)=8.9434442564413299 +EndPos(1,1)=-8.8089826871897666e-016 +EndPos(1,2)=-4.6592966279350834e-016 + +[Facet1V2] +EndPos(0,0)=-8.9439343674574907 +EndPos(0,1)=11.937904678416533 +EndPos(0,2)=-5.2417087064269685e-016 +EndPos(1,0)=-8.9434442564413246 +EndPos(1,1)=-6.1153268241647966e-016 +EndPos(1,2)=3.4944724709513123e-016 + +[Tooltip2MarkerXf] +Scale=1. +S0=0.80000000000000182 +R0,0=0.9563047559630351 +R1,0=1.8041124150158794e-016 +R2,0=-0.29237170472273794 +S1=47.299999999999962 +R0,1=9.0347825433700193e-002 +R1,1=0.95105651629515331 +R2,1=0.29551442139416562 +S2=42.399999999999991 +R0,2=0.27806201495688238 +R1,2=-0.30901699437494834 +R2,2=0.90949986972269081 diff --git a/navigation/mtc_files/Markers/4Coil b/navigation/mtc_files/Markers/4Coil new file mode 100644 index 0000000..e808654 --- /dev/null +++ b/navigation/mtc_files/Markers/4Coil @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=4Coil + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-8.9585812958876883 +EndPos(0,1)=-4.8569430893988515e-016 +EndPos(0,2)=2.3154142102162125e-015 +EndPos(1,0)=8.95858129588769 +EndPos(1,1)=-1.2210192124187057e-016 +EndPos(1,2)=4.6308284204324244e-016 + +[Facet1V2] +EndPos(0,0)=8.95858129588769 +EndPos(0,1)=-1.2210192124187057e-016 +EndPos(0,2)=4.6308284204324244e-016 +EndPos(1,0)=8.8914854228233384 +EndPos(1,1)=11.981845264775341 +EndPos(1,2)=3.4731213153243185e-016 + +[Tooltip2MarkerXf] +Scale=1. +S0=-0.40000000000000141 +R0,0=1. +R1,0=0. +R2,0=0. +S1=10.700000000000001 +R0,1=0. +R1,1=1. +R2,1=0. +S2=110.09999999999999 +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/5Ref b/navigation/mtc_files/Markers/5Ref new file mode 100644 index 0000000..8c548cb --- /dev/null +++ b/navigation/mtc_files/Markers/5Ref @@ -0,0 +1,38 @@ +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=5Ref + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-8.9120676333490003 +EndPos(0,1)=2.3980817331903383e-016 +EndPos(0,2)=-1.2505552149377763e-015 +EndPos(1,0)=8.9120676333490003 +EndPos(1,1)=2.6645352591003756e-017 +EndPos(1,2)=0. + +[Facet1V2] +EndPos(0,0)=-8.9951734202602971 +EndPos(0,1)=-12.045839883319603 +EndPos(0,2)=-4.5474735088646413e-016 +EndPos(1,0)=-8.9120676333490003 +EndPos(1,1)=2.3980817331903383e-016 +EndPos(1,2)=-1.2505552149377763e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/COOLCARD b/navigation/mtc_files/Markers/COOLCARD new file mode 100644 index 0000000..7045f9d --- /dev/null +++ b/navigation/mtc_files/Markers/COOLCARD @@ -0,0 +1,37 @@ +[Marker] +FacetsCount=1 +Name=COOLCARD + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-31.763129600371474 +EndPos(0,1)=-6.4066430885648868e-016 +EndPos(0,2)=1.9712913472123418e-015 +EndPos(1,0)=31.763129600371467 +EndPos(1,1)=-6.4440270518456277e-016 +EndPos(1,2)=1.9656972001890368e-015 + +[Facet1V2] +EndPos(0,0)=-31.746690593113094 +EndPos(0,1)=25.423398067377672 +EndPos(0,2)=1.9712913472123418e-015 +EndPos(1,0)=-31.763129600371474 +EndPos(1,1)=-6.4066430885648868e-016 +EndPos(1,2)=1.9712913472123418e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/Pointer Template b/navigation/mtc_files/Markers/Pointer Template new file mode 100644 index 0000000..ab716bf --- /dev/null +++ b/navigation/mtc_files/Markers/Pointer Template @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=Pointer Template + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-8.9079792482800304 +EndPos(0,1)=4.7658354227811601e-016 +EndPos(0,2)=-3.8126683382249281e-015 +EndPos(1,0)=8.9079792482800322 +EndPos(1,1)=-3.754900636130611e-016 +EndPos(1,2)=4.6214161675453676e-016 + +[Facet1V2] +EndPos(0,0)=8.9079792482800322 +EndPos(0,1)=-3.754900636130611e-016 +EndPos(0,2)=4.6214161675453676e-016 +EndPos(1,0)=8.97037014055695 +EndPos(1,1)=-11.854493390526288 +EndPos(1,2)=-5.1990931884885378e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/Ref b/navigation/mtc_files/Markers/Ref new file mode 100644 index 0000000..9635d4c --- /dev/null +++ b/navigation/mtc_files/Markers/Ref @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=Ref + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-8.9120676333490003 +EndPos(0,1)=2.3980817331903383e-016 +EndPos(0,2)=-1.2505552149377763e-015 +EndPos(1,0)=8.9120676333490003 +EndPos(1,1)=2.6645352591003756e-017 +EndPos(1,2)=0. + +[Facet1V2] +EndPos(0,0)=-8.9951734202602971 +EndPos(0,1)=-12.045839883319603 +EndPos(0,2)=-4.5474735088646413e-016 +EndPos(1,0)=-8.9120676333490003 +EndPos(1,1)=2.3980817331903383e-016 +EndPos(1,2)=-1.2505552149377763e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/TTblock b/navigation/mtc_files/Markers/TTblock new file mode 100644 index 0000000..7833730 --- /dev/null +++ b/navigation/mtc_files/Markers/TTblock @@ -0,0 +1,37 @@ +[Marker] +FacetsCount=1 +Name=TTblock + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-44.89806848621749 +EndPos(0,1)=1.5829351718288365e-017 +EndPos(0,2)=-2.4650420593630429e-015 +EndPos(1,0)=44.898068486217483 +EndPos(1,1)=8.5012292344588403e-016 +EndPos(1,2)=-2.4138677168217271e-015 + +[Facet1V2] +EndPos(0,0)=-0.25683199956524938 +EndPos(0,1)=42.807497943328485 +EndPos(0,2)=-8.1564930877711453e-003 +EndPos(1,0)=0.23846937714486499 +EndPos(1,1)=-42.932871913619174 +EndPos(1,2)=-8.1564930877712043e-003 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/a b/navigation/mtc_files/Markers/a new file mode 100644 index 0000000..45c9a89 --- /dev/null +++ b/navigation/mtc_files/Markers/a @@ -0,0 +1,37 @@ +[Marker] +FacetsCount=1 +Name=a + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-32.971498168704073 +EndPos(0,1)=2.5755541490385065e-016 +EndPos(0,2)=1.6979881553090628e-016 +EndPos(1,0)=32.971498168704088 +EndPos(1,1)=3.2816886463184776e-016 +EndPos(1,2)=4.4082384801292978e-017 + +[Facet1V2] +EndPos(0,0)=-32.975185360462866 +EndPos(0,1)=46.723702252369826 +EndPos(0,2)=-8.9797450521152372e-018 +EndPos(1,0)=-32.971498168704073 +EndPos(1,1)=2.5755541490385065e-016 +EndPos(1,2)=1.6979881553090628e-016 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/coil b/navigation/mtc_files/Markers/coil new file mode 100644 index 0000000..577e445 --- /dev/null +++ b/navigation/mtc_files/Markers/coil @@ -0,0 +1,38 @@ + +[Marker] +FacetsCount=1 +Name=coil + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-70.299927206216339 +EndPos(0,1)=1.1368683772161603e-016 +EndPos(0,2)=1.3642420526593924e-015 +EndPos(1,0)=70.299927206216339 +EndPos(1,1)=-1.1368683772161603e-016 +EndPos(1,2)=6.8212102632969619e-016 + +[Facet1V2] +EndPos(0,0)=-69.651993385067129 +EndPos(0,1)=-65.744122964094188 +EndPos(0,2)=3.410605131648481e-016 +EndPos(1,0)=-70.299927206216339 +EndPos(1,1)=1.1368683772161603e-016 +EndPos(1,2)=1.3642420526593924e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. diff --git a/navigation/mtc_files/Markers/probe b/navigation/mtc_files/Markers/probe new file mode 100644 index 0000000..7562b53 --- /dev/null +++ b/navigation/mtc_files/Markers/probe @@ -0,0 +1,39 @@ + +[Marker] +FacetsCount=1 +SliderControlledXpointsCount=0 +Name=probe + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-61.602408326043872 +EndPos(0,1)=4.2184025144074082e-016 +EndPos(0,2)=-4.556586682229099e-016 +EndPos(1,0)=61.602408326043872 +EndPos(1,1)=-9.6115500328270063e-017 +EndPos(1,2)=-1.1391466705572748e-016 + +[Facet1V2] +EndPos(0,0)=-61.59657019736504 +EndPos(0,1)=11.949604571287503 +EndPos(0,2)=1.1391466705572748e-016 +EndPos(1,0)=-61.602408326043872 +EndPos(1,1)=4.2184025144074082e-016 +EndPos(1,2)=-4.556586682229099e-016 + +[Tooltip2MarkerXf] +Scale=1. +S0=145.66478105979206 +R0,0=-8.4560706371216821e-002 +R1,0=-0.52109081782374 +R2,0=0.84930197604725266 +S1=-13.147426563284974 +R0,1=4.5008904130328986e-002 +R1,1=-0.85348236485506712 +R2,1=-0.5191743940434248 +S2=9.0630880541399712 +R0,2=0.99540126857813949 +R1,2=-5.6756022725552024e-003 +R2,2=9.5624798310161907e-002 diff --git a/navigation/mtc_files/Markers/sptr b/navigation/mtc_files/Markers/sptr new file mode 100644 index 0000000..065e71c --- /dev/null +++ b/navigation/mtc_files/Markers/sptr @@ -0,0 +1,38 @@ + +[Marker] +FacetsCount=1 +Name=sptr + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-52.19791804784397 +EndPos(0,1)=1.2304185897405721e-016 +EndPos(0,2)=6.9721287355505385e-016 +EndPos(1,0)=52.19791804784397 +EndPos(1,1)=-7.4104692177650016e-017 +EndPos(1,2)=5.9481366254593747e-016 + +[Facet1V2] +EndPos(0,0)=-52.224959539439432 +EndPos(0,1)=11.532261461857525 +EndPos(0,2)=6.9918899867979109e-016 +EndPos(1,0)=-52.19791804784397 +EndPos(1,1)=1.2304185897405721e-016 +EndPos(1,2)=6.9721287355505385e-016 + +[Tooltip2MarkerXf] +Scale=1. +S0=132.5915439593966 +R0,0=-3.3573683844187456e-002 +R1,0=-0.99175827123861893 +R2,0=0.12364602372465228 +S1=-3.8041069565099583 +R0,1=-3.9209531312008813e-002 +R1,1=-0.12231349340138305 +R2,1=-0.9917167045009595 +S2=-0.29142356239092942 +R0,2=0.99866682152128394 +R1,2=-3.8143685738751404e-002 +R2,2=-3.4779862432738673e-002 diff --git a/navigation/mtc_files/Markers/try4 b/navigation/mtc_files/Markers/try4 new file mode 100644 index 0000000..dd8df61 --- /dev/null +++ b/navigation/mtc_files/Markers/try4 @@ -0,0 +1,38 @@ + +[Marker] +FacetsCount=1 +Name=try4 + +[Facet1] +VectorsCount=2 + +[Facet1V1] +EndPos(0,0)=-57.030279071976651 +EndPos(0,1)=-1.7053025658242405e-016 +EndPos(0,2)=-3.1832314562052489e-015 +EndPos(1,0)=57.030279071976658 +EndPos(1,1)=1.7053025658242405e-016 +EndPos(1,2)=-1.8189894035458565e-015 + +[Facet1V2] +EndPos(0,0)=-56.190229598357341 +EndPos(0,1)=93.77396683857414 +EndPos(0,2)=0. +EndPos(1,0)=-57.030279071976651 +EndPos(1,1)=-1.7053025658242405e-016 +EndPos(1,2)=-3.1832314562052489e-015 + +[Tooltip2MarkerXf] +Scale=1. +S0=0. +R0,0=1. +R1,0=0. +R2,0=0. +S1=0. +R0,1=0. +R1,1=1. +R2,1=0. +S2=0. +R0,2=0. +R1,2=0. +R2,2=1. -- libgit2 0.21.2