Commit bed72ec34ae1e89713f64d9abd4f96f8b85d644b

Authored by Victor Hugo Souza
Committed by Thiago Franco de Moraes
1 parent 6a586fc7

Neuronavigation module update and improvement (#65)

Neuronavigation module updatings and improvements

* Revert "Revert "ENH: Update neuronavigator modules""

This reverts commit 2ecc37998e18c762b82413314fcf75d1491ca1be.

* ENH: Update corregistration comments.

* ENH: Restructuring InVesalius Navigator spatial tracker communication.

* ENH: Restructuring the spatial tracker communication.

* ENH: Updating navigation tools.

* Code refactoring for navigator development

- Delete previous git mistakes while tracking files
- Code fix to run invesalius navigator and start code refactoring

* Update navigator GUI
- Improvement on manipulation of markers creation and edition
- Buttons and panels size adjustment

* Improvement on trackers connection
- MicronTracker supports dynamic reference

* VTK6 adjustments - SetInputData
-Using SetInputData instead SetInput

* Update Create Markers and fix sphere creation

- Improvement on manipulation of markers creation and edition
- Using SetInputConnection(GetOutputPort()) instead SetInputData(GetOutput())

* GUI update and creation of fiducial markers

* Fix Fiducial Markers

* Update - Load Fiducial points using "Load Markers"

* Fix Trackers (PLH) and Navigation

* Update ComboBox and trackers connection

* Full support for Analyze, NIfTI and PAR/REC images
- Support for Analyze, NIfTI, Compressed NIfTI and PAR/REC images
- Support for Analyze limited due to lack of orientation info
- Image orientation standardized to RAS+

* ComboBox Update
- Set "Select tracker" when the tracker is not connected

* Fixed usp-navegador version
- Changed GetValue to GetValue()
- Changed SetInputData to SetInput
- Changed SetInputConnection to SetInputData

* Manually merged rmatsuda master_merge branch to usp-navegador
- Improved navigation GUI
- Improved navigation control of spatial trackers
- Added management of makers creation
- Added dialogs for better neuronavigation control

* Added TMS trigger and enhanced tracker device control
- Serial communication to detect TMS trigger
- Added MicronTracker calibration and marker files
- Enhanced tracker manipulation

* Navigation cleaning and improvements in viewer volume
- Cleaned unecessary navigation functions
- Optimized volume camera and ball reference positions
- Enhanced markers manipulation
- Removed blank lines

* Improved task_navigator GUI
- Better code using GridBagSizer
- Removed useless code

* Improvement in coordinates handling for neuronavigation

* Significant refactor of navigation pipeline
- Improvement in navigation panel controls
- Refactoring of volume and slice updates
- Improvement in communication with tracking devices
- Refactoring of entire navigation pipeline

* Improved colour and size of navigation markers

* Added trigger and volume camera controls
- Reformulated markers creation
- Created control of volume camera
- Control for external trigger marker creation

* Minor code adjustments

* GUI improvements

* Minor code optimization
- Improved load markers
@@ -8,6 +8,7 @@ invesalius/*.swo @@ -8,6 +8,7 @@ invesalius/*.swo
8 invesalius/*.swp 8 invesalius/*.swp
9 invesalius/data/*.log 9 invesalius/data/*.log
10 invesalius/data/*.pyc 10 invesalius/data/*.pyc
  11 +invesalius/data/*.pyd
11 invesalius/gui/*.log 12 invesalius/gui/*.log
12 invesalius/gui/*.pyc 13 invesalius/gui/*.pyc
13 invesalius/gui/widgets/*.log 14 invesalius/gui/widgets/*.log
@@ -21,6 +22,7 @@ invesalius/reader/*.pyc @@ -21,6 +22,7 @@ invesalius/reader/*.pyc
21 tags 22 tags
22 *.c 23 *.c
23 24
  25 +.idea
24 build 26 build
25 *.patch 27 *.patch
26 *.tgz 28 *.tgz
@@ -29,4 +31,4 @@ build @@ -29,4 +31,4 @@ build
29 *.cpp 31 *.cpp
30 *.diff 32 *.diff
31 33
32 -*.directory 34 -*.directory
  35 +*.directory
33 \ No newline at end of file 36 \ No newline at end of file
invesalius/constants.py
@@ -487,8 +487,9 @@ VTK_WARNING = 0 @@ -487,8 +487,9 @@ VTK_WARNING = 0
487 487
488 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE, 488 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE,
489 ID_PROJECT_CLOSE, ID_PROJECT_INFO, ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET, 489 ID_PROJECT_CLOSE, ID_PROJECT_INFO, ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET,
490 -ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_ANALYZE_IMPORT, ID_PREFERENCES,  
491 -ID_DICOM_NETWORK, ID_TIFF_JPG_PNG, ID_VIEW_INTERPOLATED] = [wx.NewId() for number in range(15)] 490 +ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_PREFERENCES, ID_DICOM_NETWORK,
  491 +ID_TIFF_JPG_PNG, ID_VIEW_INTERPOLATED, ID_ANALYZE_IMPORT, ID_NIFTI_IMPORT,
  492 +ID_PARREC_IMPORT] = [wx.NewId() for number in range(17)]
492 ID_EXIT = wx.ID_EXIT 493 ID_EXIT = wx.ID_EXIT
493 ID_ABOUT = wx.ID_ABOUT 494 ID_ABOUT = wx.ID_ABOUT
494 495
@@ -637,3 +638,53 @@ BOOLEAN_UNION = 1 @@ -637,3 +638,53 @@ BOOLEAN_UNION = 1
637 BOOLEAN_DIFF = 2 638 BOOLEAN_DIFF = 2
638 BOOLEAN_AND = 3 639 BOOLEAN_AND = 3
639 BOOLEAN_XOR = 4 640 BOOLEAN_XOR = 4
  641 +
  642 +#------------ Navigation defaults -------------------
  643 +
  644 +SELECT = 0
  645 +MTC = 1
  646 +FASTRAK = 2
  647 +ISOTRAKII = 3
  648 +PATRIOT = 4
  649 +DEBUGTRACK = 5
  650 +DISCTRACK = 6
  651 +DEFAULT_TRACKER = SELECT
  652 +
  653 +TRACKER = [_("Select tracker:"), _("Claron MicronTracker"),
  654 + _("Polhemus FASTRAK"), _("Polhemus ISOTRAK II"),
  655 + _("Polhemus PATRIOT"), _("Debug tracker"),
  656 + _("Disconnect tracker")]
  657 +
  658 +STATIC_REF = 0
  659 +DYNAMIC_REF = 1
  660 +DEFAULT_REF_MODE = DYNAMIC_REF
  661 +REF_MODE = [_("Static ref."), _("Dynamic ref.")]
  662 +
  663 +IR1 = wx.NewId()
  664 +IR2 = wx.NewId()
  665 +IR3 = wx.NewId()
  666 +TR1 = wx.NewId()
  667 +TR2 = wx.NewId()
  668 +TR3 = wx.NewId()
  669 +SET = wx.NewId()
  670 +
  671 +BTNS_IMG = {IR1: {0: _('LEI')},
  672 + IR2: {1: _('REI')},
  673 + IR3: {2: _('NAI')}}
  674 +
  675 +TIPS_IMG = [wx.ToolTip(_("Select left ear in image")),
  676 + wx.ToolTip(_("Select right ear in image")),
  677 + wx.ToolTip(_("Select nasion in image"))]
  678 +
  679 +BTNS_TRK = {TR1: {3: _('LET')},
  680 + TR2: {4: _('RET')},
  681 + TR3: {5: _('NAT')},
  682 + SET: {6: _('SET')}}
  683 +
  684 +TIPS_TRK = [wx.ToolTip(_("Select left ear with spatial tracker")),
  685 + wx.ToolTip(_("Select right ear with spatial tracker")),
  686 + wx.ToolTip(_("Select nasion with spatial tracker")),
  687 + wx.ToolTip(_("Show set coordinates in image"))]
  688 +
  689 +CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles'))
  690 +MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers'))
invesalius/control.py
@@ -16,11 +16,9 @@ @@ -16,11 +16,9 @@
16 # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais 16 # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
17 # detalhes. 17 # detalhes.
18 #-------------------------------------------------------------------------- 18 #--------------------------------------------------------------------------
19 -import math  
20 import os 19 import os
21 import plistlib 20 import plistlib
22 import wx 21 import wx
23 -import numpy  
24 from wx.lib.pubsub import pub as Publisher 22 from wx.lib.pubsub import pub as Publisher
25 23
26 import invesalius.constants as const 24 import invesalius.constants as const
@@ -32,10 +30,10 @@ import invesalius.data.surface as srf @@ -32,10 +30,10 @@ import invesalius.data.surface as srf
32 import invesalius.data.volume as volume 30 import invesalius.data.volume as volume
33 import invesalius.gui.dialogs as dialog 31 import invesalius.gui.dialogs as dialog
34 import invesalius.project as prj 32 import invesalius.project as prj
35 -import invesalius.reader.analyze_reader as analyze  
36 import invesalius.reader.dicom_grouper as dg 33 import invesalius.reader.dicom_grouper as dg
37 import invesalius.reader.dicom_reader as dcm 34 import invesalius.reader.dicom_reader as dcm
38 import invesalius.reader.bitmap_reader as bmp 35 import invesalius.reader.bitmap_reader as bmp
  36 +import invesalius.reader.others_reader as oth
39 import invesalius.session as ses 37 import invesalius.session as ses
40 38
41 39
@@ -65,6 +63,8 @@ class Controller(): @@ -65,6 +63,8 @@ class Controller():
65 Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') 63 Publisher.subscribe(self.OnImportMedicalImages, 'Import directory')
66 Publisher.subscribe(self.OnShowDialogImportDirectory, 64 Publisher.subscribe(self.OnShowDialogImportDirectory,
67 'Show import directory dialog') 65 'Show import directory dialog')
  66 + Publisher.subscribe(self.OnShowDialogImportOtherFiles,
  67 + 'Show import other files dialog')
68 Publisher.subscribe(self.OnShowDialogOpenProject, 68 Publisher.subscribe(self.OnShowDialogOpenProject,
69 'Show open project dialog') 69 'Show open project dialog')
70 70
@@ -77,7 +77,9 @@ class Controller(): @@ -77,7 +77,9 @@ class Controller():
77 Publisher.subscribe(self.OnOpenDicomGroup, 77 Publisher.subscribe(self.OnOpenDicomGroup,
78 'Open DICOM group') 78 'Open DICOM group')
79 Publisher.subscribe(self.OnOpenBitmapFiles, 79 Publisher.subscribe(self.OnOpenBitmapFiles,
80 - 'Open bitmap files') 80 + 'Open bitmap files')
  81 + Publisher.subscribe(self.OnOpenOtherFiles,
  82 + 'Open other files')
81 Publisher.subscribe(self.Progress, "Update dicom load") 83 Publisher.subscribe(self.Progress, "Update dicom load")
82 Publisher.subscribe(self.Progress, "Update bitmap load") 84 Publisher.subscribe(self.Progress, "Update bitmap load")
83 Publisher.subscribe(self.OnLoadImportPanel, "End dicom load") 85 Publisher.subscribe(self.OnLoadImportPanel, "End dicom load")
@@ -88,7 +90,6 @@ class Controller(): @@ -88,7 +90,6 @@ class Controller():
88 Publisher.subscribe(self.OnShowDialogCloseProject, 'Close Project') 90 Publisher.subscribe(self.OnShowDialogCloseProject, 'Close Project')
89 Publisher.subscribe(self.OnOpenProject, 'Open project') 91 Publisher.subscribe(self.OnOpenProject, 'Open project')
90 Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project') 92 Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project')
91 - Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog')  
92 Publisher.subscribe(self.OnShowBitmapFile, 'Show bitmap dialog') 93 Publisher.subscribe(self.OnShowBitmapFile, 'Show bitmap dialog')
93 94
94 Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog') 95 Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog')
@@ -116,6 +117,10 @@ class Controller(): @@ -116,6 +117,10 @@ class Controller():
116 def OnShowDialogImportDirectory(self, pubsub_evt): 117 def OnShowDialogImportDirectory(self, pubsub_evt):
117 self.ShowDialogImportDirectory() 118 self.ShowDialogImportDirectory()
118 119
  120 + def OnShowDialogImportOtherFiles(self, pubsub_evt):
  121 + id_type = pubsub_evt.data
  122 + self.ShowDialogImportOtherFiles(id_type)
  123 +
119 def OnShowDialogOpenProject(self, pubsub_evt): 124 def OnShowDialogOpenProject(self, pubsub_evt):
120 self.ShowDialogOpenProject() 125 self.ShowDialogOpenProject()
121 126
@@ -126,15 +131,6 @@ class Controller(): @@ -126,15 +131,6 @@ class Controller():
126 def OnShowDialogCloseProject(self, pubsub_evt): 131 def OnShowDialogCloseProject(self, pubsub_evt):
127 self.ShowDialogCloseProject() 132 self.ShowDialogCloseProject()
128 133
129 - def OnShowAnalyzeFile(self, pubsub_evt):  
130 - dirpath = dialog.ShowOpenAnalyzeDialog()  
131 - imagedata = analyze.ReadAnalyze(dirpath)  
132 - if imagedata:  
133 - self.CreateAnalyzeProject(imagedata)  
134 -  
135 - self.LoadProject()  
136 - Publisher.sendMessage("Enable state project", True)  
137 -  
138 def OnShowBitmapFile(self, pubsub_evt): 134 def OnShowBitmapFile(self, pubsub_evt):
139 self.ShowDialogImportBitmapFile() 135 self.ShowDialogImportBitmapFile()
140 ########################### 136 ###########################
@@ -163,7 +159,6 @@ class Controller(): @@ -163,7 +159,6 @@ class Controller():
163 self.StartImportBitmapPanel(dirpath) 159 self.StartImportBitmapPanel(dirpath)
164 # Publisher.sendMessage("Load data to import panel", dirpath) 160 # Publisher.sendMessage("Load data to import panel", dirpath)
165 161
166 -  
167 def ShowDialogImportDirectory(self): 162 def ShowDialogImportDirectory(self):
168 # Offer to save current project if necessary 163 # Offer to save current project if necessary
169 session = ses.Session() 164 session = ses.Session()
@@ -186,6 +181,40 @@ class Controller(): @@ -186,6 +181,40 @@ class Controller():
186 self.StartImportPanel(dirpath) 181 self.StartImportPanel(dirpath)
187 Publisher.sendMessage("Load data to import panel", dirpath) 182 Publisher.sendMessage("Load data to import panel", dirpath)
188 183
  184 + def ShowDialogImportOtherFiles(self, id_type):
  185 + # Offer to save current project if necessary
  186 + session = ses.Session()
  187 + st = session.project_status
  188 + if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE):
  189 + filename = session.project_path[1]
  190 + answer = dialog.SaveChangesDialog2(filename)
  191 + if answer:
  192 + self.ShowDialogSaveProject()
  193 + self.CloseProject()
  194 + # Publisher.sendMessage("Enable state project", False)
  195 + Publisher.sendMessage('Set project name')
  196 + Publisher.sendMessage("Stop Config Recording")
  197 + Publisher.sendMessage("Set slice interaction style", const.STATE_DEFAULT)
  198 +
  199 + # Warning for limited support to Analyze format
  200 + if id_type == const.ID_ANALYZE_IMPORT:
  201 + dialog.ImportAnalyzeWarning()
  202 +
  203 + # Import project treating compressed nifti exception
  204 + suptype = ('hdr', 'nii', 'nii.gz', 'par')
  205 + filepath = dialog.ShowImportOtherFilesDialog(id_type)
  206 + name = filepath.rpartition('\\')[-1].split('.')
  207 +
  208 + if name[-1] == 'gz':
  209 + name[1] = 'nii.gz'
  210 +
  211 + filetype = name[1].lower()
  212 +
  213 + if filetype in suptype:
  214 + Publisher.sendMessage("Open other files", filepath)
  215 + else:
  216 + dialog.ImportInvalidFiles()
  217 +
189 def ShowDialogOpenProject(self): 218 def ShowDialogOpenProject(self):
190 # Offer to save current project if necessary 219 # Offer to save current project if necessary
191 session = ses.Session() 220 session = ses.Session()
@@ -281,8 +310,6 @@ class Controller(): @@ -281,8 +310,6 @@ class Controller():
281 else: 310 else:
282 dialog.InexistentPath(filepath) 311 dialog.InexistentPath(filepath)
283 312
284 -  
285 -  
286 def OpenProject(self, filepath): 313 def OpenProject(self, filepath):
287 Publisher.sendMessage('Begin busy cursor') 314 Publisher.sendMessage('Begin busy cursor')
288 path = os.path.abspath(filepath) 315 path = os.path.abspath(filepath)
@@ -342,7 +369,7 @@ class Controller(): @@ -342,7 +369,7 @@ class Controller():
342 369
343 def StartImportPanel(self, path): 370 def StartImportPanel(self, path):
344 371
345 - # retrieve DICOM files splited into groups 372 + # retrieve DICOM files split into groups
346 reader = dcm.ProgressDicomReader() 373 reader = dcm.ProgressDicomReader()
347 reader.SetWindowEvent(self.frame) 374 reader.SetWindowEvent(self.frame)
348 reader.SetDirectoryPath(path) 375 reader.SetDirectoryPath(path)
@@ -410,21 +437,33 @@ class Controller(): @@ -410,21 +437,33 @@ class Controller():
410 self.ImportMedicalImages(directory) 437 self.ImportMedicalImages(directory)
411 438
412 def ImportMedicalImages(self, directory): 439 def ImportMedicalImages(self, directory):
413 - # OPTION 1: DICOM?  
414 patients_groups = dcm.GetDicomGroups(directory) 440 patients_groups = dcm.GetDicomGroups(directory)
  441 + name = directory.rpartition('\\')[-1].split('.')
  442 + print "patients: ", patients_groups
  443 +
415 if len(patients_groups): 444 if len(patients_groups):
  445 + # OPTION 1: DICOM
416 group = dcm.SelectLargerDicomGroup(patients_groups) 446 group = dcm.SelectLargerDicomGroup(patients_groups)
417 - matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0,0],gui=True) 447 + matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=True)
418 self.CreateDicomProject(dicom, matrix, matrix_filename) 448 self.CreateDicomProject(dicom, matrix, matrix_filename)
419 - # OPTION 2: ANALYZE?  
420 else: 449 else:
421 - imagedata = analyze.ReadDirectory(directory)  
422 - if imagedata:  
423 - self.CreateAnalyzeProject(imagedata)  
424 - # OPTION 3: Nothing... 450 + # OPTION 2: NIfTI, Analyze or PAR/REC
  451 + if name[-1] == 'gz':
  452 + name[1] = 'nii.gz'
  453 +
  454 + suptype = ('hdr', 'nii', 'nii.gz', 'par')
  455 + filetype = name[1].lower()
  456 +
  457 + if filetype in suptype:
  458 + group = oth.ReadOthers(directory)
425 else: 459 else:
426 utils.debug("No medical images found on given directory") 460 utils.debug("No medical images found on given directory")
427 return 461 return
  462 +
  463 + matrix, matrix_filename = self.OpenOtherFiles(group)
  464 + self.CreateOtherProject(str(name[0]), matrix, matrix_filename)
  465 + # OPTION 4: Nothing...
  466 +
428 self.LoadProject() 467 self.LoadProject()
429 Publisher.sendMessage("Enable state project", True) 468 Publisher.sendMessage("Enable state project", True)
430 469
@@ -493,43 +532,6 @@ class Controller(): @@ -493,43 +532,6 @@ class Controller():
493 532
494 Publisher.sendMessage('End busy cursor') 533 Publisher.sendMessage('End busy cursor')
495 534
496 - def CreateAnalyzeProject(self, imagedata):  
497 - header = imagedata.get_header()  
498 - proj = prj.Project()  
499 - proj.imagedata = None  
500 - proj.name = _("Untitled")  
501 - proj.SetAcquisitionModality("MRI")  
502 - #TODO: Verify if all Analyse are in AXIAL orientation  
503 -  
504 - # To get Z, X, Y (used by InVesaliu), not X, Y, Z  
505 - matrix, matrix_filename = image_utils.analyze2mmap(imagedata)  
506 - if header['orient'] == 0:  
507 - proj.original_orientation = const.AXIAL  
508 - elif header['orient'] == 1:  
509 - proj.original_orientation = const.CORONAL  
510 - elif header['orient'] == 2:  
511 - proj.original_orientation = const.SAGITAL  
512 - else:  
513 - proj.original_orientation = const.SAGITAL  
514 -  
515 - proj.threshold_range = (int(header['glmin']),  
516 - int(header['glmax']))  
517 - proj.window = proj.threshold_range[1] - proj.threshold_range[0]  
518 - proj.level = (0.5 * (proj.threshold_range[1] + proj.threshold_range[0]))  
519 - proj.spacing = header['pixdim'][1:4]  
520 - proj.matrix_shape = matrix.shape  
521 -  
522 - self.Slice = sl.Slice()  
523 - self.Slice.matrix = matrix  
524 - self.Slice.matrix_filename = matrix_filename  
525 -  
526 - self.Slice.window_level = proj.level  
527 - self.Slice.window_width = proj.window  
528 - self.Slice.spacing = header.get_zooms()[:3]  
529 -  
530 - Publisher.sendMessage('Update threshold limits list',  
531 - proj.threshold_range)  
532 -  
533 def CreateDicomProject(self, dicom, matrix, matrix_filename): 535 def CreateDicomProject(self, dicom, matrix, matrix_filename):
534 name_to_const = {"AXIAL":const.AXIAL, 536 name_to_const = {"AXIAL":const.AXIAL,
535 "CORONAL":const.CORONAL, 537 "CORONAL":const.CORONAL,
@@ -604,6 +606,40 @@ class Controller(): @@ -604,6 +606,40 @@ class Controller():
604 606
605 dirpath = session.CreateProject(filename) 607 dirpath = session.CreateProject(filename)
606 608
  609 + def CreateOtherProject(self, name, matrix, matrix_filename):
  610 + name_to_const = {"AXIAL": const.AXIAL,
  611 + "CORONAL": const.CORONAL,
  612 + "SAGITTAL": const.SAGITAL}
  613 +
  614 + proj = prj.Project()
  615 + proj.name = name
  616 + proj.modality = 'MRI'
  617 + proj.SetAcquisitionModality('MRI')
  618 + proj.matrix_shape = matrix.shape
  619 + proj.matrix_dtype = matrix.dtype.name
  620 + proj.matrix_filename = matrix_filename
  621 +
  622 + # Orientation must be CORONAL in order to as_closes_canonical and
  623 + # swap axis in img2memmap to work in a standardized way.
  624 + # TODO: Create standard import image for all acquisition orientations
  625 + orientation = 'CORONAL'
  626 +
  627 + proj.original_orientation =\
  628 + name_to_const[orientation]
  629 +
  630 + proj.window = self.Slice.window_width
  631 + proj.level = self.Slice.window_level
  632 + proj.threshold_range = int(matrix.min()), int(matrix.max())
  633 + proj.spacing = self.Slice.spacing
  634 +
  635 + ######
  636 + session = ses.Session()
  637 + filename = proj.name + ".inv3"
  638 +
  639 + filename = filename.replace("/", "") # Fix problem case other/Skull_DICOM
  640 +
  641 + dirpath = session.CreateProject(filename)
  642 +
607 def OnOpenBitmapFiles(self, pubsub_evt): 643 def OnOpenBitmapFiles(self, pubsub_evt):
608 rec_data = pubsub_evt.data 644 rec_data = pubsub_evt.data
609 bmp_data = bmp.BitmapData() 645 bmp_data = bmp.BitmapData()
@@ -688,6 +724,26 @@ class Controller(): @@ -688,6 +724,26 @@ class Controller():
688 self.LoadProject() 724 self.LoadProject()
689 Publisher.sendMessage("Enable state project", True) 725 Publisher.sendMessage("Enable state project", True)
690 726
  727 + def OnOpenOtherFiles(self, pubsub_evt):
  728 + filepath = pubsub_evt.data
  729 + name = filepath.rpartition('\\')[-1].split('.')
  730 +
  731 + if name[-1] == 'gz':
  732 + name[1] = 'nii.gz'
  733 +
  734 + suptype = ('hdr', 'nii', 'nii.gz', 'par')
  735 + filetype = name[1].lower()
  736 +
  737 + if filetype in suptype:
  738 + group = oth.ReadOthers(filepath)
  739 + else:
  740 + dialog.ImportInvalidFiles()
  741 +
  742 + matrix, matrix_filename = self.OpenOtherFiles(group)
  743 + self.CreateOtherProject(str(name[0]), matrix, matrix_filename)
  744 + self.LoadProject()
  745 + Publisher.sendMessage("Enable state project", True)
  746 +
691 def OpenDicomGroup(self, dicom_group, interval, file_range, gui=True): 747 def OpenDicomGroup(self, dicom_group, interval, file_range, gui=True):
692 # Retrieve general DICOM headers 748 # Retrieve general DICOM headers
693 dicom = dicom_group.GetDicomSample() 749 dicom = dicom_group.GetDicomSample()
@@ -773,6 +829,30 @@ class Controller(): @@ -773,6 +829,30 @@ class Controller():
773 829
774 return self.matrix, self.filename, dicom 830 return self.matrix, self.filename, dicom
775 831
  832 + def OpenOtherFiles(self, group):
  833 + # Retreaving matrix from image data
  834 + self.matrix, scalar_range, self.filename = image_utils.img2memmap(group)
  835 +
  836 + hdr = group.header
  837 + hdr.set_data_dtype('int16')
  838 + dims = hdr.get_zooms()
  839 + dimsf = tuple([float(s) for s in dims])
  840 +
  841 + wl = float((scalar_range[0] + scalar_range[1]) * 0.5)
  842 + ww = float((scalar_range[1] - scalar_range[0]))
  843 +
  844 + self.Slice = sl.Slice()
  845 + self.Slice.matrix = self.matrix
  846 + self.Slice.matrix_filename = self.filename
  847 +
  848 + self.Slice.spacing = dimsf
  849 + self.Slice.window_level = wl
  850 + self.Slice.window_width = ww
  851 +
  852 + scalar_range = int(scalar_range[0]), int(scalar_range[1])
  853 + Publisher.sendMessage('Update threshold limits list', scalar_range)
  854 + return self.matrix, self.filename
  855 +
776 def LoadImagedataInfo(self): 856 def LoadImagedataInfo(self):
777 proj = prj.Project() 857 proj = prj.Project()
778 858
invesalius/data/bases.py
1 -from numpy import *  
2 from math import sqrt 1 from math import sqrt
  2 +import numpy as np
3 3
4 -class Bases:  
5 -  
6 - def __init__(self, p1, p2, p3):  
7 -  
8 - self.p1 = array([p1[0], p1[1], p1[2]])  
9 - self.p2 = array([p2[0], p2[1], p2[2]])  
10 - self.p3 = array([p3[0], p3[1], p3[2]])  
11 -  
12 - print "p1: ", self.p1  
13 - print "p2: ", self.p2  
14 - print "p3: ", self.p3  
15 4
16 - self.sub1 = self.p2 - self.p1  
17 - self.sub2 = self.p3 - self.p1  
18 -  
19 - def Basecreation(self):  
20 - #g1  
21 - g1 = self.sub1  
22 -  
23 - #g2  
24 - lamb1 = g1[0]*self.sub2[0] + g1[1]*self.sub2[1] + g1[2]*self.sub2[2]  
25 - lamb2 = dot(g1, g1)  
26 - lamb = lamb1/lamb2  
27 -  
28 - #Ponto q  
29 - q = self.p1 + lamb*self.sub1  
30 -  
31 - #g1 e g2 com origem em q  
32 - g1 = self.p1 - q  
33 - g2 = self.p3 - q  
34 -  
35 - #testa se o g1 nao eh um vetor nulo  
36 - if g1.any() == False:  
37 - g1 = self.p2 - q  
38 -  
39 - #g3 - Produto vetorial NumPy  
40 - g3 = cross(g2, g1)  
41 -  
42 - #normalizacao dos vetores  
43 - g1 = g1/sqrt(lamb2)  
44 - g2 = g2/sqrt(dot(g2, g2))  
45 - g3 = g3/sqrt(dot(g3, g3))  
46 -  
47 - M = matrix([[g1[0],g1[1],g1[2]], [g2[0],g2[1],g2[2]], [g3[0],g3[1],g3[2]]])  
48 - q.shape = (3, 1)  
49 - q = matrix(q.copy())  
50 - print"M: ", M  
51 - print  
52 - print"q: ", q  
53 - print  
54 - Minv = M.I  
55 -  
56 - return M, q, Minv  
57 -  
58 -def FlipX(point):  
59 -  
60 - point = matrix(point + (0,))  
61 -  
62 - #inverter o eixo z  
63 - ## possivel explicacaoo -- origem do eixo do imagedata esta no canto  
64 - ## superior esquerdo e origem da superfice eh no canto inferior esquerdo  
65 - ## ou a ordem de empilhamento das fatias  
66 -  
67 - point[0, 2] = -point[0, 2]  
68 -  
69 - #Flip em y  
70 - Mrot = matrix([[1.0, 0.0, 0.0, 0.0],  
71 - [0.0, -1.0, 0.0, 0.0],  
72 - [0.0, 0.0, -1.0, 0.0],  
73 - [0.0, 0.0, 0.0, 1.0]])  
74 - Mtrans = matrix([[1.0, 0, 0, -point[0, 0]],  
75 - [0.0, 1.0, 0, -point[0, 1]],  
76 - [0.0, 0.0, 1.0, -point[0, 2]],  
77 - [0.0, 0.0, 0.0, 1.0]])  
78 - Mtrans_return = matrix([[1.0, 0, 0, point[0, 0]], 5 +def angle_calculation(ap_axis, coil_axis):
  6 + """
  7 + Calculate angle between two given axis (in degrees)
  8 +
  9 + :param ap_axis: anterior posterior axis represented
  10 + :param coil_axis: tms coil axis
  11 + :return: angle between the two given axes
  12 + """
  13 +
  14 + ap_axis = np.array([ap_axis[0], ap_axis[1]])
  15 + coil_axis = np.array([float(coil_axis[0]), float(coil_axis[1])])
  16 + angle = np.rad2deg(np.arccos((np.dot(ap_axis, coil_axis))/(
  17 + np.linalg.norm(ap_axis)*np.linalg.norm(coil_axis))))
  18 +
  19 + return float(angle)
  20 +
  21 +
  22 +def base_creation(fiducials):
  23 + """
  24 + Calculate the origin and matrix for coordinate system
  25 + transformation.
  26 + q: origin of coordinate system
  27 + g1, g2, g3: orthogonal vectors of coordinate system
  28 +
  29 + :param fiducials: array of 3 rows (p1, p2, p3) and 3 columns (x, y, z) with fiducials coordinates
  30 + :return: matrix and origin for base transformation
  31 + """
  32 +
  33 + p1 = fiducials[0, :]
  34 + p2 = fiducials[1, :]
  35 + p3 = fiducials[2, :]
  36 +
  37 + sub1 = p2 - p1
  38 + sub2 = p3 - p1
  39 + lamb = (sub1[0]*sub2[0]+sub1[1]*sub2[1]+sub1[2]*sub2[2])/np.dot(sub1, sub1)
  40 +
  41 + q = p1 + lamb*sub1
  42 + g1 = p1 - q
  43 + g2 = p3 - q
  44 +
  45 + if not g1.any():
  46 + g1 = p2 - q
  47 +
  48 + g3 = np.cross(g2, g1)
  49 +
  50 + g1 = g1/sqrt(np.dot(g1, g1))
  51 + g2 = g2/sqrt(np.dot(g2, g2))
  52 + g3 = g3/sqrt(np.dot(g3, g3))
  53 +
  54 + m = np.matrix([[g1[0], g1[1], g1[2]],
  55 + [g2[0], g2[1], g2[2]],
  56 + [g3[0], g3[1], g3[2]]])
  57 +
  58 + q.shape = (3, 1)
  59 + q = np.matrix(q.copy())
  60 + m_inv = m.I
  61 +
  62 + # print"M: ", m
  63 + # print"q: ", q
  64 +
  65 + return m, q, m_inv
  66 +
  67 +
  68 +def calculate_fre(fiducials, minv, n, q1, q2):
  69 + """
  70 + Calculate the Fiducial Registration Error for neuronavigation.
  71 +
  72 + :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates
  73 + :param minv: inverse matrix given by base creation
  74 + :param n: base change matrix given by base creation
  75 + :param q1: origin of first base
  76 + :param q2: origin of second base
  77 + :return: float number of fiducial registration error
  78 + """
  79 +
  80 + img = np.zeros([3, 3])
  81 + dist = np.zeros([3, 1])
  82 +
  83 + p1 = np.mat(fiducials[3, :]).reshape(3, 1)
  84 + p2 = np.mat(fiducials[4, :]).reshape(3, 1)
  85 + p3 = np.mat(fiducials[5, :]).reshape(3, 1)
  86 +
  87 + img[0, :] = np.asarray((q1 + (minv * n) * (p1 - q2)).reshape(1, 3))
  88 + img[1, :] = np.asarray((q1 + (minv * n) * (p2 - q2)).reshape(1, 3))
  89 + img[2, :] = np.asarray((q1 + (minv * n) * (p3 - q2)).reshape(1, 3))
  90 +
  91 + dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2)))
  92 + dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2)))
  93 + dist[2] = np.sqrt(np.sum(np.power((img[2, :] - fiducials[2, :]), 2)))
  94 +
  95 + return float(np.sqrt(np.sum(dist ** 2) / 3))
  96 +
  97 +
  98 +def flip_x(point):
  99 + """
  100 + Flip coordinates of a vector according to X axis
  101 + Coronal Images do not require this transformation - 1 tested
  102 + and for this case, at navigation, the z axis is inverted
  103 +
  104 + It's necessary to multiply the z coordinate by (-1). Possibly
  105 + because the origin of coordinate system of imagedata is
  106 + located in superior left corner and the origin of VTK scene coordinate
  107 + system (polygonal surface) is in the interior left corner. Second
  108 + possibility is the order of slice stacking
  109 +
  110 + :param point: list of coordinates x, y and z
  111 + :return: flipped coordinates
  112 + """
  113 +
  114 + # TODO: check if the Flip function is related to the X or Y axis
  115 +
  116 + point = np.matrix(point + (0,))
  117 + point[0, 2] = -point[0, 2]
  118 +
  119 + m_rot = np.matrix([[1.0, 0.0, 0.0, 0.0],
  120 + [0.0, -1.0, 0.0, 0.0],
  121 + [0.0, 0.0, -1.0, 0.0],
  122 + [0.0, 0.0, 0.0, 1.0]])
  123 + m_trans = np.matrix([[1.0, 0, 0, -point[0, 0]],
  124 + [0.0, 1.0, 0, -point[0, 1]],
  125 + [0.0, 0.0, 1.0, -point[0, 2]],
  126 + [0.0, 0.0, 0.0, 1.0]])
  127 + m_trans_return = np.matrix([[1.0, 0, 0, point[0, 0]],
79 [0.0, 1.0, 0, point[0, 1]], 128 [0.0, 1.0, 0, point[0, 1]],
80 [0.0, 0.0, 1.0, point[0, 2]], 129 [0.0, 0.0, 1.0, point[0, 2]],
81 [0.0, 0.0, 0.0, 1.0]]) 130 [0.0, 0.0, 0.0, 1.0]])
82 131
83 - point_rot = point*Mtrans*Mrot*Mtrans_return  
84 - x, y, z = point_rot.tolist()[0][:3]  
85 - return x, y, z 132 + point_rot = point*m_trans*m_rot*m_trans_return
  133 + x, y, z = point_rot.tolist()[0][:3]
  134 +
  135 + return x, y, z
invesalius/data/co_registration.py
@@ -1,65 +0,0 @@ @@ -1,65 +0,0 @@
1 -import threading  
2 -  
3 -import serial  
4 -import wx  
5 -from wx.lib.pubsub import pub as Publisher  
6 -  
7 -from numpy import *  
8 -from math import sqrt  
9 -from time import sleep  
10 -  
11 -class Corregister(threading.Thread):  
12 -  
13 - def __init__(self, bases, flag):  
14 - threading.Thread.__init__(self)  
15 - self.Minv = bases[0]  
16 - self.N = bases[1]  
17 - self.q1 = bases[2]  
18 - self.q2 = bases[3]  
19 - self.flag = flag  
20 - self._pause_ = 0  
21 - self.start()  
22 -  
23 - def stop(self):  
24 - # Stop neuronavigation  
25 - self._pause_ = 1  
26 -  
27 - def Coordinates(self):  
28 - #Get Polhemus points for neuronavigation  
29 - ser = serial.Serial(0)  
30 - ser.write("Y")  
31 - ser.write("P")  
32 - str = ser.readline()  
33 - ser.write("Y")  
34 - str = str.replace("\r\n","")  
35 - str = str.replace("-"," -")  
36 - aostr = [s for s in str.split()]  
37 - #aoflt -> 0:letter 1:x 2:y 3:z  
38 - aoflt = [float(aostr[1]), float(aostr[2]), float(aostr[3]),  
39 - float(aostr[4]), float(aostr[5]), float(aostr[6])]  
40 - ser.close()  
41 -  
42 - #Unit change: inches to millimeters  
43 - x = 25.4  
44 - y = 25.4  
45 - z = -25.4  
46 -  
47 - coord = (aoflt[0]*x, aoflt[1]*y, aoflt[2]*z)  
48 - return coord  
49 -  
50 - def run(self):  
51 - while self.flag == True:  
52 - #Neuronavigation with Polhemus  
53 - trck = self.Coordinates()  
54 - tracker = matrix([[trck[0]], [trck[1]], [trck[2]]])  
55 - img = self.q1 + (self.Minv*self.N)*(tracker - self.q2)  
56 - coord = [float(img[0]), float(img[1]), float(img[2])]  
57 - coord_cam = float(img[0]), float(img[1]), float(img[2])  
58 - Publisher.sendMessage('Set ball reference position based on bound', coord_cam)  
59 - Publisher.sendMessage('Set camera in volume', coord_cam)  
60 - wx.CallAfter(Publisher.sendMessage, 'Render volume viewer')  
61 - wx.CallAfter(Publisher.sendMessage, 'Co-registered Points', coord)  
62 - sleep(0.005)  
63 -  
64 - if self._pause_:  
65 - return  
invesalius/data/coordinates.py 0 → 100644
@@ -0,0 +1,277 @@ @@ -0,0 +1,277 @@
  1 +#--------------------------------------------------------------------------
  2 +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas
  3 +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer
  4 +# Homepage: http://www.softwarepublico.gov.br
  5 +# Contact: invesalius@cti.gov.br
  6 +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
  7 +#--------------------------------------------------------------------------
  8 +# Este programa e software livre; voce pode redistribui-lo e/ou
  9 +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
  10 +# publicada pela Free Software Foundation; de acordo com a versao 2
  11 +# da Licenca.
  12 +#
  13 +# Este programa eh distribuido na expectativa de ser util, mas SEM
  14 +# QUALQUER GARANTIA; sem mesmo a garantia implicita de
  15 +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
  16 +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
  17 +# detalhes.
  18 +#--------------------------------------------------------------------------
  19 +
  20 +from math import sin, cos
  21 +import numpy as np
  22 +
  23 +from random import uniform
  24 +
  25 +
  26 +def GetCoordinates(trck_init, trck_id, ref_mode):
  27 +
  28 + """
  29 + Read coordinates from spatial tracking devices using
  30 +
  31 + :param trck_init: Initialization variable of tracking device and connection type. See tracker.py.
  32 + :param trck_id: ID of tracking device.
  33 + :param ref_mode: Single or dynamic reference mode of tracking.
  34 + :return: array of six coordinates (x, y, z, alpha, beta, gamma)
  35 + """
  36 +
  37 + coord = None
  38 + if trck_id:
  39 + getcoord = {1: ClaronCoord,
  40 + 2: PolhemusCoord,
  41 + 3: PolhemusCoord,
  42 + 4: PolhemusCoord,
  43 + 5: DebugCoord}
  44 + coord = getcoord[trck_id](trck_init, trck_id, ref_mode)
  45 + else:
  46 + print "Select Tracker"
  47 +
  48 + return coord
  49 +
  50 +
  51 +def ClaronCoord(trck_init, trck_id, ref_mode):
  52 + trck = trck_init[0]
  53 + scale = 10. * np.array([1., 1.0, -1.0])
  54 + coord = None
  55 + k = 0
  56 + # TODO: try to replace while and use some Claron internal computation
  57 + if ref_mode:
  58 + while k < 20:
  59 + try:
  60 + trck.Run()
  61 + probe = np.array([trck.PositionTooltipX1 * scale[0], trck.PositionTooltipY1 * scale[1],
  62 + trck.PositionTooltipZ1 * scale[2], trck.AngleX1, trck.AngleY1, trck.AngleZ1])
  63 + reference = np.array([trck.PositionTooltipX2 * scale[0], trck.PositionTooltipY2 * scale[1],
  64 + trck.PositionTooltipZ2 * scale[2], trck.AngleX2, trck.AngleY2, trck.AngleZ2])
  65 + k = 30
  66 + except AttributeError:
  67 + k += 1
  68 + print "wait, collecting coordinates ..."
  69 + if k == 30:
  70 + coord = dynamic_reference(probe, reference)
  71 + else:
  72 + while k < 20:
  73 + try:
  74 + trck.Run()
  75 + coord = np.array([trck.PositionTooltipX1 * scale[0], trck.PositionTooltipY1 * scale[1],
  76 + trck.PositionTooltipZ1 * scale[2], trck.AngleX1, trck.AngleY1, trck.AngleZ1])
  77 + k = 30
  78 + except AttributeError:
  79 + k += 1
  80 + print "wait, collecting coordinates ..."
  81 +
  82 + return coord
  83 +
  84 +
  85 +def PolhemusCoord(trck, trck_id, ref_mode):
  86 + coord = None
  87 +
  88 + if trck[1] == 'serial':
  89 + coord = PolhemusSerialCoord(trck[0], trck_id, ref_mode)
  90 +
  91 + elif trck[1] == 'usb':
  92 + coord = PolhemusUSBCoord(trck[0], trck_id, ref_mode)
  93 +
  94 + elif trck[1] == 'wrapper':
  95 + coord = PolhemusWrapperCoord(trck[0], trck_id, ref_mode)
  96 +
  97 + return coord
  98 +
  99 +
  100 +def PolhemusWrapperCoord(trck, trck_id, ref_mode):
  101 + if trck_id == 2:
  102 + scale = 10. * np.array([1., 1.0, -1.0])
  103 + else:
  104 + scale = 25.4 * np.array([1., 1.0, -1.0])
  105 + coord = None
  106 +
  107 + if ref_mode:
  108 + trck.Run()
  109 + probe = np.array([float(trck.PositionTooltipX1) * scale[0], float(trck.PositionTooltipY1) * scale[1],
  110 + float(trck.PositionTooltipZ1) * scale[2], float(trck.AngleX1), float(trck.AngleY1),
  111 + float(trck.AngleZ1)])
  112 + reference = np.array([float(trck.PositionTooltipX2) * scale[0], float(trck.PositionTooltipY2) * scale[1],
  113 + float(trck.PositionTooltipZ2) * scale[2], float(trck.AngleX2), float(trck.AngleY2),
  114 + float(trck.AngleZ2)])
  115 +
  116 + if probe.all() and reference.all():
  117 + coord = dynamic_reference(probe, reference)
  118 +
  119 + else:
  120 + trck.Run()
  121 + coord = np.array([float(trck.PositionTooltipX1) * scale[0], float(trck.PositionTooltipY1) * scale[1],
  122 + float(trck.PositionTooltipZ1) * scale[2], float(trck.AngleX1), float(trck.AngleY1),
  123 + float(trck.AngleZ1)])
  124 +
  125 + return coord
  126 +
  127 +
  128 +def PolhemusUSBCoord(trck, trck_id, ref_mode):
  129 + endpoint = trck[0][(0, 0)][0]
  130 + # Tried to write some settings to Polhemus in trackers.py while initializing the device.
  131 + # TODO: Check if it's working properly.
  132 + trck.write(0x02, "P")
  133 + if trck_id == 2:
  134 + scale = 10. * np.array([1., 1.0, -1.0])
  135 + else:
  136 + scale = 25.4 * np.array([1., 1.0, -1.0])
  137 + coord = None
  138 +
  139 + if ref_mode:
  140 +
  141 + data = trck.read(endpoint.bEndpointAddress, 2 * endpoint.wMaxPacketSize)
  142 + data = str2float(data.tostring())
  143 +
  144 + # six coordinates of first and second sensor: x, y, z and alfa, beta and gama
  145 + # jump one element for reference to avoid the sensor ID returned by Polhemus
  146 + probe = data[0] * scale[0], data[1] * scale[1], data[2] * scale[2], \
  147 + data[3], data[4], data[5], data[6]
  148 + reference = data[7] * scale[0], data[8] * scale[1], data[9] * scale[2], data[10], \
  149 + data[11], data[12], data[13]
  150 +
  151 + if probe.all() and reference.all():
  152 + coord = dynamic_reference(probe, reference)
  153 +
  154 + return coord
  155 +
  156 + else:
  157 + data = trck.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize)
  158 + coord = str2float(data.tostring())
  159 +
  160 + coord = np.array((coord[0] * scale[0], coord[1] * scale[1], coord[2] * scale[2],
  161 + coord[3], coord[4], coord[5]))
  162 +
  163 + return coord
  164 +
  165 +
  166 +def PolhemusSerialCoord(trck_init, trck_id, ref_mode):
  167 + # mudanca para fastrak - ref 1 tem somente x, y, z
  168 + # aoflt -> 0:letter 1:x 2:y 3:z
  169 + # this method is not optimized to work with all trackers, only with ISOTRAK
  170 + # serial connection is obsolete, remove in future
  171 + trck_init.write("P")
  172 + lines = trck_init.readlines()
  173 +
  174 + coord = None
  175 +
  176 + if lines[0][0] != '0':
  177 + print "The Polhemus is not connected!"
  178 + else:
  179 + for s in lines:
  180 + if s[1] == '1':
  181 + data = s
  182 + elif s[1] == '2':
  183 + data = s
  184 +
  185 + # single ref mode
  186 + if not ref_mode:
  187 + data = data.replace('-', ' -')
  188 + data = [s for s in data.split()]
  189 + j = 0
  190 + while j == 0:
  191 + try:
  192 + plh1 = [float(s) for s in data[1:len(data)]]
  193 + j = 1
  194 + except ValueError:
  195 + trck_init.write("P")
  196 + data = trck_init.readline()
  197 + data = data.replace('-', ' -')
  198 + data = [s for s in data.split()]
  199 + print "Trying to fix the error!!"
  200 +
  201 + coord = data[0:6]
  202 + return coord
  203 +
  204 +
  205 +def DebugCoord(trk_init, trck_id, ref_mode):
  206 + """
  207 + Method to simulate a tracking device for debug and error check. Generate a random
  208 + x, y, z, alfa, beta and gama coordinates in interval [1, 200[
  209 + :param trk_init: tracker initialization instance
  210 + :param ref_mode: flag for singular of dynamic reference
  211 + :param trck_id: id of tracking device
  212 + :return: six coordinates x, y, z, alfa, beta and gama
  213 + """
  214 +
  215 + if ref_mode:
  216 + probe = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200),
  217 + uniform(1, 200), uniform(1, 200), uniform(1, 200)])
  218 + reference = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200),
  219 + uniform(1, 200), uniform(1, 200), uniform(1, 200)])
  220 +
  221 + coord = dynamic_reference(probe, reference)
  222 +
  223 + else:
  224 + coord = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200),
  225 + uniform(1, 200), uniform(1, 200), uniform(1, 200)])
  226 +
  227 + return coord
  228 +
  229 +
  230 +def dynamic_reference(probe, reference):
  231 + """
  232 + Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama
  233 + rotation angles of reference to rotate the probe coordinate and returns the x, y, z
  234 + difference between probe and reference. Angles sequences and equation was extracted from
  235 + Polhemus manual and Attitude matrix in Wikipedia.
  236 + General equation is:
  237 + coord = Mrot * (probe - reference)
  238 + :param probe: sensor one defined as probe
  239 + :param reference: sensor two defined as reference
  240 + :return: rotated and translated coordinates
  241 + """
  242 + a, b, g = np.radians(reference[3:6])
  243 +
  244 + vet = probe[0:3] - reference[0:3]
  245 + vet = np.mat(vet.reshape(3, 1))
  246 +
  247 + # Attitude Matrix given by Patriot Manual
  248 + Mrot = np.mat([[cos(a) * cos(b), sin(b) * sin(g) * cos(a) - cos(g) * sin(a),
  249 + cos(a) * sin(b) * cos(g) + sin(a) * sin(g)],
  250 + [cos(b) * sin(a), sin(b) * sin(g) * sin(a) + cos(g) * cos(a),
  251 + cos(g) * sin(b) * sin(a) - sin(g) * cos(a)],
  252 + [-sin(b), sin(g) * cos(b), cos(b) * cos(g)]])
  253 +
  254 + coord_rot = Mrot.T * vet
  255 + coord_rot = np.squeeze(np.asarray(coord_rot))
  256 +
  257 + return coord_rot[0], coord_rot[1], coord_rot[2], probe[3], probe[4], probe[5]
  258 +
  259 +
  260 +def str2float(data):
  261 + """
  262 + Converts string detected wth Polhemus device to float array of coordinates. THis method applies
  263 + a correction for the minus sign in string that raises error while splitting the string into coordinates.
  264 + :param data: string of coordinates read with Polhemus
  265 + :return: six float coordinates x, y, z, alfa, beta and gama
  266 + """
  267 +
  268 + count = 0
  269 + for i, j in enumerate(data):
  270 + if j == '-':
  271 + data = data[:i + count] + ' ' + data[i + count:]
  272 + count += 1
  273 +
  274 + data = [s for s in data.split()]
  275 + data = [float(s) for s in data[1:len(data)]]
  276 +
  277 + return data
invesalius/data/coregistration.py 0 → 100644
@@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
  1 +#--------------------------------------------------------------------------
  2 +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas
  3 +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer
  4 +# Homepage: http://www.softwarepublico.gov.br
  5 +# Contact: invesalius@cti.gov.br
  6 +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
  7 +#--------------------------------------------------------------------------
  8 +# Este programa e software livre; voce pode redistribui-lo e/ou
  9 +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
  10 +# publicada pela Free Software Foundation; de acordo com a versao 2
  11 +# da Licenca.
  12 +#
  13 +# Este programa eh distribuido na expectativa de ser util, mas SEM
  14 +# QUALQUER GARANTIA; sem mesmo a garantia implicita de
  15 +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
  16 +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
  17 +# detalhes.
  18 +#--------------------------------------------------------------------------
  19 +
  20 +import threading
  21 +from time import sleep
  22 +
  23 +from numpy import mat
  24 +import wx
  25 +from wx.lib.pubsub import pub as Publisher
  26 +
  27 +import invesalius.data.coordinates as dco
  28 +
  29 +# TODO: Optimize navigation thread. Remove the infinite loop and optimize sleep.
  30 +
  31 +
  32 +class Coregistration(threading.Thread):
  33 + """
  34 + Thread to update the coordinates with the fiducial points
  35 + co-registration method while the Navigation Button is pressed.
  36 + Sleep function in run method is used to avoid blocking GUI and
  37 + for better real-time navigation
  38 + """
  39 +
  40 + def __init__(self, bases, nav_id, trck_info):
  41 + threading.Thread.__init__(self)
  42 + self.bases = bases
  43 + self.nav_id = nav_id
  44 + self.trck_info = trck_info
  45 + self._pause_ = False
  46 + self.start()
  47 +
  48 + def stop(self):
  49 + self._pause_ = True
  50 +
  51 + def run(self):
  52 + m_inv = self.bases[0]
  53 + n = self.bases[1]
  54 + q1 = self.bases[2]
  55 + q2 = self.bases[3]
  56 + trck_init = self.trck_info[0]
  57 + trck_id = self.trck_info[1]
  58 + trck_mode = self.trck_info[2]
  59 +
  60 + while self.nav_id:
  61 + trck_coord = dco.GetCoordinates(trck_init, trck_id, trck_mode)
  62 + trck_xyz = mat([[trck_coord[0]], [trck_coord[1]], [trck_coord[2]]])
  63 +
  64 + img = q1 + (m_inv*n)*(trck_xyz - q2)
  65 +
  66 + coord = (float(img[0]), float(img[1]), float(img[2]), trck_coord[3],
  67 + trck_coord[4], trck_coord[5])
  68 +
  69 + # Tried several combinations and different locations to send the messages,
  70 + # however only this one does not block the GUI during navigation.
  71 + wx.CallAfter(Publisher.sendMessage, 'Co-registered points', coord[0:3])
  72 + wx.CallAfter(Publisher.sendMessage, 'Set camera in volume', coord[0:3])
  73 +
  74 + # TODO: Optimize the value of sleep for each tracking device.
  75 + # Debug tracker is not working with 0.175 so changed to 0.2
  76 + # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY.
  77 + sleep(.3)
  78 + # sleep(0.175)
  79 +
  80 + if self._pause_:
  81 + return
invesalius/data/imagedata_utils.py
@@ -592,41 +592,55 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage): @@ -592,41 +592,55 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage):
592 592
593 return matrix, scalar_range, temp_file 593 return matrix, scalar_range, temp_file
594 594
595 -def analyze2mmap(analyze):  
596 - data = analyze.get_data()  
597 - header = analyze.get_header() 595 +
  596 +def img2memmap(group):
  597 + """
  598 + From a nibabel image data creates a memmap file in the temp folder and
  599 + returns it and its related filename.
  600 + """
  601 +
598 temp_file = tempfile.mktemp() 602 temp_file = tempfile.mktemp()
599 603
600 - # Sagital  
601 - if header['orient'] == 2:  
602 - print "Orientation Sagital"  
603 - shape = tuple([data.shape[i] for i in (1, 2, 0)])  
604 - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape)  
605 - for n, slice in enumerate(data):  
606 - matrix[:,:, n] = slice  
607 -  
608 - # Coronal  
609 - elif header['orient'] == 1:  
610 - print "Orientation coronal"  
611 - shape = tuple([data.shape[i] for i in (1, 0, 2)])  
612 - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape)  
613 - for n, slice in enumerate(data):  
614 - matrix[:,n,:] = slice  
615 -  
616 - # AXIAL  
617 - elif header['orient'] == 0:  
618 - print "no orientation"  
619 - shape = tuple([data.shape[i] for i in (0, 1, 2)])  
620 - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape)  
621 - for n, slice in enumerate(data):  
622 - matrix[n] = slice 604 + data = group.get_data()
  605 + # Normalize image pixel values and convert to int16
  606 + data = imgnormalize(data)
623 607
624 - else:  
625 - print "Orientation Sagital"  
626 - shape = tuple([data.shape[i] for i in (1, 2, 0)])  
627 - matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=shape)  
628 - for n, slice in enumerate(data):  
629 - matrix[:,:, n] = slice 608 + # Convert RAS+ to default InVesalius orientation ZYX
  609 + data = numpy.swapaxes(data, 0, 2)
  610 + data = numpy.fliplr(data)
630 611
  612 + matrix = numpy.memmap(temp_file, mode='w+', dtype=data.dtype, shape=data.shape)
  613 + matrix[:] = data[:]
631 matrix.flush() 614 matrix.flush()
632 - return matrix, temp_file 615 +
  616 + scalar_range = numpy.amin(matrix), numpy.amax(matrix)
  617 +
  618 + return matrix, scalar_range, temp_file
  619 +
  620 +
  621 +def imgnormalize(data, srange=(0, 255)):
  622 + """
  623 + Normalize image pixel intensity for int16 gray scale values.
  624 +
  625 + :param data: image matrix
  626 + :param srange: range for normalization, default is 0 to 255
  627 + :return: normalized pixel intensity matrix
  628 + """
  629 +
  630 + dataf = numpy.asarray(data)
  631 + rangef = numpy.asarray(srange)
  632 + faux = numpy.ravel(dataf).astype(float)
  633 + minimum = numpy.min(faux)
  634 + maximum = numpy.max(faux)
  635 + lower = rangef[0]
  636 + upper = rangef[1]
  637 +
  638 + if minimum == maximum:
  639 + datan = numpy.ones(dataf.shape)*(upper + lower) / 2.
  640 + else:
  641 + datan = (faux-minimum)*(upper-lower) / (maximum-minimum) + lower
  642 +
  643 + datan = numpy.reshape(datan, dataf.shape)
  644 + datan = datan.astype(numpy.int16)
  645 +
  646 + return datan
633 \ No newline at end of file 647 \ No newline at end of file
invesalius/data/slice_.py
@@ -531,10 +531,12 @@ class Slice(object): @@ -531,10 +531,12 @@ class Slice(object):
531 image = self.do_colour_image(ww_wl_image) 531 image = self.do_colour_image(ww_wl_image)
532 if self.current_mask and self.current_mask.is_shown: 532 if self.current_mask and self.current_mask.is_shown:
533 if self.buffer_slices[orientation].vtk_mask: 533 if self.buffer_slices[orientation].vtk_mask:
534 - print "Getting from buffer" 534 + # Prints that during navigation causes delay in update
  535 + # print "Getting from buffer"
535 mask = self.buffer_slices[orientation].vtk_mask 536 mask = self.buffer_slices[orientation].vtk_mask
536 else: 537 else:
537 - print "Do not getting from buffer" 538 + # Prints that during navigation causes delay in update
  539 + # print "Do not getting from buffer"
538 n_mask = self.get_mask_slice(orientation, slice_number) 540 n_mask = self.get_mask_slice(orientation, slice_number)
539 mask = converters.to_vtk(n_mask, self.spacing, slice_number, orientation) 541 mask = converters.to_vtk(n_mask, self.spacing, slice_number, orientation)
540 mask = self.do_colour_mask(mask, self.opacity) 542 mask = self.do_colour_mask(mask, self.opacity)
invesalius/data/styles.py
@@ -230,10 +230,8 @@ class CrossInteractorStyle(DefaultInteractorStyle): @@ -230,10 +230,8 @@ class CrossInteractorStyle(DefaultInteractorStyle):
230 coord = self.viewer.calcultate_scroll_position(px, py) 230 coord = self.viewer.calcultate_scroll_position(px, py)
231 Publisher.sendMessage('Update cross position', (wx, wy, wz)) 231 Publisher.sendMessage('Update cross position', (wx, wy, wz))
232 self.ScrollSlice(coord) 232 self.ScrollSlice(coord)
233 - Publisher.sendMessage('Set ball reference position based on bound',  
234 - (wx, wy, wz)) 233 + Publisher.sendMessage('Set ball reference position', (wx, wy, wz))
235 Publisher.sendMessage('Set camera in volume', (wx, wy, wz)) 234 Publisher.sendMessage('Set camera in volume', (wx, wy, wz))
236 - Publisher.sendMessage('Render volume viewer')  
237 235
238 iren.Render() 236 iren.Render()
239 237
invesalius/data/trackers.py 0 → 100644
@@ -0,0 +1,222 @@ @@ -0,0 +1,222 @@
  1 +#--------------------------------------------------------------------------
  2 +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas
  3 +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer
  4 +# Homepage: http://www.softwarepublico.gov.br
  5 +# Contact: invesalius@cti.gov.br
  6 +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
  7 +#--------------------------------------------------------------------------
  8 +# Este programa e software livre; voce pode redistribui-lo e/ou
  9 +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
  10 +# publicada pela Free Software Foundation; de acordo com a versao 2
  11 +# da Licenca.
  12 +#
  13 +# Este programa eh distribuido na expectativa de ser util, mas SEM
  14 +# QUALQUER GARANTIA; sem mesmo a garantia implicita de
  15 +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
  16 +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
  17 +# detalhes.
  18 +#--------------------------------------------------------------------------
  19 +
  20 +# TODO: Disconnect tracker when a new one is connected
  21 +# TODO: Test if there are too many prints when connection fails
  22 +
  23 +
  24 +def TrackerConnection(tracker_id, action):
  25 + """
  26 + Initialize spatial trackers for coordinate detection during navigation.
  27 +
  28 + :param tracker_id: ID of tracking device.
  29 + :param action: string with to decide whether connect or disconnect the selected device.
  30 + :return spatial tracker initialization instance or None if could not open device.
  31 + """
  32 +
  33 + if action == 'connect':
  34 + trck_fcn = {1: ClaronTracker,
  35 + 2: PolhemusTracker, # FASTRAK
  36 + 3: PolhemusTracker, # ISOTRAK
  37 + 4: PolhemusTracker, # PATRIOT
  38 + 5: DebugTracker}
  39 +
  40 + trck_init = trck_fcn[tracker_id](tracker_id)
  41 +
  42 + elif action == 'disconnect':
  43 + trck_init = DisconnectTracker(tracker_id)
  44 +
  45 + return trck_init
  46 +
  47 +
  48 +def DefaultTracker(tracker_id):
  49 + trck_init = None
  50 + try:
  51 + # import spatial tracker library
  52 + print 'Connect to default tracking device.'
  53 +
  54 + except:
  55 + print 'Could not connect to default tracker.'
  56 +
  57 + # return tracker initialization variable and type of connection
  58 + return trck_init, 'wrapper'
  59 +
  60 +
  61 +def ClaronTracker(tracker_id):
  62 + import invesalius.constants as const
  63 +
  64 + trck_init = None
  65 + try:
  66 + import pyclaron
  67 +
  68 + lib_mode = 'wrapper'
  69 + trck_init = pyclaron.pyclaron()
  70 + trck_init.CalibrationDir = const.CAL_DIR
  71 + trck_init.MarkerDir = const.MAR_DIR
  72 + trck_init.NumberFramesProcessed = 10
  73 + trck_init.FramesExtrapolated = 0
  74 + trck_init.Initialize()
  75 +
  76 + if trck_init.GetIdentifyingCamera():
  77 + trck_init.Run()
  78 + print "MicronTracker camera identified."
  79 + else:
  80 + trck_init = None
  81 +
  82 + except ImportError:
  83 + lib_mode = 'error'
  84 + print 'The ClaronTracker library is not installed.'
  85 +
  86 + return trck_init, lib_mode
  87 +
  88 +
  89 +def PolhemusTracker(tracker_id):
  90 + trck_init = None
  91 + try:
  92 + trck_init = PlhWrapperConnection()
  93 + lib_mode = 'wrapper'
  94 + if not trck_init:
  95 + print 'Could not connect with Polhemus wrapper, trying USB connection...'
  96 + trck_init = PlhUSBConnection(tracker_id)
  97 + lib_mode = 'usb'
  98 + if not trck_init:
  99 + print 'Could not connect with Polhemus USB, trying serial connection...'
  100 + trck_init = PlhSerialConnection(tracker_id)
  101 + lib_mode = 'serial'
  102 + except:
  103 + lib_mode = 'error'
  104 + print 'Could not connect to Polhemus.'
  105 +
  106 + return trck_init, lib_mode
  107 +
  108 +
  109 +def DebugTracker(tracker_id):
  110 + trck_init = True
  111 + print 'Debug device started.'
  112 + return trck_init, 'debug'
  113 +
  114 +
  115 +def PlhWrapperConnection():
  116 + trck_init = None
  117 + try:
  118 + import polhemus
  119 +
  120 + trck_init = polhemus.polhemus()
  121 + trck_check = trck_init.Initialize()
  122 +
  123 + if trck_check:
  124 + # First run is necessary to discard the first coord collection
  125 + trck_init.Run()
  126 + except:
  127 + print 'Could not connect to Polhemus via wrapper.'
  128 +
  129 + return trck_init
  130 +
  131 +
  132 +def PlhSerialConnection(tracker_id):
  133 + trck_init = None
  134 + try:
  135 + import serial
  136 +
  137 + trck_init = serial.Serial(0, baudrate=115200, timeout=0.2)
  138 +
  139 + if tracker_id == 2:
  140 + # Polhemus FASTRAK needs configurations first
  141 + trck_init.write(0x02, "u")
  142 + trck_init.write(0x02, "F")
  143 + elif tracker_id == 3:
  144 + # Polhemus ISOTRAK needs to set tracking point from
  145 + # center to tip.
  146 + trck_init.write("Y")
  147 +
  148 + trck_init.write('P')
  149 + data = trck_init.readlines()
  150 +
  151 + if not data:
  152 + trck_init = None
  153 +
  154 + except:
  155 + print 'Could not connect to Polhemus serial.'
  156 +
  157 + return trck_init
  158 +
  159 +
  160 +def PlhUSBConnection(tracker_id):
  161 + trck_init = None
  162 + try:
  163 + import usb.core as uc
  164 + trck_init = uc.find(idVendor=0x0F44, idProduct=0x0003)
  165 + cfg = trck_init.get_active_configuration()
  166 + for i in cfg:
  167 + for x in i:
  168 + # TODO: try better code
  169 + x = x
  170 + trck_init.set_configuration()
  171 + endpoint = trck_init[0][(0, 0)][0]
  172 + if tracker_id == 2:
  173 + # Polhemus FASTRAK needs configurations first
  174 + # TODO: Check configurations to standardize initialization for all Polhemus devices
  175 + trck_init.write(0x02, "u")
  176 + trck_init.write(0x02, "F")
  177 + # First run to confirm that everything is working
  178 + trck_init.write(0x02, "P")
  179 + data = trck_init.read(endpoint.bEndpointAddress,
  180 + endpoint.wMaxPacketSize)
  181 + if not data:
  182 + trck_init = None
  183 +
  184 + except uc.USBError as err:
  185 + print 'Could not set configuration %s' % err
  186 + else:
  187 + print 'Could not connect to Polhemus USB.'
  188 +
  189 + return trck_init
  190 +
  191 +
  192 +def DisconnectTracker(tracker_id):
  193 + """
  194 + Disconnect current spatial tracker
  195 +
  196 + :param tracker_id: ID of tracking device.
  197 + """
  198 + trck_init = None
  199 + # TODO: create individual functions to disconnect each other device, e.g. Polhemus.
  200 + if tracker_id == 1:
  201 + try:
  202 + import pyclaron
  203 + pyclaron.pyclaron().Close()
  204 + lib_mode = 'wrapper'
  205 + except ImportError:
  206 + lib_mode = 'error'
  207 + print 'The ClaronTracker library is not installed.'
  208 +
  209 + elif tracker_id == 4:
  210 + try:
  211 + import polhemus
  212 + polhemus.polhemus().Close()
  213 + lib_mode = 'wrapper'
  214 + except ImportError:
  215 + lib_mode = 'error'
  216 + print 'The polhemus library is not installed.'
  217 +
  218 + elif tracker_id == 5:
  219 + print 'Debug tracker disconnected.'
  220 + lib_mode = 'debug'
  221 +
  222 + return trck_init, lib_mode
0 \ No newline at end of file 223 \ No newline at end of file
invesalius/data/trigger.py 0 → 100644
@@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
  1 +#--------------------------------------------------------------------------
  2 +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas
  3 +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer
  4 +# Homepage: http://www.softwarepublico.gov.br
  5 +# Contact: invesalius@cti.gov.br
  6 +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
  7 +#--------------------------------------------------------------------------
  8 +# Este programa e software livre; voce pode redistribui-lo e/ou
  9 +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
  10 +# publicada pela Free Software Foundation; de acordo com a versao 2
  11 +# da Licenca.
  12 +#
  13 +# Este programa eh distribuido na expectativa de ser util, mas SEM
  14 +# QUALQUER GARANTIA; sem mesmo a garantia implicita de
  15 +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
  16 +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
  17 +# detalhes.
  18 +#--------------------------------------------------------------------------
  19 +
  20 +import threading
  21 +from time import sleep
  22 +
  23 +import wx
  24 +from wx.lib.pubsub import pub as Publisher
  25 +
  26 +
  27 +class Trigger(threading.Thread):
  28 + """
  29 + Thread created to use external trigger to interact with software during neuronavigation
  30 + """
  31 +
  32 + def __init__(self, nav_id):
  33 + threading.Thread.__init__(self)
  34 + self.trigger_init = None
  35 + try:
  36 + import serial
  37 +
  38 + self.trigger_init = serial.Serial('COM1', baudrate=115200, timeout=0)
  39 + self.nav_id = nav_id
  40 + self._pause_ = False
  41 + self.start()
  42 +
  43 + except ImportError:
  44 + print 'PySerial library not installed. Please install to use Trigger option.'
  45 +
  46 + except serial.serialutil.SerialException:
  47 + print 'Connection with port COM1 failed.'
  48 +
  49 + def stop(self):
  50 + if self.trigger_init:
  51 + self.trigger_init.close()
  52 + self._pause_ = True
  53 +
  54 + def run(self):
  55 + while self.nav_id:
  56 + lines = self.trigger_init.readlines()
  57 + # Following lines can simulate a trigger in 3 sec repetitions
  58 + # sleep(3)
  59 + # lines = True
  60 + if lines:
  61 + wx.CallAfter(Publisher.sendMessage, 'Create marker')
  62 + sleep(0.175)
  63 + if self._pause_:
  64 + return
invesalius/data/viewer_slice.py
@@ -911,17 +911,15 @@ class Viewer(wx.Panel): @@ -911,17 +911,15 @@ class Viewer(wx.Panel):
911 if self.slice_data.cursor: 911 if self.slice_data.cursor:
912 self.slice_data.cursor.SetColour(colour_vtk) 912 self.slice_data.cursor.SetColour(colour_vtk)
913 913
914 - def Navigation(self, pubsub_evt): 914 + def UpdateSlicesNavigation(self, pubsub_evt):
915 # Get point from base change 915 # Get point from base change
916 - x, y, z = pubsub_evt.data  
917 - coord_cross = x, y, z  
918 - position = self.slice_data.actor.GetInput().FindPoint(x, y, z)  
919 - coord_cross = self.slice_data.actor.GetInput().GetPoint(position)  
920 - coord = self.calcultate_scroll_position(position)  
921 - Publisher.sendMessage('Update cross position', coord_cross) 916 + wx, wy, wz = pubsub_evt.data
  917 + px, py = self.get_slice_pixel_coord_by_world_pos(wx, wy, wz)
  918 + coord = self.calcultate_scroll_position(px, py)
922 919
  920 + self.cross.SetFocalPoint((wx, wy, wz))
923 self.ScrollSlice(coord) 921 self.ScrollSlice(coord)
924 - self.interactor.Render() 922 + Publisher.sendMessage('Set ball reference position', (wx, wy, wz))
925 923
926 def ScrollSlice(self, coord): 924 def ScrollSlice(self, coord):
927 if self.orientation == "AXIAL": 925 if self.orientation == "AXIAL":
@@ -1165,8 +1163,8 @@ class Viewer(wx.Panel): @@ -1165,8 +1163,8 @@ class Viewer(wx.Panel):
1165 self.orientation)) 1163 self.orientation))
1166 Publisher.subscribe(self.__update_cross_position, 1164 Publisher.subscribe(self.__update_cross_position,
1167 'Update cross position') 1165 'Update cross position')
1168 - Publisher.subscribe(self.Navigation,  
1169 - 'Co-registered Points') 1166 + Publisher.subscribe(self.UpdateSlicesNavigation,
  1167 + 'Co-registered points')
1170 ### 1168 ###
1171 # Publisher.subscribe(self.ChangeBrushColour, 1169 # Publisher.subscribe(self.ChangeBrushColour,
1172 # 'Add mask') 1170 # 'Add mask')
invesalius/data/viewer_volume.py
@@ -22,7 +22,8 @@ @@ -22,7 +22,8 @@
22 22
23 import sys 23 import sys
24 24
25 -import numpy 25 +import numpy as np
  26 +from numpy.core.umath_tests import inner1d
26 import wx 27 import wx
27 import vtk 28 import vtk
28 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor 29 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
@@ -45,10 +46,11 @@ class Viewer(wx.Panel): @@ -45,10 +46,11 @@ class Viewer(wx.Panel):
45 46
46 self.interaction_style = st.StyleStateManager() 47 self.interaction_style = st.StyleStateManager()
47 48
48 - self.ball_reference = None  
49 - self.initial_foco = None 49 + self.initial_focus = None
50 50
51 - style = vtk.vtkInteractorStyleTrackballCamera() 51 + self.staticballs = []
  52 +
  53 + style = vtk.vtkInteractorStyleTrackballCamera()
52 self.style = style 54 self.style = style
53 55
54 interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize()) 56 interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize())
@@ -111,6 +113,9 @@ class Viewer(wx.Panel): @@ -111,6 +113,9 @@ class Viewer(wx.Panel):
111 self.repositioned_coronal_plan = 0 113 self.repositioned_coronal_plan = 0
112 self.added_actor = 0 114 self.added_actor = 0
113 115
  116 + self.camera_state = True
  117 +
  118 + self.ball_actor = None
114 self._mode_cross = False 119 self._mode_cross = False
115 self._to_show_ball = 0 120 self._to_show_ball = 0
116 self._ball_ref_visibility = False 121 self._ball_ref_visibility = False
@@ -153,6 +158,7 @@ class Viewer(wx.Panel): @@ -153,6 +158,7 @@ class Viewer(wx.Panel):
153 158
154 Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range') 159 Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range')
155 Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') 160 Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume')
  161 + Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state')
156 162
157 Publisher.subscribe(self.OnEnableStyle, 'Enable style') 163 Publisher.subscribe(self.OnEnableStyle, 'Enable style')
158 Publisher.subscribe(self.OnDisableStyle, 'Disable style') 164 Publisher.subscribe(self.OnDisableStyle, 'Disable style')
@@ -174,23 +180,24 @@ class Viewer(wx.Panel): @@ -174,23 +180,24 @@ class Viewer(wx.Panel):
174 Publisher.subscribe(self.OnStartSeed,'Create surface by seeding - start') 180 Publisher.subscribe(self.OnStartSeed,'Create surface by seeding - start')
175 Publisher.subscribe(self.OnEndSeed,'Create surface by seeding - end') 181 Publisher.subscribe(self.OnEndSeed,'Create surface by seeding - end')
176 182
177 - Publisher.subscribe(self.ActivateBallReference,  
178 - 'Activate ball reference')  
179 - Publisher.subscribe(self.DeactivateBallReference,  
180 - 'Deactivate ball reference')  
181 - Publisher.subscribe(self.SetBallReferencePosition,  
182 - 'Set ball reference position')  
183 - Publisher.subscribe(self.SetBallReferencePositionBasedOnBound,  
184 - 'Set ball reference position based on bound')  
185 Publisher.subscribe(self.SetStereoMode, 'Set stereo mode') 183 Publisher.subscribe(self.SetStereoMode, 'Set stereo mode')
186 184
187 Publisher.subscribe(self.Reposition3DPlane, 'Reposition 3D Plane') 185 Publisher.subscribe(self.Reposition3DPlane, 'Reposition 3D Plane')
188 186
189 Publisher.subscribe(self.RemoveVolume, 'Remove Volume') 187 Publisher.subscribe(self.RemoveVolume, 'Remove Volume')
190 188
  189 + Publisher.subscribe(self.SetBallReferencePosition,
  190 + 'Set ball reference position')
191 Publisher.subscribe(self._check_ball_reference, 'Enable style') 191 Publisher.subscribe(self._check_ball_reference, 'Enable style')
192 Publisher.subscribe(self._uncheck_ball_reference, 'Disable style') 192 Publisher.subscribe(self._uncheck_ball_reference, 'Disable style')
193 193
  194 + # Related to marker creation in navigation tools
  195 + Publisher.subscribe(self.AddMarker, 'Add marker')
  196 + Publisher.subscribe(self.HideAllMarkers, 'Hide all markers')
  197 + Publisher.subscribe(self.ShowAllMarkers, 'Show all markers')
  198 + Publisher.subscribe(self.RemoveAllMarkers, 'Remove all markers')
  199 + Publisher.subscribe(self.RemoveMarker, 'Remove marker')
  200 +
194 def SetStereoMode(self, pubsub_evt): 201 def SetStereoMode(self, pubsub_evt):
195 mode = pubsub_evt.data 202 mode = pubsub_evt.data
196 ren_win = self.interactor.GetRenderWindow() 203 ren_win = self.interactor.GetRenderWindow()
@@ -220,43 +227,6 @@ class Viewer(wx.Panel): @@ -220,43 +227,6 @@ class Viewer(wx.Panel):
220 227
221 self.interactor.Render() 228 self.interactor.Render()
222 229
223 - def CreateBallReference(self):  
224 - MRAD = 3.0  
225 - proj = prj.Project()  
226 - s = proj.spacing  
227 - # The sphere's radius will be MRAD times bigger than the media of the  
228 - # spacing values.  
229 - r = (s[0] + s[1] + s[2]) / 3.0 * MRAD  
230 - self.ball_reference = vtk.vtkSphereSource()  
231 - self.ball_reference.SetRadius(r)  
232 -  
233 - mapper = vtk.vtkPolyDataMapper()  
234 - mapper.SetInputConnection(self.ball_reference.GetOutputPort())  
235 -  
236 - p = vtk.vtkProperty()  
237 - p.SetColor(1, 0, 0)  
238 -  
239 - self.ball_actor = vtk.vtkActor()  
240 - self.ball_actor.SetMapper(mapper)  
241 - self.ball_actor.SetProperty(p)  
242 -  
243 - def RemoveBallReference(self):  
244 - self._ball_ref_visibility = False  
245 - if self.ball_reference:  
246 - self.ren.RemoveActor(self.ball_actor)  
247 -  
248 - def ActivateBallReference(self, pubsub_evt):  
249 - self._mode_cross = True  
250 - self._ball_ref_visibility = True  
251 - if self._to_show_ball:  
252 - if not self.ball_reference:  
253 - self.CreateBallReference()  
254 - self.ren.AddActor(self.ball_actor)  
255 -  
256 - def DeactivateBallReference(self, pubsub_evt):  
257 - self._mode_cross = False  
258 - self.RemoveBallReference()  
259 -  
260 def _check_ball_reference(self, pubsub_evt): 230 def _check_ball_reference(self, pubsub_evt):
261 st = pubsub_evt.data 231 st = pubsub_evt.data
262 if st == const.SLICE_STATE_CROSS: 232 if st == const.SLICE_STATE_CROSS:
@@ -279,19 +249,6 @@ class Viewer(wx.Panel): @@ -279,19 +249,6 @@ class Viewer(wx.Panel):
279 self._to_show_ball -= 1 249 self._to_show_ball -= 1
280 self._check_and_set_ball_visibility() 250 self._check_and_set_ball_visibility()
281 251
282 - def SetBallReferencePosition(self, pubsub_evt):  
283 - x, y, z = pubsub_evt.data  
284 - self.ball_reference.SetCenter(x, y, z)  
285 -  
286 - def SetBallReferencePositionBasedOnBound(self, pubsub_evt):  
287 - if self._to_show_ball:  
288 - self.ActivateBallReference(None)  
289 - coord = pubsub_evt.data  
290 - x, y, z = bases.FlipX(coord)  
291 - self.ball_reference.SetCenter(x, y, z)  
292 - else:  
293 - self.DeactivateBallReference(None)  
294 -  
295 def OnStartSeed(self, pubsub_evt): 252 def OnStartSeed(self, pubsub_evt):
296 index = pubsub_evt.data 253 index = pubsub_evt.data
297 self.seed_points = [] 254 self.seed_points = []
@@ -402,7 +359,6 @@ class Viewer(wx.Panel): @@ -402,7 +359,6 @@ class Viewer(wx.Panel):
402 actor.PickableOff() 359 actor.PickableOff()
403 360
404 self.ren.AddActor(actor) 361 self.ren.AddActor(actor)
405 -  
406 self.points_reference.append(actor) 362 self.points_reference.append(actor)
407 363
408 def RemoveAllPointsReference(self): 364 def RemoveAllPointsReference(self):
@@ -418,6 +374,113 @@ class Viewer(wx.Panel): @@ -418,6 +374,113 @@ class Viewer(wx.Panel):
418 actor = self.points_reference.pop(point) 374 actor = self.points_reference.pop(point)
419 self.ren.RemoveActor(actor) 375 self.ren.RemoveActor(actor)
420 376
  377 + def AddMarker(self, pubsub_evt):
  378 + """
  379 + Markers create by navigation tools and
  380 + rendered in volume viewer.
  381 + """
  382 + self.ball_id = pubsub_evt.data[0]
  383 + ballsize = pubsub_evt.data[1]
  384 + ballcolour = pubsub_evt.data[2]
  385 + coord = pubsub_evt.data[3]
  386 + x, y, z = bases.flip_x(coord)
  387 +
  388 + ball_ref = vtk.vtkSphereSource()
  389 + ball_ref.SetRadius(ballsize)
  390 + ball_ref.SetCenter(x, y, z)
  391 +
  392 + mapper = vtk.vtkPolyDataMapper()
  393 + mapper.SetInputConnection(ball_ref.GetOutputPort())
  394 +
  395 + prop = vtk.vtkProperty()
  396 + prop.SetColor(ballcolour)
  397 +
  398 + #adding a new actor for the present ball
  399 + self.staticballs.append(vtk.vtkActor())
  400 +
  401 + self.staticballs[self.ball_id].SetMapper(mapper)
  402 + self.staticballs[self.ball_id].SetProperty(prop)
  403 +
  404 + self.ren.AddActor(self.staticballs[self.ball_id])
  405 + self.ball_id = self.ball_id + 1
  406 + self.UpdateRender()
  407 +
  408 + def HideAllMarkers(self, pubsub_evt):
  409 + ballid = pubsub_evt.data
  410 + for i in range(0, ballid):
  411 + self.staticballs[i].SetVisibility(0)
  412 + self.UpdateRender()
  413 +
  414 + def ShowAllMarkers(self, pubsub_evt):
  415 + ballid = pubsub_evt.data
  416 + for i in range(0, ballid):
  417 + self.staticballs[i].SetVisibility(1)
  418 + self.UpdateRender()
  419 +
  420 + def RemoveAllMarkers(self, pubsub_evt):
  421 + ballid = pubsub_evt.data
  422 + for i in range(0, ballid):
  423 + self.ren.RemoveActor(self.staticballs[i])
  424 + self.staticballs = []
  425 + self.UpdateRender()
  426 +
  427 + def RemoveMarker(self, pubsub_evt):
  428 + index = pubsub_evt.data
  429 + self.ren.RemoveActor(self.staticballs[index])
  430 + del self.staticballs[index]
  431 + self.ball_id = self.ball_id - 1
  432 + self.UpdateRender()
  433 +
  434 + def CreateBallReference(self):
  435 + """
  436 + Red sphere on volume visualization to reference center of
  437 + cross in slice planes.
  438 + The sphere's radius will be scale times bigger than the average of
  439 + image spacing values.
  440 + """
  441 + scale = 3.0
  442 + proj = prj.Project()
  443 + s = proj.spacing
  444 + r = (s[0] + s[1] + s[2]) / 3.0 * scale
  445 +
  446 + ball_source = vtk.vtkSphereSource()
  447 + ball_source.SetRadius(r)
  448 +
  449 + mapper = vtk.vtkPolyDataMapper()
  450 + mapper.SetInputConnection(ball_source.GetOutputPort())
  451 +
  452 + self.ball_actor = vtk.vtkActor()
  453 + self.ball_actor.SetMapper(mapper)
  454 + self.ball_actor.GetProperty().SetColor(1, 0, 0)
  455 +
  456 + self.ren.AddActor(self.ball_actor)
  457 +
  458 + def ActivateBallReference(self):
  459 + self._mode_cross = True
  460 + self._ball_ref_visibility = True
  461 + if self._to_show_ball:
  462 + if not self.ball_actor:
  463 + self.CreateBallReference()
  464 +
  465 + def RemoveBallReference(self):
  466 + self._mode_cross = False
  467 + self._ball_ref_visibility = False
  468 + if self.ball_actor:
  469 + self.ren.RemoveActor(self.ball_actor)
  470 + self.ball_actor = None
  471 +
  472 + def SetBallReferencePosition(self, pubsub_evt):
  473 + if self._to_show_ball:
  474 + if not self.ball_actor:
  475 + self.ActivateBallReference()
  476 +
  477 + coord = pubsub_evt.data
  478 + x, y, z = bases.flip_x(coord)
  479 + self.ball_actor.SetPosition(x, y, z)
  480 +
  481 + else:
  482 + self.RemoveBallReference()
  483 +
421 def __bind_events_wx(self): 484 def __bind_events_wx(self):
422 #self.Bind(wx.EVT_SIZE, self.OnSize) 485 #self.Bind(wx.EVT_SIZE, self.OnSize)
423 pass 486 pass
@@ -580,30 +643,38 @@ class Viewer(wx.Panel): @@ -580,30 +643,38 @@ class Viewer(wx.Panel):
580 self.ren.ResetCamera() 643 self.ren.ResetCamera()
581 self.ren.ResetCameraClippingRange() 644 self.ren.ResetCameraClippingRange()
582 645
  646 + def SetVolumeCameraState(self, pubsub_evt):
  647 + self.camera_state = pubsub_evt.data
  648 +
583 def SetVolumeCamera(self, pubsub_evt): 649 def SetVolumeCamera(self, pubsub_evt):
584 -  
585 - coord_camera = pubsub_evt.data  
586 - coord_camera = numpy.array(bases.FlipX(coord_camera))  
587 -  
588 - cam = self.ren.GetActiveCamera()  
589 -  
590 - if self.initial_foco is None:  
591 - self.initial_foco = numpy.array(cam.GetFocalPoint())  
592 -  
593 - cam_initialposition = numpy.array(cam.GetPosition())  
594 - cam_initialfoco = numpy.array(cam.GetFocalPoint())  
595 -  
596 - cam_sub = cam_initialposition - cam_initialfoco  
597 - cam_sub_norm = numpy.linalg.norm(cam_sub)  
598 - vet1 = cam_sub/cam_sub_norm  
599 -  
600 - cam_sub_novo = coord_camera - self.initial_foco  
601 - cam_sub_novo_norm = numpy.linalg.norm(cam_sub_novo)  
602 - vet2 = cam_sub_novo/cam_sub_novo_norm  
603 - vet2 = vet2*cam_sub_norm + coord_camera  
604 -  
605 - cam.SetFocalPoint(coord_camera)  
606 - cam.SetPosition(vet2) 650 + if self.camera_state:
  651 + #TODO: exclude dependency on initial focus
  652 + cam_focus = np.array(bases.flip_x(pubsub_evt.data))
  653 + cam = self.ren.GetActiveCamera()
  654 +
  655 + if self.initial_focus is None:
  656 + self.initial_focus = np.array(cam.GetFocalPoint())
  657 +
  658 + cam_pos0 = np.array(cam.GetPosition())
  659 + cam_focus0 = np.array(cam.GetFocalPoint())
  660 +
  661 + v0 = cam_pos0 - cam_focus0
  662 + v0n = np.sqrt(inner1d(v0, v0))
  663 +
  664 + v1 = (cam_focus - self.initial_focus)
  665 + v1n = np.sqrt(inner1d(v1, v1))
  666 + if not v1n:
  667 + v1n = 1.0
  668 + cam_pos = (v1/v1n)*v0n + cam_focus
  669 +
  670 + cam.SetFocalPoint(cam_focus)
  671 + cam.SetPosition(cam_pos)
  672 +
  673 + # It works without doing the reset. Check with trackers if there is any difference.
  674 + # Need to be outside condition for sphere marker position update
  675 + # self.ren.ResetCameraClippingRange()
  676 + # self.ren.ResetCamera()
  677 + self.interactor.Render()
607 678
608 def OnExportSurface(self, pubsub_evt): 679 def OnExportSurface(self, pubsub_evt):
609 filename, filetype = pubsub_evt.data 680 filename, filetype = pubsub_evt.data
@@ -706,19 +777,18 @@ class Viewer(wx.Panel): @@ -706,19 +777,18 @@ class Viewer(wx.Panel):
706 self.interactor.Render() 777 self.interactor.Render()
707 self._to_show_ball -= 1 778 self._to_show_ball -= 1
708 self._check_and_set_ball_visibility() 779 self._check_and_set_ball_visibility()
709 - 780 +
710 def RemoveAllActor(self, pubsub_evt): 781 def RemoveAllActor(self, pubsub_evt):
711 utils.debug("RemoveAllActor") 782 utils.debug("RemoveAllActor")
712 self.ren.RemoveAllProps() 783 self.ren.RemoveAllProps()
713 Publisher.sendMessage('Render volume viewer') 784 Publisher.sendMessage('Render volume viewer')
714 785
715 -  
716 def LoadSlicePlane(self, pubsub_evt): 786 def LoadSlicePlane(self, pubsub_evt):
717 self.slice_plane = SlicePlane() 787 self.slice_plane = SlicePlane()
718 788
719 def LoadVolume(self, pubsub_evt): 789 def LoadVolume(self, pubsub_evt):
720 self.raycasting_volume = True 790 self.raycasting_volume = True
721 - #self._to_show_ball += 1 791 + self._to_show_ball += 1
722 792
723 volume = pubsub_evt.data[0] 793 volume = pubsub_evt.data[0]
724 colour = pubsub_evt.data[1] 794 colour = pubsub_evt.data[1]
@@ -895,14 +965,16 @@ class Viewer(wx.Panel): @@ -895,14 +965,16 @@ class Viewer(wx.Panel):
895 self.repositioned_coronal_plan = 1 965 self.repositioned_coronal_plan = 1
896 966
897 def _check_and_set_ball_visibility(self): 967 def _check_and_set_ball_visibility(self):
  968 + #TODO: When creating Raycasting volume and cross is pressed, it is not
  969 + # automatically creating the ball reference.
898 if self._mode_cross: 970 if self._mode_cross:
899 if self._to_show_ball > 0 and not self._ball_ref_visibility: 971 if self._to_show_ball > 0 and not self._ball_ref_visibility:
900 - self.ActivateBallReference(None) 972 + self.ActivateBallReference()
901 self.interactor.Render() 973 self.interactor.Render()
902 elif not self._to_show_ball and self._ball_ref_visibility: 974 elif not self._to_show_ball and self._ball_ref_visibility:
903 self.RemoveBallReference() 975 self.RemoveBallReference()
904 self.interactor.Render() 976 self.interactor.Render()
905 - 977 +
906 978
907 class SlicePlane: 979 class SlicePlane:
908 def __init__(self): 980 def __init__(self):
invesalius/gui/default_tasks.py
@@ -250,7 +250,8 @@ class UpperTaskPanel(wx.Panel): @@ -250,7 +250,8 @@ class UpperTaskPanel(wx.Panel):
250 tasks = [(_("Load data"), importer.TaskPanel), 250 tasks = [(_("Load data"), importer.TaskPanel),
251 (_("Select region of interest"), slice_.TaskPanel), 251 (_("Select region of interest"), slice_.TaskPanel),
252 (_("Configure 3D surface"), surface.TaskPanel), 252 (_("Configure 3D surface"), surface.TaskPanel),
253 - (_("Utilize navigation system"), navigator.TaskPanel)] 253 + (_("Export data"), exporter.TaskPanel),
  254 + (_("Navigation system"), navigator.TaskPanel)]
254 255
255 for i in xrange(len(tasks)): 256 for i in xrange(len(tasks)):
256 (name, panel) = tasks[i] 257 (name, panel) = tasks[i]
invesalius/gui/dialogs.py
@@ -22,8 +22,11 @@ import os @@ -22,8 +22,11 @@ import os
22 import random 22 import random
23 import sys 23 import sys
24 24
  25 +import vtk
25 import wx 26 import wx
26 import wx.combo 27 import wx.combo
  28 +
  29 +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
27 from wx.lib import masked 30 from wx.lib import masked
28 from wx.lib.agw import floatspin 31 from wx.lib.agw import floatspin
29 from wx.lib.wordwrap import wordwrap 32 from wx.lib.wordwrap import wordwrap
@@ -206,48 +209,27 @@ class ProgressDialog(object): @@ -206,48 +209,27 @@ class ProgressDialog(object):
206 self.dlg.Destroy() 209 self.dlg.Destroy()
207 210
208 211
209 -  
210 -#---------  
211 -WILDCARD_OPEN = "InVesalius 3 project (*.inv3)|*.inv3|"\ 212 +# ---------
  213 +WILDCARD_OPEN = "InVesalius 3 project (*.inv3)|*.inv3|" \
212 "All files (*.*)|*.*" 214 "All files (*.*)|*.*"
213 215
214 -WILDCARD_ANALYZE = "Analyze (*.hdr)|*.hdr|"\  
215 - "All files (*.*)|*.*" 216 +WILDCARD_ANALYZE = "Analyze 7.5 (*.hdr)|*.hdr|" \
  217 + "All files (*.*)|*.*"
216 218
217 -def ShowOpenProjectDialog():  
218 - # Default system path  
219 - current_dir = os.path.abspath(".")  
220 - dlg = wx.FileDialog(None, message=_("Open InVesalius 3 project..."),  
221 - defaultDir="",  
222 - defaultFile="", wildcard=WILDCARD_OPEN,  
223 - style=wx.FD_OPEN|wx.FD_CHANGE_DIR) 219 +WILDCARD_NIFTI = "NIfTI 1 (*.nii)|*.nii|" \
  220 + "Compressed NIfTI (*.nii.gz)|*.nii.gz|" \
  221 + "All files (*.*)|*.*"
224 222
225 - # inv3 filter is default  
226 - dlg.SetFilterIndex(0) 223 +WILDCARD_PARREC = "PAR/REC (*.par)|*.par|" \
  224 + "All files (*.*)|*.*"
227 225
228 - # Show the dialog and retrieve the user response. If it is the OK response,  
229 - # process the data.  
230 - filepath = None  
231 - try:  
232 - if dlg.ShowModal() == wx.ID_OK:  
233 - # This returns a Python list of files that were selected.  
234 - filepath = dlg.GetPath()  
235 - except(wx._core.PyAssertionError): #FIX: win64  
236 - filepath = dlg.GetPath()  
237 226
238 - # Destroy the dialog. Don't do this until you are done with it!  
239 - # BAD things can happen otherwise!  
240 - dlg.Destroy()  
241 - os.chdir(current_dir)  
242 - return filepath  
243 -  
244 -  
245 -def ShowOpenAnalyzeDialog(): 227 +def ShowOpenProjectDialog():
246 # Default system path 228 # Default system path
247 current_dir = os.path.abspath(".") 229 current_dir = os.path.abspath(".")
248 - dlg = wx.FileDialog(None, message=_("Open Analyze file"), 230 + dlg = wx.FileDialog(None, message=_("Open InVesalius 3 project..."),
249 defaultDir="", 231 defaultDir="",
250 - defaultFile="", wildcard=WILDCARD_ANALYZE, 232 + defaultFile="", wildcard=WILDCARD_OPEN,
251 style=wx.FD_OPEN|wx.FD_CHANGE_DIR) 233 style=wx.FD_OPEN|wx.FD_CHANGE_DIR)
252 234
253 # inv3 filter is default 235 # inv3 filter is default
@@ -260,7 +242,7 @@ def ShowOpenAnalyzeDialog(): @@ -260,7 +242,7 @@ def ShowOpenAnalyzeDialog():
260 if dlg.ShowModal() == wx.ID_OK: 242 if dlg.ShowModal() == wx.ID_OK:
261 # This returns a Python list of files that were selected. 243 # This returns a Python list of files that were selected.
262 filepath = dlg.GetPath() 244 filepath = dlg.GetPath()
263 - except(wx._core.PyAssertionError): #FIX: win64 245 + except(wx._core.PyAssertionError): # FIX: win64
264 filepath = dlg.GetPath() 246 filepath = dlg.GetPath()
265 247
266 # Destroy the dialog. Don't do this until you are done with it! 248 # Destroy the dialog. Don't do this until you are done with it!
@@ -353,6 +335,47 @@ def ShowImportBitmapDirDialog(): @@ -353,6 +335,47 @@ def ShowImportBitmapDirDialog():
353 return path 335 return path
354 336
355 337
  338 +def ShowImportOtherFilesDialog(id_type):
  339 + # Default system path
  340 + current_dir = os.path.abspath(".")
  341 + dlg = wx.FileDialog(None, message=_("Import Analyze 7.5 file"),
  342 + defaultDir="",
  343 + defaultFile="", wildcard=WILDCARD_ANALYZE,
  344 + style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
  345 +
  346 + if id_type == const.ID_NIFTI_IMPORT:
  347 + dlg.SetMessage(_("Import NIFTi 1 file"))
  348 + dlg.SetWildcard(WILDCARD_NIFTI)
  349 + elif id_type == const.ID_PARREC_IMPORT:
  350 + dlg.SetMessage(_("Import PAR/REC file"))
  351 + dlg.SetWildcard(WILDCARD_PARREC)
  352 +
  353 + # inv3 filter is default
  354 + dlg.SetFilterIndex(0)
  355 +
  356 + # Show the dialog and retrieve the user response. If it is the OK response,
  357 + # process the data.
  358 + filename = None
  359 + try:
  360 + if dlg.ShowModal() == wx.ID_OK:
  361 + # GetPath returns in unicode, if a path has non-ascii characters a
  362 + # UnicodeEncodeError is raised. To avoid this, path is encoded in utf-8
  363 + if sys.platform == "win32":
  364 + filename = dlg.GetPath()
  365 + else:
  366 + filename = dlg.GetPath().encode('utf-8')
  367 +
  368 + except(wx._core.PyAssertionError): # TODO: error win64
  369 + if (dlg.GetPath()):
  370 + filename = dlg.GetPath()
  371 +
  372 + # Destroy the dialog. Don't do this until you are done with it!
  373 + # BAD things can happen otherwise!
  374 + dlg.Destroy()
  375 + os.chdir(current_dir)
  376 + return filename
  377 +
  378 +
356 def ShowSaveAsProjectDialog(default_filename=None): 379 def ShowSaveAsProjectDialog(default_filename=None):
357 current_dir = os.path.abspath(".") 380 current_dir = os.path.abspath(".")
358 dlg = wx.FileDialog(None, 381 dlg = wx.FileDialog(None,
@@ -380,10 +403,70 @@ def ShowSaveAsProjectDialog(default_filename=None): @@ -380,10 +403,70 @@ def ShowSaveAsProjectDialog(default_filename=None):
380 if filename.split(".")[-1] != extension: 403 if filename.split(".")[-1] != extension:
381 filename = filename + "." + extension 404 filename = filename + "." + extension
382 405
  406 + os.chdir(current_dir)
  407 + return filename
  408 +
  409 +
  410 +# Dialog for neuronavigation markers
  411 +def ShowSaveMarkersDialog(default_filename=None):
  412 + current_dir = os.path.abspath(".")
  413 + dlg = wx.FileDialog(None,
  414 + _("Save markers as..."), # title
  415 + "", # last used directory
  416 + default_filename,
  417 + _("Markers (*.txt)|*.txt"),
  418 + wx.SAVE | wx.OVERWRITE_PROMPT)
  419 + # dlg.SetFilterIndex(0) # default is VTI
  420 +
  421 + filename = None
  422 + try:
  423 + if dlg.ShowModal() == wx.ID_OK:
  424 + filename = dlg.GetPath()
  425 + ok = 1
  426 + else:
  427 + ok = 0
  428 + except(wx._core.PyAssertionError): # TODO: fix win64
  429 + filename = dlg.GetPath()
  430 + ok = 1
  431 +
  432 + if (ok):
  433 + extension = "txt"
  434 + if sys.platform != 'win32':
  435 + if filename.split(".")[-1] != extension:
  436 + filename = filename + "." + extension
383 437
384 os.chdir(current_dir) 438 os.chdir(current_dir)
385 return filename 439 return filename
386 440
  441 +
  442 +def ShowLoadMarkersDialog():
  443 + current_dir = os.path.abspath(".")
  444 +
  445 + dlg = wx.FileDialog(None, message=_("Load markers"),
  446 + defaultDir="",
  447 + defaultFile="",
  448 + style=wx.OPEN|wx.CHANGE_DIR)
  449 +
  450 + # inv3 filter is default
  451 + dlg.SetFilterIndex(0)
  452 +
  453 + # Show the dialog and retrieve the user response. If it is the OK response,
  454 + # process the data.
  455 + filepath = None
  456 + try:
  457 + if dlg.ShowModal() == wx.ID_OK:
  458 + # This returns a Python list of files that were selected.
  459 + filepath = dlg.GetPath()
  460 + except(wx._core.PyAssertionError): # FIX: win64
  461 + filepath = dlg.GetPath()
  462 +
  463 + # Destroy the dialog. Don't do this until you are done with it!
  464 + # BAD things can happen otherwise!
  465 + dlg.Destroy()
  466 + os.chdir(current_dir)
  467 + return filepath
  468 +
  469 +
387 class MessageDialog(wx.Dialog): 470 class MessageDialog(wx.Dialog):
388 def __init__(self, message): 471 def __init__(self, message):
389 pre = wx.PreDialog() 472 pre = wx.PreDialog()
@@ -509,6 +592,7 @@ def ImportEmptyDirectory(dirpath): @@ -509,6 +592,7 @@ def ImportEmptyDirectory(dirpath):
509 dlg.ShowModal() 592 dlg.ShowModal()
510 dlg.Destroy() 593 dlg.Destroy()
511 594
  595 +
512 def ImportInvalidFiles(ftype="DICOM"): 596 def ImportInvalidFiles(ftype="DICOM"):
513 if ftype == "Bitmap": 597 if ftype == "Bitmap":
514 msg = _("There are no Bitmap, JPEG, PNG or TIFF files in the selected folder.") 598 msg = _("There are no Bitmap, JPEG, PNG or TIFF files in the selected folder.")
@@ -524,6 +608,20 @@ def ImportInvalidFiles(ftype=&quot;DICOM&quot;): @@ -524,6 +608,20 @@ def ImportInvalidFiles(ftype=&quot;DICOM&quot;):
524 dlg.ShowModal() 608 dlg.ShowModal()
525 dlg.Destroy() 609 dlg.Destroy()
526 610
  611 +
  612 +def ImportAnalyzeWarning():
  613 + msg1 = _("Warning! InVesalius has limited support to Analyze format.\n")
  614 + msg2 = _("Slices may be wrongly oriented and functions may not work properly.")
  615 + if sys.platform == 'darwin':
  616 + dlg = wx.MessageDialog(None, "", msg1 + msg2,
  617 + wx.ICON_INFORMATION | wx.OK)
  618 + else:
  619 + dlg = wx.MessageDialog(None, msg1 + msg2, "InVesalius 3",
  620 + wx.ICON_INFORMATION | wx.OK)
  621 + dlg.ShowModal()
  622 + dlg.Destroy()
  623 +
  624 +
527 def InexistentMask(): 625 def InexistentMask():
528 msg = _("A mask is needed to create a surface.") 626 msg = _("A mask is needed to create a surface.")
529 if sys.platform == 'darwin': 627 if sys.platform == 'darwin':
@@ -593,6 +691,86 @@ def SurfaceSelectionRequiredForDuplication(): @@ -593,6 +691,86 @@ def SurfaceSelectionRequiredForDuplication():
593 dlg.ShowModal() 691 dlg.ShowModal()
594 dlg.Destroy() 692 dlg.Destroy()
595 693
  694 +
  695 +# Dialogs for neuronavigation mode
  696 +def InvalidFiducials():
  697 + msg = _("Fiducials are invalid. Select six coordinates.")
  698 + if sys.platform == 'darwin':
  699 + dlg = wx.MessageDialog(None, "", msg,
  700 + wx.ICON_INFORMATION | wx.OK)
  701 + else:
  702 + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator",
  703 + wx.ICON_INFORMATION | wx.OK)
  704 + dlg.ShowModal()
  705 + dlg.Destroy()
  706 +
  707 +
  708 +def NavigationTrackerWarning(trck_id, lib_mode):
  709 + """
  710 + Spatial Tracker connection error
  711 + """
  712 + trck = {1: 'Claron MicronTracker',
  713 + 2: 'Polhemus FASTRAK',
  714 + 3: 'Polhemus ISOTRAK',
  715 + 4: 'Polhemus PATRIOT',
  716 + 5: 'Debug tracker device'}
  717 +
  718 + if lib_mode == 'choose':
  719 + msg = _('No tracking device selected')
  720 + elif lib_mode == 'error':
  721 + msg = trck[trck_id] + _(' is not installed.')
  722 + elif lib_mode == 'disconnect':
  723 + msg = trck[trck_id] + _(' disconnected.')
  724 + else:
  725 + msg = trck[trck_id] + _(' is not connected.')
  726 +
  727 + if sys.platform == 'darwin':
  728 + dlg = wx.MessageDialog(None, "", msg,
  729 + wx.ICON_INFORMATION | wx.OK)
  730 + else:
  731 + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator",
  732 + wx.ICON_INFORMATION | wx.OK)
  733 +
  734 + dlg.ShowModal()
  735 + dlg.Destroy()
  736 +
  737 +
  738 +def InvalidMarkersFile():
  739 + msg = _("The TXT file is invalid.")
  740 + if sys.platform == 'darwin':
  741 + dlg = wx.MessageDialog(None, "", msg,
  742 + wx.ICON_INFORMATION | wx.OK)
  743 + else:
  744 + dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator",
  745 + wx.ICON_INFORMATION | wx.OK)
  746 + dlg.ShowModal()
  747 + dlg.Destroy()
  748 +
  749 +
  750 +def NoMarkerSelected():
  751 + msg = _("No data selected")
  752 + if sys.platform == 'darwin':
  753 + dlg = wx.MessageDialog(None, "", msg,
  754 + wx.ICON_INFORMATION | wx.OK)
  755 + else:
  756 + dlg = wx.MessageDialog(None,msg, "InVesalius 3 - Neuronavigator",
  757 + wx.ICON_INFORMATION | wx.OK)
  758 + dlg.ShowModal()
  759 + dlg.Destroy()
  760 +
  761 +
  762 +def EnterMarkerID(default):
  763 + msg = _("Edit marker ID")
  764 + if sys.platform == 'darwin':
  765 + dlg = wx.TextEntryDialog(None, "", msg, defaultValue=default)
  766 + else:
  767 + dlg = wx.TextEntryDialog(None, msg, "InVesalius 3", defaultValue=default)
  768 + dlg.ShowModal()
  769 + result = dlg.GetValue()
  770 + dlg.Destroy()
  771 + return result
  772 +
  773 +
596 class NewMask(wx.Dialog): 774 class NewMask(wx.Dialog):
597 def __init__(self, 775 def __init__(self,
598 parent=None, 776 parent=None,
@@ -828,6 +1006,8 @@ def ShowAboutDialog(parent): @@ -828,6 +1006,8 @@ def ShowAboutDialog(parent):
828 info.Developers = ["Paulo Henrique Junqueira Amorim", 1006 info.Developers = ["Paulo Henrique Junqueira Amorim",
829 "Thiago Franco de Moraes", 1007 "Thiago Franco de Moraes",
830 "Jorge Vicente Lopes da Silva", 1008 "Jorge Vicente Lopes da Silva",
  1009 + "Victor Hugo de Oliveira e Souza (navigator)",
  1010 + "Renan Hiroshi Matsuda (navigator)"
831 "Tatiana Al-Chueyr (former)", 1011 "Tatiana Al-Chueyr (former)",
832 "Guilherme Cesar Soares Ruppert (former)", 1012 "Guilherme Cesar Soares Ruppert (former)",
833 "Fabio de Souza Azevedo (former)", 1013 "Fabio de Souza Azevedo (former)",
invesalius/gui/frame.py
@@ -396,7 +396,11 @@ class Frame(wx.Frame): @@ -396,7 +396,11 @@ class Frame(wx.Frame):
396 elif id == const.ID_PROJECT_OPEN: 396 elif id == const.ID_PROJECT_OPEN:
397 self.ShowOpenProject() 397 self.ShowOpenProject()
398 elif id == const.ID_ANALYZE_IMPORT: 398 elif id == const.ID_ANALYZE_IMPORT:
399 - self.ShowAnalyzeImporter() 399 + self.ShowImportOtherFiles(id)
  400 + elif id == const.ID_NIFTI_IMPORT:
  401 + self.ShowImportOtherFiles(id)
  402 + elif id == const.ID_PARREC_IMPORT:
  403 + self.ShowImportOtherFiles(id)
400 elif id == const.ID_TIFF_JPG_PNG: 404 elif id == const.ID_TIFF_JPG_PNG:
401 self.ShowBitmapImporter() 405 self.ShowBitmapImporter()
402 elif id == const.ID_PROJECT_SAVE: 406 elif id == const.ID_PROJECT_SAVE:
@@ -538,6 +542,12 @@ class Frame(wx.Frame): @@ -538,6 +542,12 @@ class Frame(wx.Frame):
538 Show import DICOM panel. as dicom """ 542 Show import DICOM panel. as dicom """
539 Publisher.sendMessage('Show import directory dialog') 543 Publisher.sendMessage('Show import directory dialog')
540 544
  545 + def ShowImportOtherFiles(self, id_file):
  546 + """
  547 + Show import Analyze, NiFTI1 or PAR/REC dialog.
  548 + """
  549 + Publisher.sendMessage('Show import other files dialog', id_file)
  550 +
541 def ShowRetrieveDicomPanel(self): 551 def ShowRetrieveDicomPanel(self):
542 Publisher.sendMessage('Show retrieve dicom panel') 552 Publisher.sendMessage('Show retrieve dicom panel')
543 553
@@ -553,12 +563,6 @@ class Frame(wx.Frame): @@ -553,12 +563,6 @@ class Frame(wx.Frame):
553 """ 563 """
554 Publisher.sendMessage('Show save dialog', True) 564 Publisher.sendMessage('Show save dialog', True)
555 565
556 - def ShowAnalyzeImporter(self):  
557 - """  
558 - Show save as dialog.  
559 - """  
560 - Publisher.sendMessage('Show analyze dialog', True)  
561 -  
562 def ShowBitmapImporter(self): 566 def ShowBitmapImporter(self):
563 """ 567 """
564 Tiff, BMP, JPEG and PNG 568 Tiff, BMP, JPEG and PNG
@@ -672,7 +676,9 @@ class MenuBar(wx.MenuBar): @@ -672,7 +676,9 @@ class MenuBar(wx.MenuBar):
672 676
673 #Import Others Files 677 #Import Others Files
674 others_file_menu = wx.Menu() 678 others_file_menu = wx.Menu()
675 - others_file_menu.Append(const.ID_ANALYZE_IMPORT, "Analyze") 679 + others_file_menu.Append(const.ID_ANALYZE_IMPORT, _("Analyze 7.5"))
  680 + others_file_menu.Append(const.ID_NIFTI_IMPORT, _("NIfTI 1"))
  681 + others_file_menu.Append(const.ID_PARREC_IMPORT, _("PAR/REC"))
676 others_file_menu.Append(const.ID_TIFF_JPG_PNG, u"TIFF,BMP,JPG or PNG (\xb5CT)") 682 others_file_menu.Append(const.ID_TIFF_JPG_PNG, u"TIFF,BMP,JPG or PNG (\xb5CT)")
677 683
678 # FILE 684 # FILE
invesalius/gui/task_importer.py
@@ -64,8 +64,8 @@ class InnerTaskPanel(wx.Panel): @@ -64,8 +64,8 @@ class InnerTaskPanel(wx.Panel):
64 self.float_hyper_list = [] 64 self.float_hyper_list = []
65 65
66 # Fixed hyperlink items 66 # Fixed hyperlink items
67 - tooltip = wx.ToolTip(_("Select DICOM files to be reconstructed"))  
68 - link_import_local = hl.HyperLinkCtrl(self, -1, _("Import DICOM images...")) 67 + tooltip = wx.ToolTip(_("Select DICOM, Analyze, NIfTI or REC/PAR files to be reconstructed"))
  68 + link_import_local = hl.HyperLinkCtrl(self, -1, _("Import medical images..."))
69 link_import_local.SetUnderlines(False, False, False) 69 link_import_local.SetUnderlines(False, False, False)
70 link_import_local.SetBold(True) 70 link_import_local.SetBold(True)
71 link_import_local.SetColours("BLACK", "BLACK", "BLACK") 71 link_import_local.SetColours("BLACK", "BLACK", "BLACK")
invesalius/gui/task_navigator.py
@@ -17,433 +17,727 @@ @@ -17,433 +17,727 @@
17 # detalhes. 17 # detalhes.
18 #-------------------------------------------------------------------------- 18 #--------------------------------------------------------------------------
19 19
20 -import os 20 +from functools import partial
21 import sys 21 import sys
22 22
23 -import serial 23 +import numpy as np
24 import wx 24 import wx
25 import wx.lib.hyperlink as hl 25 import wx.lib.hyperlink as hl
26 import wx.lib.masked.numctrl 26 import wx.lib.masked.numctrl
27 -import wx.lib.platebtn as pbtn  
28 from wx.lib.pubsub import pub as Publisher 27 from wx.lib.pubsub import pub as Publisher
29 28
  29 +import invesalius.constants as const
30 import invesalius.data.bases as db 30 import invesalius.data.bases as db
31 -import invesalius.data.co_registration as dcr  
32 -import invesalius.project as project  
33 -IR1 = wx.NewId()  
34 -IR2 = wx.NewId()  
35 -IR3 = wx.NewId()  
36 -PR1 = wx.NewId()  
37 -PR2 = wx.NewId()  
38 -PR3 = wx.NewId()  
39 -Neuronavigate = wx.NewId()  
40 -Corregistration = wx.NewId()  
41 -GetPoint = wx.NewId() 31 +import invesalius.data.coordinates as dco
  32 +import invesalius.data.coregistration as dcr
  33 +import invesalius.data.trackers as dt
  34 +import invesalius.data.trigger as trig
  35 +import invesalius.gui.dialogs as dlg
  36 +import invesalius.gui.widgets.foldpanelbar as fpb
  37 +import invesalius.gui.widgets.colourselect as csel
  38 +
42 39
43 class TaskPanel(wx.Panel): 40 class TaskPanel(wx.Panel):
44 - """  
45 - This panel works as a "frame", drawing a white margin arround  
46 - the panel that really matters (InnerTaskPanel).  
47 - """  
48 def __init__(self, parent): 41 def __init__(self, parent):
49 - # note: don't change this class!!!  
50 wx.Panel.__init__(self, parent) 42 wx.Panel.__init__(self, parent)
51 43
52 inner_panel = InnerTaskPanel(self) 44 inner_panel = InnerTaskPanel(self)
53 45
54 sizer = wx.BoxSizer(wx.HORIZONTAL) 46 sizer = wx.BoxSizer(wx.HORIZONTAL)
55 - sizer.Add(inner_panel, 1, wx.EXPAND | wx.GROW | wx.BOTTOM | wx.RIGHT |  
56 - wx.LEFT, 8) 47 + sizer.Add(inner_panel, 1, wx.EXPAND|wx.GROW|wx.BOTTOM|wx.RIGHT |
  48 + wx.LEFT, 7)
57 sizer.Fit(self) 49 sizer.Fit(self)
58 50
59 self.SetSizer(sizer) 51 self.SetSizer(sizer)
60 self.Update() 52 self.Update()
61 self.SetAutoLayout(1) 53 self.SetAutoLayout(1)
62 54
  55 +
63 class InnerTaskPanel(wx.Panel): 56 class InnerTaskPanel(wx.Panel):
  57 + def __init__(self, parent):
  58 + wx.Panel.__init__(self, parent)
  59 + default_colour = self.GetBackgroundColour()
  60 + background_colour = wx.Colour(255,255,255)
  61 + self.SetBackgroundColour(background_colour)
  62 +
  63 + txt_nav = wx.StaticText(self, -1, _('Select fiducials and navigate'),
  64 + size=wx.Size(90, 20))
  65 + txt_nav.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.BOLD))
  66 +
  67 + # Create horizontal sizer to represent lines in the panel
  68 + txt_sizer = wx.BoxSizer(wx.HORIZONTAL)
  69 + txt_sizer.Add(txt_nav, 1, wx.EXPAND|wx.GROW, 5)
  70 +
  71 + # Fold panel which contains navigation configurations
  72 + fold_panel = FoldPanel(self)
  73 + fold_panel.SetBackgroundColour(default_colour)
  74 +
  75 +
  76 + # Add line sizer into main sizer
  77 + main_sizer = wx.BoxSizer(wx.VERTICAL)
  78 + main_sizer.Add(txt_sizer, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
  79 + main_sizer.Add(fold_panel, 1, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
  80 + main_sizer.AddSpacer(5)
  81 + main_sizer.Fit(self)
  82 +
  83 + self.SetSizerAndFit(main_sizer)
  84 + self.Update()
  85 + self.SetAutoLayout(1)
64 86
  87 + self.sizer = main_sizer
  88 +
  89 +
  90 +class FoldPanel(wx.Panel):
65 def __init__(self, parent): 91 def __init__(self, parent):
66 - wx.Panel.__init__(self, parent, size=wx.Size(320,300))  
67 - self.SetBackgroundColour(wx.Colour(221, 221, 221, 255)) 92 + wx.Panel.__init__(self, parent)
  93 +
  94 + inner_panel = InnerFoldPanel(self)
  95 +
  96 + sizer = wx.BoxSizer(wx.VERTICAL)
  97 + sizer.Add(inner_panel, 0, wx.EXPAND|wx.GROW)
  98 + sizer.Fit(self)
  99 +
  100 + self.SetSizerAndFit(sizer)
  101 + self.Update()
68 self.SetAutoLayout(1) 102 self.SetAutoLayout(1)
69 - self.__bind_events()  
70 103
71 - self.aux_img_ref1 = 0  
72 - self.aux_img_ref2 = 0  
73 - self.aux_img_ref3 = 0  
74 - self.flagpoint = 0  
75 - self.aux_plh_ref1 = 1  
76 - self.aux_plh_ref2 = 1  
77 - self.aux_plh_ref3 = 1  
78 - self.a = 0, 0, 0  
79 - self.coord1a = (0, 0, 0)  
80 - self.coord2a = (0, 0, 0)  
81 - self.coord3a = (0, 0, 0)  
82 - self.coord1b = (0, 0, 0)  
83 - self.coord2b = (0, 0, 0)  
84 - self.coord3b = (0, 0, 0)  
85 - self.correg = None  
86 -  
87 104
88 - self.button_img_ref1 = wx.ToggleButton(self, IR1, label = 'TEI', size = wx.Size(30,23))  
89 - self.button_img_ref1.Bind(wx.EVT_TOGGLEBUTTON, self.Img_Ref_ToggleButton1)  
90 -  
91 - self.button_img_ref2 = wx.ToggleButton(self, IR2, label = 'TDI', size = wx.Size(30,23))  
92 - self.button_img_ref2.Bind(wx.EVT_TOGGLEBUTTON, self.Img_Ref_ToggleButton2)  
93 -  
94 - self.button_img_ref3 = wx.ToggleButton(self, IR3, label = 'FNI', size = wx.Size(30,23))  
95 - self.button_img_ref3.Bind(wx.EVT_TOGGLEBUTTON, self.Img_Ref_ToggleButton3)  
96 -  
97 - self.button_plh_ref1 = wx.Button(self, PR1, label = 'TEP', size = wx.Size(30,23))  
98 - self.button_plh_ref2 = wx.Button(self, PR2, label = 'TDP', size = wx.Size(30,23))  
99 - self.button_plh_ref3 = wx.Button(self, PR3, label = 'FNP', size = wx.Size(30,23))  
100 - self.button_crg = wx.Button(self, Corregistration, label = 'Corregistrate')  
101 - self.button_getpoint = wx.Button(self, GetPoint, label = 'GP', size = wx.Size(23,23))  
102 - self.Bind(wx.EVT_BUTTON, self.Buttons)  
103 -  
104 - self.button_neuronavigate = wx.ToggleButton(self, Neuronavigate, "Neuronavigate")  
105 - self.button_neuronavigate.Bind(wx.EVT_TOGGLEBUTTON, self.Neuronavigate_ToggleButton)  
106 -  
107 - self.numCtrl1a = wx.lib.masked.numctrl.NumCtrl(  
108 - name='numCtrl1a', parent=self, integerWidth = 4, fractionWidth = 1)  
109 - self.numCtrl2a = wx.lib.masked.numctrl.NumCtrl(  
110 - name='numCtrl2a', parent=self, integerWidth = 4, fractionWidth = 1)  
111 - self.numCtrl3a = wx.lib.masked.numctrl.NumCtrl(  
112 - name='numCtrl3a', parent=self, integerWidth = 4, fractionWidth = 1)  
113 - self.numCtrl1b = wx.lib.masked.numctrl.NumCtrl(  
114 - name='numCtrl1b', parent=self, integerWidth = 4, fractionWidth = 1)  
115 - self.numCtrl2b = wx.lib.masked.numctrl.NumCtrl(  
116 - name='numCtrl2b', parent=self, integerWidth = 4, fractionWidth = 1)  
117 - self.numCtrl3b = wx.lib.masked.numctrl.NumCtrl(  
118 - name='numCtrl3b', parent=self, integerWidth = 4, fractionWidth = 1)  
119 - self.numCtrl1c = wx.lib.masked.numctrl.NumCtrl(  
120 - name='numCtrl1c', parent=self, integerWidth = 4, fractionWidth = 1)  
121 - self.numCtrl2c = wx.lib.masked.numctrl.NumCtrl(  
122 - name='numCtrl2c', parent=self, integerWidth = 4, fractionWidth = 1)  
123 - self.numCtrl3c = wx.lib.masked.numctrl.NumCtrl(  
124 - name='numCtrl3c', parent=self, integerWidth = 4, fractionWidth = 1)  
125 - self.numCtrl1d = wx.lib.masked.numctrl.NumCtrl(  
126 - name='numCtrl1d', parent=self, integerWidth = 4, fractionWidth = 1)  
127 - self.numCtrl2d = wx.lib.masked.numctrl.NumCtrl(  
128 - name='numCtrl2d', parent=self, integerWidth = 4, fractionWidth = 1)  
129 - self.numCtrl3d = wx.lib.masked.numctrl.NumCtrl(  
130 - name='numCtrl3d', parent=self, integerWidth = 4, fractionWidth = 1)  
131 - self.numCtrl1e = wx.lib.masked.numctrl.NumCtrl(  
132 - name='numCtrl1e', parent=self, integerWidth = 4, fractionWidth = 1)  
133 - self.numCtrl2e = wx.lib.masked.numctrl.NumCtrl(  
134 - name='numCtrl2e', parent=self, integerWidth = 4, fractionWidth = 1)  
135 - self.numCtrl3e = wx.lib.masked.numctrl.NumCtrl(  
136 - name='numCtrl3e', parent=self, integerWidth = 4, fractionWidth = 1)  
137 - self.numCtrl1f = wx.lib.masked.numctrl.NumCtrl(  
138 - name='numCtrl1f', parent=self, integerWidth = 4, fractionWidth = 1)  
139 - self.numCtrl2f = wx.lib.masked.numctrl.NumCtrl(  
140 - name='numCtrl2f', parent=self, integerWidth = 4, fractionWidth = 1)  
141 - self.numCtrl3f = wx.lib.masked.numctrl.NumCtrl(  
142 - name='numCtrl3f', parent=self, integerWidth = 4, fractionWidth = 1)  
143 - self.numCtrl1g = wx.lib.masked.numctrl.NumCtrl(  
144 - name='numCtrl1g', parent=self, integerWidth = 4, fractionWidth = 1)  
145 - self.numCtrl2g = wx.lib.masked.numctrl.NumCtrl(  
146 - name='numCtrl2g', parent=self, integerWidth = 4, fractionWidth = 1)  
147 - self.numCtrl3g = wx.lib.masked.numctrl.NumCtrl(  
148 - name='numCtrl3g', parent=self, integerWidth = 4, fractionWidth = 1)  
149 -  
150 - RefImg_sizer1 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
151 - RefImg_sizer1.AddMany([ (self.button_img_ref1),  
152 - (self.numCtrl1a),  
153 - (self.numCtrl2a),  
154 - (self.numCtrl3a)])  
155 -  
156 - RefImg_sizer2 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
157 - RefImg_sizer2.AddMany([ (self.button_img_ref2),  
158 - (self.numCtrl1b),  
159 - (self.numCtrl2b),  
160 - (self.numCtrl3b)])  
161 -  
162 - RefImg_sizer3 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
163 - RefImg_sizer3.AddMany([ (self.button_img_ref3),  
164 - (self.numCtrl1c),  
165 - (self.numCtrl2c),  
166 - (self.numCtrl3c)])  
167 -  
168 - RefPlh_sizer1 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
169 - RefPlh_sizer1.AddMany([ (self.button_plh_ref1, 0, wx.GROW|wx.EXPAND),  
170 - (self.numCtrl1d, wx.RIGHT),  
171 - (self.numCtrl2d),  
172 - (self.numCtrl3d, wx.LEFT)])  
173 -  
174 - RefPlh_sizer2 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
175 - RefPlh_sizer2.AddMany([ (self.button_plh_ref2, 0, wx.GROW|wx.EXPAND),  
176 - (self.numCtrl1e, 0, wx.RIGHT),  
177 - (self.numCtrl2e),  
178 - (self.numCtrl3e, 0, wx.LEFT)])  
179 -  
180 - RefPlh_sizer3 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
181 - RefPlh_sizer3.AddMany([ (self.button_plh_ref3, 0, wx.GROW|wx.EXPAND),  
182 - (self.numCtrl1f, wx.RIGHT),  
183 - (self.numCtrl2f),  
184 - (self.numCtrl3f, wx.LEFT)])  
185 -  
186 - Buttons_sizer4 = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5)  
187 - Buttons_sizer4.AddMany([ (self.button_crg, wx.RIGHT),  
188 - (self.button_neuronavigate, wx.LEFT)])  
189 -  
190 - GetPoint_sizer5 = wx.FlexGridSizer(rows=1, cols=4, hgap=5, vgap=5)  
191 - GetPoint_sizer5.AddMany([ (self.button_getpoint, 0, wx.GROW|wx.EXPAND),  
192 - (self.numCtrl1g, wx.RIGHT),  
193 - (self.numCtrl2g),  
194 - (self.numCtrl3g, wx.LEFT)])  
195 -  
196 - text = wx.StaticText(self, -1, 'Neuronavigator') 105 +class InnerFoldPanel(wx.Panel):
  106 + def __init__(self, parent):
  107 + wx.Panel.__init__(self, parent)
  108 + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR)
  109 + self.SetBackgroundColour(default_colour)
  110 +
  111 + # Fold panel and its style settings
  112 + # FIXME: If we dont insert a value in size or if we set wx.DefaultSize,
  113 + # the fold_panel doesnt show. This means that, for some reason, Sizer
  114 + # is not working properly in this panel. It might be on some child or
  115 + # parent panel. Perhaps we need to insert the item into the sizer also...
  116 + # Study this.
  117 +
  118 + fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition,
  119 + (10, 293), 0, fpb.FPB_SINGLE_FOLD)
  120 +
  121 + # Fold panel style
  122 + style = fpb.CaptionBarStyle()
  123 + style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V)
  124 + style.SetFirstColour(default_colour)
  125 + style.SetSecondColour(default_colour)
  126 +
  127 + # Fold 1 - Navigation panel
  128 + item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True)
  129 + ntw = NeuronavigationPanel(item)
  130 +
  131 + fold_panel.ApplyCaptionStyle(item, style)
  132 + fold_panel.AddFoldPanelWindow(item, ntw, Spacing= 0,
  133 + leftSpacing=0, rightSpacing=0)
  134 + fold_panel.Expand(fold_panel.GetFoldPanel(0))
  135 +
  136 + # Fold 2 - Markers panel
  137 + item = fold_panel.AddFoldPanel(_("Extra tools"), collapsed=True)
  138 + mtw = MarkersPanel(item)
  139 +
  140 + fold_panel.ApplyCaptionStyle(item, style)
  141 + fold_panel.AddFoldPanelWindow(item, mtw, Spacing= 0,
  142 + leftSpacing=0, rightSpacing=0)
  143 +
197 144
198 - Ref_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5)  
199 - Ref_sizer.AddGrowableCol(0, 1)  
200 - Ref_sizer.AddGrowableRow(0, 1)  
201 - Ref_sizer.AddGrowableRow(1, 1)  
202 - Ref_sizer.AddGrowableRow(2, 1)  
203 - Ref_sizer.AddGrowableRow(3, 1)  
204 - Ref_sizer.AddGrowableRow(4, 1)  
205 - Ref_sizer.AddGrowableRow(5, 1)  
206 - Ref_sizer.AddGrowableRow(6, 1)  
207 - Ref_sizer.AddGrowableRow(7, 1)  
208 - Ref_sizer.AddGrowableRow(8, 1)  
209 - Ref_sizer.SetFlexibleDirection(wx.BOTH)  
210 - Ref_sizer.AddMany([ (text, 0, wx.ALIGN_CENTER_HORIZONTAL),  
211 - (RefImg_sizer1, 0, wx.ALIGN_CENTER_HORIZONTAL),  
212 - (RefImg_sizer2, 0, wx.ALIGN_CENTER_HORIZONTAL),  
213 - (RefImg_sizer3, 0, wx.ALIGN_CENTER_HORIZONTAL),  
214 - (RefPlh_sizer1, 0, wx.ALIGN_CENTER_HORIZONTAL),  
215 - (RefPlh_sizer2, 0, wx.ALIGN_CENTER_HORIZONTAL),  
216 - (RefPlh_sizer3, 0, wx.ALIGN_CENTER_HORIZONTAL),  
217 - (Buttons_sizer4, 0, wx.ALIGN_CENTER_HORIZONTAL),  
218 - (GetPoint_sizer5, 0, wx.ALIGN_CENTER_HORIZONTAL)]) 145 + # Check box for camera update in volume rendering during navigation
  146 + tooltip = wx.ToolTip(_("Update camera in volume"))
  147 + checkcamera = wx.CheckBox(self, -1, _('Volume camera'))
  148 + checkcamera.SetToolTip(tooltip)
  149 + checkcamera.SetValue(True)
  150 + checkcamera.Bind(wx.EVT_CHECKBOX, partial(self.UpdateVolumeCamera, ctrl=checkcamera))
  151 +
  152 + # Check box for camera update in volume rendering during navigation
  153 + tooltip = wx.ToolTip(_("Enable external trigger for creating markers"))
  154 + checktrigger = wx.CheckBox(self, -1, _('External trigger'))
  155 + checktrigger.SetToolTip(tooltip)
  156 + checktrigger.SetValue(False)
  157 + checktrigger.Bind(wx.EVT_CHECKBOX, partial(self.UpdateExternalTrigger, ctrl=checktrigger))
  158 +
  159 + if sys.platform != 'win32':
  160 + checkcamera.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
  161 + checktrigger.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
  162 +
  163 + line_sizer = wx.BoxSizer(wx.HORIZONTAL)
  164 + line_sizer.Add(checkcamera, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, 5)
  165 + line_sizer.Add(checktrigger, 1,wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, 5)
  166 + line_sizer.Fit(self)
  167 +
  168 + # Panel sizer to expand fold panel
  169 + sizer = wx.BoxSizer(wx.VERTICAL)
  170 + sizer.Add(fold_panel, 0, wx.GROW|wx.EXPAND)
  171 + sizer.Add(line_sizer, 1, wx.GROW | wx.EXPAND)
  172 + sizer.Fit(self)
  173 +
  174 + self.SetSizer(sizer)
  175 + self.Update()
  176 + self.SetAutoLayout(1)
219 177
  178 + def UpdateExternalTrigger(self, evt, ctrl):
  179 + Publisher.sendMessage('Update trigger state', ctrl.GetValue())
  180 +
  181 + def UpdateVolumeCamera(self, evt, ctrl):
  182 + Publisher.sendMessage('Update volume camera state', ctrl.GetValue())
  183 +
  184 +
  185 +
  186 +
  187 +class NeuronavigationPanel(wx.Panel):
  188 + def __init__(self, parent):
  189 + wx.Panel.__init__(self, parent)
  190 + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR)
  191 + self.SetBackgroundColour(default_colour)
  192 +
  193 + self.SetAutoLayout(1)
  194 +
  195 + self.__bind_events()
  196 +
  197 + # Initialize global variables
  198 + self.fiducials = np.full([6, 3], np.nan)
  199 + self.correg = None
  200 + self.current_coord = 0, 0, 0
  201 + self.trk_init = None
  202 + self.trigger = None
  203 + self.trigger_state = False
  204 +
  205 + self.tracker_id = const.DEFAULT_TRACKER
  206 + self.ref_mode_id = const.DEFAULT_REF_MODE
  207 +
  208 + # Initialize list of buttons and numctrls for wx objects
  209 + self.btns_coord = [None] * 7
  210 + self.numctrls_coord = [list(), list(), list(), list(), list(), list(), list()]
  211 +
  212 + # ComboBox for spatial tracker device selection
  213 + tooltip = wx.ToolTip(_("Choose the tracking device"))
  214 + choice_trck = wx.ComboBox(self, -1, "",
  215 + choices=const.TRACKER, style=wx.CB_DROPDOWN|wx.CB_READONLY)
  216 + choice_trck.SetToolTip(tooltip)
  217 + choice_trck.SetSelection(const.DEFAULT_TRACKER)
  218 + choice_trck.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceTracker, ctrl=choice_trck))
  219 +
  220 + # ComboBox for tracker reference mode
  221 + tooltip = wx.ToolTip(_("Choose the navigation reference mode"))
  222 + choice_ref = wx.ComboBox(self, -1, "",
  223 + choices=const.REF_MODE, style=wx.CB_DROPDOWN|wx.CB_READONLY)
  224 + choice_ref.SetSelection(const.DEFAULT_REF_MODE)
  225 + choice_ref.SetToolTip(tooltip)
  226 + choice_ref.Bind(wx.EVT_COMBOBOX, partial(self.OnChoiceRefMode, ctrl=choice_trck))
  227 +
  228 + # Toggle buttons for image fiducials
  229 + btns_img = const.BTNS_IMG
  230 + tips_img = const.TIPS_IMG
  231 +
  232 + for k in btns_img:
  233 + n = btns_img[k].keys()[0]
  234 + lab = btns_img[k].values()[0]
  235 + self.btns_coord[n] = wx.ToggleButton(self, k, label=lab, size=wx.Size(30, 23))
  236 + self.btns_coord[n].SetToolTip(tips_img[n])
  237 + self.btns_coord[n].Bind(wx.EVT_TOGGLEBUTTON, self.OnImageFiducials)
  238 +
  239 + # Push buttons for tracker fiducials
  240 + btns_trk = const.BTNS_TRK
  241 + tips_trk = const.TIPS_TRK
  242 +
  243 + for k in btns_trk:
  244 + n = btns_trk[k].keys()[0]
  245 + lab = btns_trk[k].values()[0]
  246 + self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(30, 23))
  247 + self.btns_coord[n].SetToolTip(tips_trk[n-3])
  248 + # Excepetion for event of button that set image coordinates
  249 + if n == 6:
  250 + self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnSetImageCoordinates)
  251 + else:
  252 + self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnTrackerFiducials)
  253 +
  254 + # TODO: Find a better allignment between FRE, text and navigate button
  255 + txt_fre = wx.StaticText(self, -1, _('FRE:'))
  256 +
  257 + # Fiducial registration error text box
  258 + tooltip = wx.ToolTip(_("Fiducial registration error"))
  259 + txtctrl_fre = wx.TextCtrl(self, value="", size=wx.Size(60, -1), style=wx.TE_CENTRE)
  260 + txtctrl_fre.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.BOLD))
  261 + txtctrl_fre.SetBackgroundColour('WHITE')
  262 + txtctrl_fre.SetEditable(0)
  263 + txtctrl_fre.SetToolTip(tooltip)
  264 +
  265 + # Toggle button for neuronavigation
  266 + tooltip = wx.ToolTip(_("Start navigation"))
  267 + btn_nav = wx.ToggleButton(self, -1, _("Navigate"), size=wx.Size(80, -1))
  268 + btn_nav.SetToolTip(tooltip)
  269 + btn_nav.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnNavigate, btn=(btn_nav, choice_trck, choice_ref, txtctrl_fre)))
  270 +
  271 + # Image and tracker coordinates number controls
  272 + for m in range(0, 7):
  273 + for n in range(0, 3):
  274 + self.numctrls_coord[m].append(
  275 + wx.lib.masked.numctrl.NumCtrl(parent=self, integerWidth=4, fractionWidth=1))
  276 +
  277 + # Sizers to group all GUI objects
  278 + choice_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5)
  279 + choice_sizer.AddMany([(choice_trck, wx.LEFT),
  280 + (choice_ref, wx.RIGHT)])
  281 +
  282 + coord_sizer = wx.GridBagSizer(hgap=5, vgap=5)
  283 +
  284 + for m in range(0, 7):
  285 + coord_sizer.Add(self.btns_coord[m], pos=wx.GBPosition(m, 0))
  286 + for n in range(0, 3):
  287 + coord_sizer.Add(self.numctrls_coord[m][n], pos=wx.GBPosition(m, n+1))
  288 + if m in range(1, 6):
  289 + self.numctrls_coord[m][n].SetEditable(False)
  290 +
  291 + nav_sizer = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5)
  292 + nav_sizer.AddMany([(txt_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL),
  293 + (txtctrl_fre, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL),
  294 + (btn_nav, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)])
  295 +
  296 + group_sizer = wx.FlexGridSizer(rows=9, cols=1, hgap=5, vgap=5)
  297 + group_sizer.AddGrowableCol(0, 1)
  298 + group_sizer.AddGrowableRow(0, 1)
  299 + group_sizer.AddGrowableRow(1, 1)
  300 + group_sizer.AddGrowableRow(2, 1)
  301 + group_sizer.SetFlexibleDirection(wx.BOTH)
  302 + group_sizer.AddMany([(choice_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL),
  303 + (coord_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL),
  304 + (nav_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)])
  305 +
220 main_sizer = wx.BoxSizer(wx.HORIZONTAL) 306 main_sizer = wx.BoxSizer(wx.HORIZONTAL)
221 - main_sizer.Add(Ref_sizer, 1, wx.ALIGN_CENTER_HORIZONTAL, 10) 307 + main_sizer.Add(group_sizer, 1, wx.ALIGN_CENTER_HORIZONTAL, 10)
222 self.sizer = main_sizer 308 self.sizer = main_sizer
223 self.SetSizer(main_sizer) 309 self.SetSizer(main_sizer)
224 self.Fit() 310 self.Fit()
225 311
226 - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the image")  
227 - self.button_img_ref1.SetToolTip(tooltip)  
228 - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the image")  
229 - self.button_img_ref2.SetToolTip(tooltip)  
230 - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the image")  
231 - self.button_img_ref3.SetToolTip(tooltip)  
232 - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the space")  
233 - self.button_plh_ref1.SetToolTip(tooltip)  
234 - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the space")  
235 - self.button_plh_ref2.SetToolTip(tooltip)  
236 - tooltip = wx.ToolTip("Pick the coordinates x, y, z in the space")  
237 - self.button_plh_ref3.SetToolTip(tooltip)  
238 - tooltip = wx.ToolTip("X Coordinate")  
239 - self.numCtrl1a.SetToolTip(tooltip)  
240 - tooltip = wx.ToolTip("X Coordinate")  
241 - self.numCtrl1b.SetToolTip(tooltip)  
242 - tooltip = wx.ToolTip("X Coordinate")  
243 - self.numCtrl1c.SetToolTip(tooltip)  
244 - tooltip = wx.ToolTip("X Coordinate")  
245 - self.numCtrl1d.SetToolTip(tooltip)  
246 - tooltip = wx.ToolTip("X Coordinate")  
247 - self.numCtrl1e.SetToolTip(tooltip)  
248 - tooltip = wx.ToolTip("X Coordinate")  
249 - self.numCtrl1f.SetToolTip(tooltip)  
250 - tooltip = wx.ToolTip("X Coordinate")  
251 - self.numCtrl1g.SetToolTip(tooltip)  
252 - tooltip = wx.ToolTip("Y Coordinate")  
253 - self.numCtrl2a.SetToolTip(tooltip)  
254 - tooltip = wx.ToolTip("Y Coordinate")  
255 - self.numCtrl2b.SetToolTip(tooltip)  
256 - tooltip = wx.ToolTip("Y Coordinate")  
257 - self.numCtrl2c.SetToolTip(tooltip)  
258 - tooltip = wx.ToolTip("Y Coordinate")  
259 - self.numCtrl2d.SetToolTip(tooltip)  
260 - tooltip = wx.ToolTip("Y Coordinate")  
261 - self.numCtrl2e.SetToolTip(tooltip)  
262 - tooltip = wx.ToolTip("Y Coordinate")  
263 - self.numCtrl2f.SetToolTip(tooltip)  
264 - tooltip = wx.ToolTip("Y Coordinate")  
265 - self.numCtrl2g.SetToolTip(tooltip)  
266 - tooltip = wx.ToolTip("Z Coordinate")  
267 - self.numCtrl3a.SetToolTip(tooltip)  
268 - tooltip = wx.ToolTip("Z Coordinate")  
269 - self.numCtrl3b.SetToolTip(tooltip)  
270 - tooltip = wx.ToolTip("Z Coordinate")  
271 - self.numCtrl3c.SetToolTip(tooltip)  
272 - tooltip = wx.ToolTip("Z Coordinate")  
273 - self.numCtrl3d.SetToolTip(tooltip)  
274 - tooltip = wx.ToolTip("Z Coordinate")  
275 - self.numCtrl3e.SetToolTip(tooltip)  
276 - tooltip = wx.ToolTip("Z Coordinate")  
277 - self.numCtrl3f.SetToolTip(tooltip)  
278 - tooltip = wx.ToolTip("Z Coordinate")  
279 - self.numCtrl3g.SetToolTip(tooltip)  
280 - tooltip = wx.ToolTip("Corregistration of the real position with the image position")  
281 - self.button_crg.SetToolTip(tooltip)  
282 - tooltip = wx.ToolTip("Neuronavigation")  
283 - self.button_neuronavigate.SetToolTip(tooltip)  
284 - tooltip = wx.ToolTip("Get Cross Center Coordinates")  
285 - self.button_getpoint.SetToolTip(tooltip)  
286 -  
287 def __bind_events(self): 312 def __bind_events(self):
288 - Publisher.subscribe(self.__update_points_img, 'Update cross position')  
289 - Publisher.subscribe(self.__update_points_plh, 'Update plh position')  
290 -  
291 - def __update_points_img(self, pubsub_evt):  
292 - x, y, z = pubsub_evt.data[1]  
293 - self.a = x, y, z  
294 - if self.aux_img_ref1 == 0:  
295 - self.numCtrl1a.SetValue(x)  
296 - self.numCtrl2a.SetValue(y)  
297 - self.numCtrl3a.SetValue(z)  
298 - if self.aux_img_ref2 == 0:  
299 - self.numCtrl1b.SetValue(x)  
300 - self.numCtrl2b.SetValue(y)  
301 - self.numCtrl3b.SetValue(z)  
302 - if self.aux_img_ref3 == 0:  
303 - self.numCtrl1c.SetValue(x)  
304 - self.numCtrl2c.SetValue(y)  
305 - self.numCtrl3c.SetValue(z) 313 + Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials')
  314 + Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state')
  315 + Publisher.subscribe(self.UpdateImageCoordinates, 'Set ball reference position')
  316 +
  317 + def LoadImageFiducials(self, pubsub_evt):
  318 + marker_id = pubsub_evt.data[0]
  319 + coord = pubsub_evt.data[1]
  320 + for n in const.BTNS_IMG:
  321 + btn_id = const.BTNS_IMG[n].keys()[0]
  322 + fid_id = const.BTNS_IMG[n].values()[0]
  323 + if marker_id == fid_id and not self.btns_coord[btn_id].GetValue():
  324 + self.btns_coord[btn_id].SetValue(True)
  325 + self.fiducials[btn_id, :] = coord[0:3]
  326 + for m in [0, 1, 2]:
  327 + self.numctrls_coord[btn_id][m].SetValue(coord[m])
  328 +
  329 + def UpdateImageCoordinates(self, pubsub_evt):
  330 + # TODO: Change from world coordinates to matrix coordinates. They are better for multi software communication.
  331 + self.current_coord = pubsub_evt.data
  332 + for m in [0, 1, 2, 6]:
  333 + if m == 6 and self.btns_coord[m].IsEnabled():
  334 + for n in [0, 1, 2]:
  335 + self.numctrls_coord[m][n].SetValue(self.current_coord[n])
  336 + elif m != 6 and not self.btns_coord[m].GetValue():
  337 + # btn_state = self.btns_coord[m].GetValue()
  338 + # if not btn_state:
  339 + for n in [0, 1, 2]:
  340 + self.numctrls_coord[m][n].SetValue(self.current_coord[n])
  341 +
  342 + def UpdateTriggerState (self, pubsub_evt):
  343 + self.trigger_state = pubsub_evt.data
  344 +
  345 + def OnChoiceTracker(self, evt, ctrl):
  346 + if evt:
  347 + choice = evt.GetSelection()
  348 + else:
  349 + choice = self.tracker_id
  350 +
  351 + if self.trk_init:
  352 + trck = self.trk_init[0]
  353 + else:
  354 + trck = None
  355 +
  356 + # Conditions check if click was on current selection and if any other tracker
  357 + # has been initialized before
  358 + if trck and choice != 6:
  359 + self.ResetTrackerFiducials()
  360 + self.trk_init = dt.TrackerConnection(self.tracker_id, 'disconnect')
  361 + self.tracker_id = choice
  362 + if not self.trk_init[0]:
  363 + self.trk_init = dt.TrackerConnection(self.tracker_id, 'connect')
  364 + if not self.trk_init[0]:
  365 + dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1])
  366 + ctrl.SetSelection(0)
  367 + print "Tracker not connected!"
  368 + else:
  369 + ctrl.SetSelection(self.tracker_id)
  370 + print "Tracker connected!"
  371 + elif choice == 6:
  372 + if trck:
  373 + self.trk_init = dt.TrackerConnection(self.tracker_id, 'disconnect')
  374 + if not self.trk_init[0]:
  375 + dlg.NavigationTrackerWarning(self.tracker_id, 'disconnect')
  376 + self.tracker_id = 0
  377 + ctrl.SetSelection(self.tracker_id)
  378 + print "Tracker disconnected!"
  379 + else:
  380 + print "Tracker still connected!"
  381 + else:
  382 + ctrl.SetSelection(self.tracker_id)
  383 +
  384 + else:
  385 + # If trk_init is None try to connect. If doesn't succeed show dialog.
  386 + if choice:
  387 + self.tracker_id = choice
  388 + self.trk_init = dt.TrackerConnection(self.tracker_id, 'connect')
  389 + if not self.trk_init[0]:
  390 + dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1])
  391 + self.tracker_id = 0
  392 + ctrl.SetSelection(self.tracker_id)
  393 +
  394 + def OnChoiceRefMode(self, evt, ctrl):
  395 + # When ref mode is changed the tracker coords are set to zero
  396 + self.ref_mode_id = evt.GetSelection()
  397 + self.ResetTrackerFiducials()
  398 + # Some trackers do not accept restarting within this time window
  399 + # TODO: Improve the restarting of trackers after changing reference mode
  400 + # self.OnChoiceTracker(None, ctrl)
  401 + print "Reference mode changed!"
  402 +
  403 + def OnSetImageCoordinates(self, evt):
  404 + # FIXME: Cross does not update in last clicked slice, only on the other two
  405 + btn_id = const.BTNS_TRK[evt.GetId()].keys()[0]
  406 +
  407 + wx, wy, wz = self.numctrls_coord[btn_id][0].GetValue(), \
  408 + self.numctrls_coord[btn_id][1].GetValue(), \
  409 + self.numctrls_coord[btn_id][2].GetValue()
  410 +
  411 + Publisher.sendMessage('Set ball reference position', (wx, wy, wz))
  412 + Publisher.sendMessage('Set camera in volume', (wx, wy, wz))
  413 + Publisher.sendMessage('Co-registered points', (wx, wy, wz))
  414 + Publisher.sendMessage('Update cross position', (wx, wy, wz))
  415 +
  416 + def OnImageFiducials(self, evt):
  417 + btn_id = const.BTNS_IMG[evt.GetId()].keys()[0]
  418 + marker_id = const.BTNS_IMG[evt.GetId()].values()[0]
  419 +
  420 + if self.btns_coord[btn_id].GetValue():
  421 + coord = self.numctrls_coord[btn_id][0].GetValue(),\
  422 + self.numctrls_coord[btn_id][1].GetValue(),\
  423 + self.numctrls_coord[btn_id][2].GetValue()
  424 +
  425 + self.fiducials[btn_id, :] = coord[0:3]
  426 + Publisher.sendMessage('Create marker', (coord, marker_id))
  427 + else:
  428 + for n in [0, 1, 2]:
  429 + self.numctrls_coord[btn_id][n].SetValue(self.current_coord[n])
  430 +
  431 + self.fiducials[btn_id, :] = np.nan
  432 + Publisher.sendMessage('Delete fiducial marker', marker_id)
  433 +
  434 + def OnTrackerFiducials(self, evt):
  435 + btn_id = const.BTNS_TRK[evt.GetId()].keys()[0]
  436 + coord = None
  437 +
  438 + if self.trk_init and self.tracker_id:
  439 + coord = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id)
  440 + else:
  441 + dlg.NavigationTrackerWarning(0, 'choose')
  442 +
  443 + # Update number controls with tracker coordinates
  444 + if coord is not None:
  445 + self.fiducials[btn_id, :] = coord[0:3]
  446 + for n in [0, 1, 2]:
  447 + self.numctrls_coord[btn_id][n].SetValue(float(coord[n]))
  448 +
  449 + def OnNavigate(self, evt, btn):
  450 + btn_nav = btn[0]
  451 + choice_trck = btn[1]
  452 + choice_ref = btn[2]
  453 + txtctrl_fre = btn[3]
  454 +
  455 + nav_id = btn_nav.GetValue()
  456 + if nav_id:
  457 + if np.isnan(self.fiducials).any():
  458 + dlg.InvalidFiducials()
  459 + btn_nav.SetValue(False)
  460 +
  461 + else:
  462 + tooltip = wx.ToolTip(_("Stop neuronavigation"))
  463 + btn_nav.SetToolTip(tooltip)
  464 +
  465 + # Disable all navigation buttons
  466 + choice_ref.Enable(False)
  467 + choice_trck.Enable(False)
  468 + for btn_c in self.btns_coord:
  469 + btn_c.Enable(False)
  470 +
  471 + m, q1, minv = db.base_creation(self.fiducials[0:3, :])
  472 + n, q2, ninv = db.base_creation(self.fiducials[3::, :])
  473 +
  474 + tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id
  475 + # FIXME: FRE is taking long to calculate so it updates on GUI delayed to navigation - I think its fixed
  476 + # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok
  477 + fre = db.calculate_fre(self.fiducials, minv, n, q1, q2)
  478 +
  479 + txtctrl_fre.SetValue(str(round(fre, 2)))
  480 + if fre <= 3:
  481 + txtctrl_fre.SetBackgroundColour('GREEN')
  482 + else:
  483 + txtctrl_fre.SetBackgroundColour('RED')
  484 +
  485 + if self.trigger_state:
  486 + self.trigger = trig.Trigger(nav_id)
  487 +
  488 + self.correg = dcr.Coregistration((minv, n, q1, q2), nav_id, tracker_mode)
  489 +
  490 + else:
  491 + tooltip = wx.ToolTip(_("Start neuronavigation"))
  492 + btn_nav.SetToolTip(tooltip)
  493 +
  494 + # Enable all navigation buttons
  495 + choice_ref.Enable(True)
  496 + choice_trck.Enable(True)
  497 + for btn_c in self.btns_coord:
  498 + btn_c.Enable(True)
  499 +
  500 + if self.trigger_state:
  501 + self.trigger.stop()
306 502
307 -  
308 - def __update_points_plh(self, pubsub_evt):  
309 - coord = pubsub_evt.data  
310 - if self.aux_plh_ref1 == 0:  
311 - self.numCtrl1d.SetValue(coord[0])  
312 - self.numCtrl2d.SetValue(coord[1])  
313 - self.numCtrl3d.SetValue(coord[2])  
314 - self.aux_plh_ref1 = 1  
315 - if self.aux_plh_ref2 == 0:  
316 - self.numCtrl1e.SetValue(coord[0])  
317 - self.numCtrl2e.SetValue(coord[1])  
318 - self.numCtrl3e.SetValue(coord[2])  
319 - self.aux_plh_ref2 = 1  
320 - if self.aux_plh_ref3 == 0:  
321 - self.numCtrl1f.SetValue(coord[0])  
322 - self.numCtrl2f.SetValue(coord[1])  
323 - self.numCtrl3f.SetValue(coord[2])  
324 - self.aux_plh_ref3 = 1  
325 -  
326 - def Buttons(self, evt):  
327 - id = evt.GetId()  
328 - x, y, z = self.a  
329 - if id == PR1:  
330 - self.aux_plh_ref1 = 0  
331 - self.coord1b = self.Coordinates()  
332 - coord = self.coord1b  
333 - elif id == PR2:  
334 - self.aux_plh_ref2 = 0  
335 - self.coord2b = self.Coordinates()  
336 - coord = self.coord2b  
337 - elif id == PR3:  
338 - self.aux_plh_ref3 = 0  
339 - self.coord3b = self.Coordinates()  
340 - coord = self.coord3b  
341 - elif id == GetPoint:  
342 - x, y, z = self.a  
343 - self.numCtrl1g.SetValue(x)  
344 - self.numCtrl2g.SetValue(y)  
345 - self.numCtrl3g.SetValue(z)  
346 - info = self.a, self.flagpoint  
347 - self.SaveCoordinates(info)  
348 - self.flagpoint = 1  
349 - elif id == Corregistration and self.aux_img_ref1 == 1 and self.aux_img_ref2 == 1 and self.aux_img_ref3 == 1:  
350 - print "Coordenadas Imagem: ", self.coord1a, self.coord2a, self.coord3a  
351 - print "Coordenadas Polhemus: ", self.coord1b, self.coord2b, self.coord3b  
352 -  
353 - self.M, self.q1, self.Minv = db.Bases(self.coord1a, self.coord2a, self.coord3a).Basecreation()  
354 - self.N, self.q2, self.Ninv = db.Bases(self.coord1b, self.coord2b, self.coord3b).Basecreation()  
355 -  
356 - if self.aux_plh_ref1 == 0 or self.aux_plh_ref2 == 0 or self.aux_plh_ref3 == 0:  
357 - Publisher.sendMessage('Update plh position', coord)  
358 -  
359 - def Coordinates(self):  
360 - #Get Polhemus points for base creation  
361 - ser = serial.Serial(0)  
362 - ser.write("Y")  
363 - ser.write("P")  
364 - str = ser.readline()  
365 - ser.write("Y")  
366 - str = str.replace("\r\n","")  
367 - str = str.replace("-"," -")  
368 - aostr = [s for s in str.split()]  
369 - #aoflt -> 0:letter 1:x 2:y 3:z  
370 - aoflt = [float(aostr[1]), float(aostr[2]), float(aostr[3]),  
371 - float(aostr[4]), float(aostr[5]), float(aostr[6])]  
372 - ser.close()  
373 - #Unit change: inches to millimeters  
374 - x = 25.4  
375 - y = 25.4  
376 - z = -25.4  
377 -  
378 - coord = (aoflt[0]*x, aoflt[1]*y, aoflt[2]*z)  
379 - return coord  
380 -  
381 - def Img_Ref_ToggleButton1(self, evt):  
382 - id = evt.GetId()  
383 - flag1 = self.button_img_ref1.GetValue()  
384 - x, y, z = self.a  
385 - if flag1 == True:  
386 - self.coord1a = x, y, z  
387 - self.aux_img_ref1 = 1  
388 - elif flag1 == False:  
389 - self.aux_img_ref1 = 0  
390 - self.coord1a = (0, 0, 0)  
391 - self.numCtrl1a.SetValue(x)  
392 - self.numCtrl2a.SetValue(y)  
393 - self.numCtrl3a.SetValue(z)  
394 -  
395 - def Img_Ref_ToggleButton2(self, evt):  
396 - id = evt.GetId()  
397 - flag2 = self.button_img_ref2.GetValue()  
398 - x, y, z = self.a  
399 - if flag2 == True:  
400 - self.coord2a = x, y, z  
401 - self.aux_img_ref2 = 1  
402 - elif flag2 == False:  
403 - self.aux_img_ref2 = 0  
404 - self.coord2a = (0, 0, 0)  
405 - self.numCtrl1b.SetValue(x)  
406 - self.numCtrl2b.SetValue(y)  
407 - self.numCtrl3b.SetValue(z)  
408 -  
409 - def Img_Ref_ToggleButton3(self, evt):  
410 - id = evt.GetId()  
411 - flag3 = self.button_img_ref3.GetValue()  
412 - x, y, z = self.a  
413 - if flag3 == True:  
414 - self.coord3a = x, y, z  
415 - self.aux_img_ref3 = 1  
416 - elif flag3 == False:  
417 - self.aux_img_ref3 = 0  
418 - self.coord3a = (0, 0, 0)  
419 - self.numCtrl1c.SetValue(x)  
420 - self.numCtrl2c.SetValue(y)  
421 - self.numCtrl3c.SetValue(z)  
422 -  
423 - def Neuronavigate_ToggleButton(self, evt):  
424 - id = evt.GetId()  
425 - flag4 = self.button_neuronavigate.GetValue()  
426 - bases = self.Minv, self.N, self.q1, self.q2  
427 - if flag4 == True:  
428 - self.correg = dcr.Corregister(bases, flag4)  
429 - elif flag4 == False:  
430 self.correg.stop() 503 self.correg.stop()
  504 +
  505 + def ResetTrackerFiducials(self):
  506 + for m in range(3, 6):
  507 + for n in range(0, 3):
  508 + self.numctrls_coord[m][n].SetValue(0.0)
  509 +
  510 +
  511 +class MarkersPanel(wx.Panel):
  512 + def __init__(self, parent):
  513 + wx.Panel.__init__(self, parent)
  514 + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR)
  515 + self.SetBackgroundColour(default_colour)
  516 +
  517 + self.SetAutoLayout(1)
  518 +
  519 + self.__bind_events()
  520 +
  521 + self.current_coord = 0, 0, 0
  522 + self.list_coord = []
  523 + self.marker_ind = 0
  524 +
  525 + self.marker_colour = (0.0, 0.0, 1.)
  526 + self.marker_size = 4
  527 +
  528 + # Change marker size
  529 + spin_size = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23))
  530 + spin_size.SetRange(1, 99)
  531 + spin_size.SetValue(self.marker_size)
  532 + spin_size.Bind(wx.EVT_TEXT, partial(self.OnSelectSize, ctrl=spin_size))
  533 + spin_size.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectSize, ctrl=spin_size))
  534 +
  535 + # Marker colour select
  536 + select_colour = csel.ColourSelect(self, -1, colour=[255*s for s in self.marker_colour], size=wx.Size(20, 23))
  537 + select_colour.Bind(csel.EVT_COLOURSELECT, partial(self.OnSelectColour, ctrl=select_colour))
  538 +
  539 + btn_create = wx.Button(self, -1, label=_('Create marker'), size=wx.Size(135, 23))
  540 + btn_create.Bind(wx.EVT_BUTTON, self.OnCreateMarker)
  541 +
  542 + sizer_create = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5)
  543 + sizer_create.AddMany([(spin_size, 1),
  544 + (select_colour, 0),
  545 + (btn_create, 0)])
  546 +
  547 + # Buttons to save and load markers and to change its visibility as well
  548 + btn_save = wx.Button(self, -1, label=_('Save'), size=wx.Size(65, 23))
  549 + btn_save.Bind(wx.EVT_BUTTON, self.OnSaveMarkers)
  550 +
  551 + btn_load = wx.Button(self, -1, label=_('Load'), size=wx.Size(65, 23))
  552 + btn_load.Bind(wx.EVT_BUTTON, self.OnLoadMarkers)
  553 +
  554 + btn_visibility = wx.ToggleButton(self, -1, _("Hide"), size=wx.Size(65, 23))
  555 + btn_visibility.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnMarkersVisibility, ctrl=btn_visibility))
  556 +
  557 + sizer_btns = wx.FlexGridSizer(rows=1, cols=3, hgap=5, vgap=5)
  558 + sizer_btns.AddMany([(btn_save, 1, wx.RIGHT),
  559 + (btn_load, 0, wx.LEFT | wx.RIGHT),
  560 + (btn_visibility, 0, wx.LEFT)])
  561 +
  562 + # Buttons to delete or remove markers
  563 + btn_delete_single = wx.Button(self, -1, label=_('Remove'), size=wx.Size(65, 23))
  564 + btn_delete_single.Bind(wx.EVT_BUTTON, self.OnDeleteSingleMarker)
  565 +
  566 + btn_delete_all = wx.Button(self, -1, label=_('Delete all markers'), size=wx.Size(135, 23))
  567 + btn_delete_all.Bind(wx.EVT_BUTTON, self.OnDeleteAllMarkers)
  568 +
  569 + sizer_delete = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5)
  570 + sizer_delete.AddMany([(btn_delete_single, 1, wx.RIGHT),
  571 + (btn_delete_all, 0, wx.LEFT)])
  572 +
  573 + # List of markers
  574 + self.lc = wx.ListCtrl(self, -1, style=wx.LC_REPORT, size=wx.Size(0,120))
  575 + self.lc.InsertColumn(0, '#')
  576 + self.lc.InsertColumn(1, 'X')
  577 + self.lc.InsertColumn(2, 'Y')
  578 + self.lc.InsertColumn(3, 'Z')
  579 + self.lc.InsertColumn(4, 'ID')
  580 + self.lc.SetColumnWidth(0, 28)
  581 + self.lc.SetColumnWidth(1, 50)
  582 + self.lc.SetColumnWidth(2, 50)
  583 + self.lc.SetColumnWidth(3, 50)
  584 + self.lc.SetColumnWidth(4, 50)
  585 + self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnListEditMarkerId)
  586 +
  587 + # Add all lines into main sizer
  588 + group_sizer = wx.BoxSizer(wx.VERTICAL)
  589 + group_sizer.Add(sizer_create, 0, wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 5)
  590 + group_sizer.Add(sizer_btns, 0, wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 5)
  591 + group_sizer.Add(sizer_delete, 0, wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 5)
  592 + group_sizer.Add(self.lc, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 5)
  593 + group_sizer.Fit(self)
  594 +
  595 + self.SetSizer(group_sizer)
  596 + self.Update()
  597 +
  598 + def __bind_events(self):
  599 + Publisher.subscribe(self.UpdateCurrentCoord, 'Set ball reference position')
  600 + Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker')
  601 + Publisher.subscribe(self.OnCreateMarker, 'Create marker')
  602 +
  603 + def UpdateCurrentCoord(self, pubsub_evt):
  604 + self.current_coord = pubsub_evt.data
  605 +
  606 + def OnListEditMarkerId(self, evt):
  607 + menu_id = wx.Menu()
  608 + menu_id.Append(-1, _('Edit ID'))
  609 + menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerId)
  610 + self.PopupMenu(menu_id)
  611 + menu_id.Destroy()
  612 +
  613 + def OnMenuEditMarkerId(self, evt):
  614 + id_label = dlg.EnterMarkerID(self.lc.GetItemText(self.lc.GetFocusedItem(), 4))
  615 + list_index = self.lc.GetFocusedItem()
  616 + self.lc.SetStringItem(list_index, 4, id_label)
  617 + # Add the new ID to exported list
  618 + self.list_coord[list_index][7] = str(id_label)
  619 +
  620 + def OnDeleteAllMarkers(self, pubsub_evt):
  621 + self.list_coord = []
  622 + self.marker_ind = 0
  623 + Publisher.sendMessage('Remove all markers', self.lc.GetItemCount())
  624 + self.lc.DeleteAllItems()
  625 +
  626 + def OnDeleteSingleMarker(self, evt):
  627 + # OnDeleteSingleMarker is used for both pubsub and button click events
  628 + # Pubsub is used for fiducial handle and button click for all others
  629 +
  630 + if hasattr(evt, 'data'):
  631 + marker_id = evt.data
  632 + if self.lc.GetItemCount():
  633 + for id_n in range(self.lc.GetItemCount()):
  634 + item = self.lc.GetItem(id_n, 4)
  635 + if item.GetText() == marker_id:
  636 + if marker_id == "LEI" or marker_id == "REI" or marker_id == "NAI":
  637 + self.lc.Focus(item.GetId())
  638 + break
  639 + else:
  640 + if self.lc.GetFocusedItem() is not -1 and self.lc.GetItemCount():
  641 + index = self.lc.GetFocusedItem()
  642 + del self.list_coord[index]
  643 + self.lc.DeleteItem(index)
  644 + for n in range(0, self.lc.GetItemCount()):
  645 + self.lc.SetStringItem(n, 0, str(n+1))
  646 + self.marker_ind -= 1
  647 + Publisher.sendMessage('Remove marker', index)
  648 + elif not self.lc.GetItemCount():
  649 + pass
  650 + else:
  651 + dlg.NoMarkerSelected()
  652 +
  653 + def OnCreateMarker(self, evt):
  654 + # OnCreateMarker is used for both pubsub and button click events
  655 + # Pubsub is used for markers created with fiducial buttons, trigger and create marker button
  656 + if hasattr(evt, 'data'):
  657 + if evt.data:
  658 + self.CreateMarker(evt.data[0], (0.0, 1.0, 0.0), self.marker_size, evt.data[1])
  659 + else:
  660 + self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size)
  661 + else:
  662 + self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size)
  663 +
  664 + def OnLoadMarkers(self, evt):
  665 + filepath = dlg.ShowLoadMarkersDialog()
  666 +
  667 + if filepath:
  668 + try:
  669 + content = [s.rstrip() for s in open(filepath)]
  670 + for data in content:
  671 + line = [s for s in data.split()]
  672 + coord = float(line[0]), float(line[1]), float(line[2])
  673 + colour = float(line[3]), float(line[4]), float(line[5])
  674 + size = float(line[6])
  675 +
  676 + if len(line) == 8:
  677 + if line[7] == "LEI" or line[7] == "REI" or line[7] == "NAI":
  678 + Publisher.sendMessage('Load image fiducials', (line[7], coord))
  679 + else:
  680 + line.append("")
  681 + self.CreateMarker(coord, colour, size, line[7])
  682 + except:
  683 + dlg.InvalidMarkersFile()
  684 +
  685 + def OnMarkersVisibility(self, evt, ctrl):
  686 +
  687 + if ctrl.GetValue():
  688 + Publisher.sendMessage('Hide all markers', self.lc.GetItemCount())
  689 + ctrl.SetLabel('Show')
  690 + else:
  691 + Publisher.sendMessage('Show all markers', self.lc.GetItemCount())
  692 + ctrl.SetLabel('Hide')
  693 +
  694 + def OnSaveMarkers(self, evt):
  695 + filename = dlg.ShowSaveMarkersDialog("markers.txt")
  696 + if filename:
  697 + if self.list_coord:
  698 + text_file = open(filename, "w")
  699 + list_slice1 = self.list_coord[0]
  700 + coord = str('%.3f' %self.list_coord[0][0]) + "\t" + str('%.3f' %self.list_coord[0][1]) + "\t" + str('%.3f' %self.list_coord[0][2])
  701 + 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]
  702 + line = coord + "\t" + properties + "\n"
  703 + list_slice = self.list_coord[1:]
  704 +
  705 + for value in list_slice:
  706 + coord = str('%.3f' %value[0]) + "\t" + str('%.3f' %value[1]) + "\t" + str('%.3f' %value[2])
  707 + properties = str('%.3f' %value[3]) + "\t" + str('%.3f' %value[4]) + "\t" + str('%.3f' %value[5]) + "\t" + str('%.1f' %value[6]) + "\t" + value[7]
  708 + line = line + coord + "\t" + properties + "\n"
  709 +
  710 + text_file.writelines(line)
  711 + text_file.close()
431 712
432 - def SaveCoordinates(self, info):  
433 - #Create a file and write the points given by getpoint's button  
434 - x, y, z = info[0]  
435 - flag = info[1]  
436 -  
437 - if flag == 0:  
438 - text_file = open("points.txt", "w")  
439 - line = str('%.2f' %x) + "\t" + str('%.2f' %y) + "\t" + str('%.2f' %z) + "\n"  
440 - text_file.writelines(line)  
441 - text_file.close() 713 + def OnSelectColour(self, evt, ctrl):
  714 + self.marker_colour = [colour/255.0 for colour in ctrl.GetValue()]
  715 +
  716 + def OnSelectSize(self, evt, ctrl):
  717 + self.marker_size = ctrl.GetValue()
  718 +
  719 + def CreateMarker(self, coord, colour, size, marker_id=""):
  720 + # TODO: Use matrix coordinates and not world coordinates as current method.
  721 + # This makes easier for inter-software comprehension.
  722 +
  723 + Publisher.sendMessage('Add marker', (self.marker_ind, size, colour, coord))
  724 +
  725 + self.marker_ind += 1
  726 +
  727 + # List of lists with coordinates and properties of a marker
  728 + line = [coord[0], coord[1], coord[2], colour[0], colour[1], colour[2], self.marker_size, marker_id]
  729 +
  730 + # Adding current line to a list of all markers already created
  731 + if not self.list_coord:
  732 + self.list_coord = [line]
442 else: 733 else:
443 - text_file = open("points.txt", "r")  
444 - filedata = text_file.read()  
445 - line = filedata + str('%.2f' %x) + "\t" + str('%.2f' %y) + "\t" + str('%.2f' %z) + "\n"  
446 - text_file = open("points.txt", "w")  
447 - text_file.write(line)  
448 - text_file.close()  
449 - 734 + self.list_coord.append(line)
  735 +
  736 + # Add item to list control in panel
  737 + num_items = self.lc.GetItemCount()
  738 + self.lc.InsertStringItem(num_items, str(num_items + 1))
  739 + self.lc.SetStringItem(num_items, 1, str(round(coord[0], 2)))
  740 + self.lc.SetStringItem(num_items, 2, str(round(coord[1], 2)))
  741 + self.lc.SetStringItem(num_items, 3, str(round(coord[2], 2)))
  742 + self.lc.SetStringItem(num_items, 4, str(marker_id))
  743 + self.lc.EnsureVisible(num_items)
invesalius/reader/analyze_reader.py
@@ -1,43 +0,0 @@ @@ -1,43 +0,0 @@
1 -#--------------------------------------------------------------------------  
2 -# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas  
3 -# Copyright: (C) 2001 Centro de Pesquisas Renato Archer  
4 -# Homepage: http://www.softwarepublico.gov.br  
5 -# Contact: invesalius@cti.gov.br  
6 -# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)  
7 -#--------------------------------------------------------------------------  
8 -# Este programa e software livre; voce pode redistribui-lo e/ou  
9 -# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme  
10 -# publicada pela Free Software Foundation; de acordo com a versao 2  
11 -# da Licenca.  
12 -#  
13 -# Este programa eh distribuido na expectativa de ser util, mas SEM  
14 -# QUALQUER GARANTIA; sem mesmo a garantia implicita de  
15 -# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM  
16 -# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais  
17 -# detalhes.  
18 -#--------------------------------------------------------------------------  
19 -  
20 -import os  
21 -import multiprocessing  
22 -import tempfile  
23 -  
24 -import vtk  
25 -  
26 -from nibabel import AnalyzeImage, squeeze_image  
27 -  
28 -def ReadAnalyze(filename):  
29 - anlz = squeeze_image(AnalyzeImage.from_filename(filename))  
30 - return anlz  
31 -  
32 -def ReadDirectory(dir_):  
33 - """  
34 - Looking for analyze files in the given directory  
35 - """  
36 - imagedata = None  
37 - for root, sub_folders, files in os.walk(dir_):  
38 - for file in files:  
39 - if file.split(".")[-1] == "hdr":  
40 - filename = os.path.join(root,file)  
41 - imagedata = ReadAnalyze(filename)  
42 - return imagedata  
43 - return imagedata  
invesalius/reader/others_reader.py 0 → 100644
@@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
  1 +#--------------------------------------------------------------------------
  2 +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas
  3 +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer
  4 +# Homepage: http://www.softwarepublico.gov.br
  5 +# Contact: invesalius@cti.gov.br
  6 +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt)
  7 +#--------------------------------------------------------------------------
  8 +# Este programa e software livre; voce pode redistribui-lo e/ou
  9 +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme
  10 +# publicada pela Free Software Foundation; de acordo com a versao 2
  11 +# da Licenca.
  12 +#
  13 +# Este programa eh distribuido na expectativa de ser util, mas SEM
  14 +# QUALQUER GARANTIA; sem mesmo a garantia implicita de
  15 +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM
  16 +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
  17 +# detalhes.
  18 +#--------------------------------------------------------------------------
  19 +
  20 +import os
  21 +
  22 +import vtk
  23 +import nibabel as nib
  24 +
  25 +import invesalius.constants as const
  26 +
  27 +
  28 +def ReadOthers(dir_):
  29 + """
  30 + Read the given Analyze, NIfTI, Compressed NIfTI or PAR/REC file,
  31 + remove singleton image dimensions and convert image orientation to
  32 + RAS+ canonical coordinate system. Analyze header does not support
  33 + affine transformation matrix, though cannot be converted automatically
  34 + to canonical orientation.
  35 +
  36 + :param dir_: file path
  37 + :return: imagedata object
  38 + """
  39 +
  40 + if not const.VTK_WARNING:
  41 + log_path = os.path.join(const.LOG_FOLDER, 'vtkoutput.txt')
  42 + fow = vtk.vtkFileOutputWindow()
  43 + fow.SetFileName(log_path)
  44 + ow = vtk.vtkOutputWindow()
  45 + ow.SetInstance(fow)
  46 +
  47 + imagedata = nib.squeeze_image(nib.load(dir_))
  48 + imagedata = nib.as_closest_canonical(imagedata)
  49 + imagedata.update_header()
  50 +
  51 + return imagedata
0 \ No newline at end of file 52 \ No newline at end of file
navigation/mtc_files/CalibrationFiles/BumbleBee_8090380.calib 0 → 100644
No preview for this file type
navigation/mtc_files/CalibrationFiles/_README.txt 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +This folder contains all the calibration files for the installed MicronTrackers
0 \ No newline at end of file 2 \ No newline at end of file
navigation/mtc_files/Markers/1Probe 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=1Probe
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-61.602408326043872
  12 +EndPos(0,1)=4.2184025144074082e-016
  13 +EndPos(0,2)=-4.556586682229099e-016
  14 +EndPos(1,0)=61.602408326043872
  15 +EndPos(1,1)=-9.6115500328270063e-017
  16 +EndPos(1,2)=-1.1391466705572748e-016
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=-61.59657019736504
  20 +EndPos(0,1)=11.949604571287503
  21 +EndPos(0,2)=1.1391466705572748e-016
  22 +EndPos(1,0)=-61.602408326043872
  23 +EndPos(1,1)=4.2184025144074082e-016
  24 +EndPos(1,2)=-4.556586682229099e-016
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=145.66478105979206
  29 +R0,0=-8.4560706371216821e-002
  30 +R1,0=-0.52109081782374
  31 +R2,0=0.84930197604725266
  32 +S1=-13.147426563284974
  33 +R0,1=4.5008904130328986e-002
  34 +R1,1=-0.85348236485506712
  35 +R2,1=-0.5191743940434248
  36 +S2=9.0630880541399712
  37 +R0,2=0.99540126857813949
  38 +R1,2=-5.6756022725552024e-003
  39 +R2,2=9.5624798310161907e-002
navigation/mtc_files/Markers/1b 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +Name=1b
  5 +
  6 +[Facet1]
  7 +VectorsCount=2
  8 +
  9 +[Facet1V1]
  10 +EndPos(0,0)=-13.977391161539257
  11 +EndPos(0,1)=1.2440742880315269e-015
  12 +EndPos(0,2)=-9.6300745155986073e-015
  13 +EndPos(1,0)=13.977391161539257
  14 +EndPos(1,1)=1.2515682934477468e-015
  15 +EndPos(1,2)=-9.6789243286821155e-015
  16 +
  17 +[Facet1V2]
  18 +EndPos(0,0)=13.977391161539257
  19 +EndPos(0,1)=1.2515682934477468e-015
  20 +EndPos(0,2)=-9.6789243286821155e-015
  21 +EndPos(1,0)=14.019670530273789
  22 +EndPos(1,1)=15.245637552880362
  23 +EndPos(1,2)=-9.7055696812731187e-015
  24 +
  25 +[Tooltip2MarkerXf]
  26 +Scale=1.
  27 +S0=0.
  28 +R0,0=1.
  29 +R1,0=0.
  30 +R2,0=0.
  31 +S1=0.
  32 +R0,1=0.
  33 +R1,1=1.
  34 +R2,1=0.
  35 +S2=0.
  36 +R0,2=0.
  37 +R1,2=0.
  38 +R2,2=1.
navigation/mtc_files/Markers/2Coil 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=2Coil
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-8.9266116684264851
  12 +EndPos(0,1)=-7.9898502660127542e-016
  13 +EndPos(0,2)=1.0214990704373533e-015
  14 +EndPos(1,0)=8.9266116684264833
  15 +EndPos(1,1)=-6.360897244681129e-016
  16 +EndPos(1,2)=2.6438799470143262e-015
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=8.9266116684264833
  20 +EndPos(0,1)=-6.360897244681129e-016
  21 +EndPos(0,2)=2.6438799470143262e-015
  22 +EndPos(1,0)=9.0134937152069554
  23 +EndPos(1,1)=-12.059011297429047
  24 +EndPos(1,2)=3.6653790174516793e-015
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=0.20000000000000079
  29 +R0,0=0.96592582628906842
  30 +R1,0=-1.457167719820518e-016
  31 +R2,0=-0.25881904510252007
  32 +S1=-14.79999999999999
  33 +R0,1=7.9979483404574392e-002
  34 +R1,1=0.95105651629515409
  35 +R2,1=0.29848749562898386
  36 +S2=99.400000000000091
  37 +R0,2=0.24615153938604106
  38 +R1,2=-0.30901699437494573
  39 +R2,2=0.91865005134999966
navigation/mtc_files/Markers/2b 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +Name=2b
  5 +
  6 +[Facet1]
  7 +VectorsCount=2
  8 +
  9 +[Facet1V1]
  10 +EndPos(0,0)=-13.9690559296461
  11 +EndPos(0,1)=-9.08090028293946e-017
  12 +EndPos(0,2)=3.7518297647387081e-015
  13 +EndPos(1,0)=13.969055929646105
  14 +EndPos(1,1)=-1.1456717217429434e-016
  15 +EndPos(1,2)=3.7783785761971358e-015
  16 +
  17 +[Facet1V2]
  18 +EndPos(0,0)=-13.924452046559736
  19 +EndPos(0,1)=-15.196209648340083
  20 +EndPos(0,2)=3.7349350665378906e-015
  21 +EndPos(1,0)=-13.9690559296461
  22 +EndPos(1,1)=-9.08090028293946e-017
  23 +EndPos(1,2)=3.7518297647387081e-015
  24 +
  25 +[Tooltip2MarkerXf]
  26 +Scale=1.
  27 +S0=0.
  28 +R0,0=1.
  29 +R1,0=0.
  30 +R2,0=0.
  31 +S1=0.
  32 +R0,1=0.
  33 +R1,1=1.
  34 +R2,1=0.
  35 +S2=0.
  36 +R0,2=0.
  37 +R1,2=0.
  38 +R2,2=1.
navigation/mtc_files/Markers/3Coil 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=3Coil
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-8.9434442564413246
  12 +EndPos(0,1)=-6.1153268241647966e-016
  13 +EndPos(0,2)=3.4944724709513123e-016
  14 +EndPos(1,0)=8.9434442564413299
  15 +EndPos(1,1)=-8.8089826871897666e-016
  16 +EndPos(1,2)=-4.6592966279350834e-016
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=-8.9439343674574907
  20 +EndPos(0,1)=11.937904678416533
  21 +EndPos(0,2)=-5.2417087064269685e-016
  22 +EndPos(1,0)=-8.9434442564413246
  23 +EndPos(1,1)=-6.1153268241647966e-016
  24 +EndPos(1,2)=3.4944724709513123e-016
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=0.80000000000000182
  29 +R0,0=0.9563047559630351
  30 +R1,0=1.8041124150158794e-016
  31 +R2,0=-0.29237170472273794
  32 +S1=47.299999999999962
  33 +R0,1=9.0347825433700193e-002
  34 +R1,1=0.95105651629515331
  35 +R2,1=0.29551442139416562
  36 +S2=42.399999999999991
  37 +R0,2=0.27806201495688238
  38 +R1,2=-0.30901699437494834
  39 +R2,2=0.90949986972269081
navigation/mtc_files/Markers/4Coil 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=4Coil
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-8.9585812958876883
  12 +EndPos(0,1)=-4.8569430893988515e-016
  13 +EndPos(0,2)=2.3154142102162125e-015
  14 +EndPos(1,0)=8.95858129588769
  15 +EndPos(1,1)=-1.2210192124187057e-016
  16 +EndPos(1,2)=4.6308284204324244e-016
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=8.95858129588769
  20 +EndPos(0,1)=-1.2210192124187057e-016
  21 +EndPos(0,2)=4.6308284204324244e-016
  22 +EndPos(1,0)=8.8914854228233384
  23 +EndPos(1,1)=11.981845264775341
  24 +EndPos(1,2)=3.4731213153243185e-016
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=-0.40000000000000141
  29 +R0,0=1.
  30 +R1,0=0.
  31 +R2,0=0.
  32 +S1=10.700000000000001
  33 +R0,1=0.
  34 +R1,1=1.
  35 +R2,1=0.
  36 +S2=110.09999999999999
  37 +R0,2=0.
  38 +R1,2=0.
  39 +R2,2=1.
navigation/mtc_files/Markers/5Ref 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +[Marker]
  2 +FacetsCount=1
  3 +SliderControlledXpointsCount=0
  4 +Name=5Ref
  5 +
  6 +[Facet1]
  7 +VectorsCount=2
  8 +
  9 +[Facet1V1]
  10 +EndPos(0,0)=-8.9120676333490003
  11 +EndPos(0,1)=2.3980817331903383e-016
  12 +EndPos(0,2)=-1.2505552149377763e-015
  13 +EndPos(1,0)=8.9120676333490003
  14 +EndPos(1,1)=2.6645352591003756e-017
  15 +EndPos(1,2)=0.
  16 +
  17 +[Facet1V2]
  18 +EndPos(0,0)=-8.9951734202602971
  19 +EndPos(0,1)=-12.045839883319603
  20 +EndPos(0,2)=-4.5474735088646413e-016
  21 +EndPos(1,0)=-8.9120676333490003
  22 +EndPos(1,1)=2.3980817331903383e-016
  23 +EndPos(1,2)=-1.2505552149377763e-015
  24 +
  25 +[Tooltip2MarkerXf]
  26 +Scale=1.
  27 +S0=0.
  28 +R0,0=1.
  29 +R1,0=0.
  30 +R2,0=0.
  31 +S1=0.
  32 +R0,1=0.
  33 +R1,1=1.
  34 +R2,1=0.
  35 +S2=0.
  36 +R0,2=0.
  37 +R1,2=0.
  38 +R2,2=1.
navigation/mtc_files/Markers/COOLCARD 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +[Marker]
  2 +FacetsCount=1
  3 +Name=COOLCARD
  4 +
  5 +[Facet1]
  6 +VectorsCount=2
  7 +
  8 +[Facet1V1]
  9 +EndPos(0,0)=-31.763129600371474
  10 +EndPos(0,1)=-6.4066430885648868e-016
  11 +EndPos(0,2)=1.9712913472123418e-015
  12 +EndPos(1,0)=31.763129600371467
  13 +EndPos(1,1)=-6.4440270518456277e-016
  14 +EndPos(1,2)=1.9656972001890368e-015
  15 +
  16 +[Facet1V2]
  17 +EndPos(0,0)=-31.746690593113094
  18 +EndPos(0,1)=25.423398067377672
  19 +EndPos(0,2)=1.9712913472123418e-015
  20 +EndPos(1,0)=-31.763129600371474
  21 +EndPos(1,1)=-6.4066430885648868e-016
  22 +EndPos(1,2)=1.9712913472123418e-015
  23 +
  24 +[Tooltip2MarkerXf]
  25 +Scale=1.
  26 +S0=0.
  27 +R0,0=1.
  28 +R1,0=0.
  29 +R2,0=0.
  30 +S1=0.
  31 +R0,1=0.
  32 +R1,1=1.
  33 +R2,1=0.
  34 +S2=0.
  35 +R0,2=0.
  36 +R1,2=0.
  37 +R2,2=1.
navigation/mtc_files/Markers/Pointer Template 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=Pointer Template
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-8.9079792482800304
  12 +EndPos(0,1)=4.7658354227811601e-016
  13 +EndPos(0,2)=-3.8126683382249281e-015
  14 +EndPos(1,0)=8.9079792482800322
  15 +EndPos(1,1)=-3.754900636130611e-016
  16 +EndPos(1,2)=4.6214161675453676e-016
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=8.9079792482800322
  20 +EndPos(0,1)=-3.754900636130611e-016
  21 +EndPos(0,2)=4.6214161675453676e-016
  22 +EndPos(1,0)=8.97037014055695
  23 +EndPos(1,1)=-11.854493390526288
  24 +EndPos(1,2)=-5.1990931884885378e-015
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=0.
  29 +R0,0=1.
  30 +R1,0=0.
  31 +R2,0=0.
  32 +S1=0.
  33 +R0,1=0.
  34 +R1,1=1.
  35 +R2,1=0.
  36 +S2=0.
  37 +R0,2=0.
  38 +R1,2=0.
  39 +R2,2=1.
navigation/mtc_files/Markers/Ref 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=Ref
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-8.9120676333490003
  12 +EndPos(0,1)=2.3980817331903383e-016
  13 +EndPos(0,2)=-1.2505552149377763e-015
  14 +EndPos(1,0)=8.9120676333490003
  15 +EndPos(1,1)=2.6645352591003756e-017
  16 +EndPos(1,2)=0.
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=-8.9951734202602971
  20 +EndPos(0,1)=-12.045839883319603
  21 +EndPos(0,2)=-4.5474735088646413e-016
  22 +EndPos(1,0)=-8.9120676333490003
  23 +EndPos(1,1)=2.3980817331903383e-016
  24 +EndPos(1,2)=-1.2505552149377763e-015
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=0.
  29 +R0,0=1.
  30 +R1,0=0.
  31 +R2,0=0.
  32 +S1=0.
  33 +R0,1=0.
  34 +R1,1=1.
  35 +R2,1=0.
  36 +S2=0.
  37 +R0,2=0.
  38 +R1,2=0.
  39 +R2,2=1.
navigation/mtc_files/Markers/TTblock 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +[Marker]
  2 +FacetsCount=1
  3 +Name=TTblock
  4 +
  5 +[Facet1]
  6 +VectorsCount=2
  7 +
  8 +[Facet1V1]
  9 +EndPos(0,0)=-44.89806848621749
  10 +EndPos(0,1)=1.5829351718288365e-017
  11 +EndPos(0,2)=-2.4650420593630429e-015
  12 +EndPos(1,0)=44.898068486217483
  13 +EndPos(1,1)=8.5012292344588403e-016
  14 +EndPos(1,2)=-2.4138677168217271e-015
  15 +
  16 +[Facet1V2]
  17 +EndPos(0,0)=-0.25683199956524938
  18 +EndPos(0,1)=42.807497943328485
  19 +EndPos(0,2)=-8.1564930877711453e-003
  20 +EndPos(1,0)=0.23846937714486499
  21 +EndPos(1,1)=-42.932871913619174
  22 +EndPos(1,2)=-8.1564930877712043e-003
  23 +
  24 +[Tooltip2MarkerXf]
  25 +Scale=1.
  26 +S0=0.
  27 +R0,0=1.
  28 +R1,0=0.
  29 +R2,0=0.
  30 +S1=0.
  31 +R0,1=0.
  32 +R1,1=1.
  33 +R2,1=0.
  34 +S2=0.
  35 +R0,2=0.
  36 +R1,2=0.
  37 +R2,2=1.
navigation/mtc_files/Markers/a 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +[Marker]
  2 +FacetsCount=1
  3 +Name=a
  4 +
  5 +[Facet1]
  6 +VectorsCount=2
  7 +
  8 +[Facet1V1]
  9 +EndPos(0,0)=-32.971498168704073
  10 +EndPos(0,1)=2.5755541490385065e-016
  11 +EndPos(0,2)=1.6979881553090628e-016
  12 +EndPos(1,0)=32.971498168704088
  13 +EndPos(1,1)=3.2816886463184776e-016
  14 +EndPos(1,2)=4.4082384801292978e-017
  15 +
  16 +[Facet1V2]
  17 +EndPos(0,0)=-32.975185360462866
  18 +EndPos(0,1)=46.723702252369826
  19 +EndPos(0,2)=-8.9797450521152372e-018
  20 +EndPos(1,0)=-32.971498168704073
  21 +EndPos(1,1)=2.5755541490385065e-016
  22 +EndPos(1,2)=1.6979881553090628e-016
  23 +
  24 +[Tooltip2MarkerXf]
  25 +Scale=1.
  26 +S0=0.
  27 +R0,0=1.
  28 +R1,0=0.
  29 +R2,0=0.
  30 +S1=0.
  31 +R0,1=0.
  32 +R1,1=1.
  33 +R2,1=0.
  34 +S2=0.
  35 +R0,2=0.
  36 +R1,2=0.
  37 +R2,2=1.
navigation/mtc_files/Markers/coil 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +Name=coil
  5 +
  6 +[Facet1]
  7 +VectorsCount=2
  8 +
  9 +[Facet1V1]
  10 +EndPos(0,0)=-70.299927206216339
  11 +EndPos(0,1)=1.1368683772161603e-016
  12 +EndPos(0,2)=1.3642420526593924e-015
  13 +EndPos(1,0)=70.299927206216339
  14 +EndPos(1,1)=-1.1368683772161603e-016
  15 +EndPos(1,2)=6.8212102632969619e-016
  16 +
  17 +[Facet1V2]
  18 +EndPos(0,0)=-69.651993385067129
  19 +EndPos(0,1)=-65.744122964094188
  20 +EndPos(0,2)=3.410605131648481e-016
  21 +EndPos(1,0)=-70.299927206216339
  22 +EndPos(1,1)=1.1368683772161603e-016
  23 +EndPos(1,2)=1.3642420526593924e-015
  24 +
  25 +[Tooltip2MarkerXf]
  26 +Scale=1.
  27 +S0=0.
  28 +R0,0=1.
  29 +R1,0=0.
  30 +R2,0=0.
  31 +S1=0.
  32 +R0,1=0.
  33 +R1,1=1.
  34 +R2,1=0.
  35 +S2=0.
  36 +R0,2=0.
  37 +R1,2=0.
  38 +R2,2=1.
navigation/mtc_files/Markers/probe 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +SliderControlledXpointsCount=0
  5 +Name=probe
  6 +
  7 +[Facet1]
  8 +VectorsCount=2
  9 +
  10 +[Facet1V1]
  11 +EndPos(0,0)=-61.602408326043872
  12 +EndPos(0,1)=4.2184025144074082e-016
  13 +EndPos(0,2)=-4.556586682229099e-016
  14 +EndPos(1,0)=61.602408326043872
  15 +EndPos(1,1)=-9.6115500328270063e-017
  16 +EndPos(1,2)=-1.1391466705572748e-016
  17 +
  18 +[Facet1V2]
  19 +EndPos(0,0)=-61.59657019736504
  20 +EndPos(0,1)=11.949604571287503
  21 +EndPos(0,2)=1.1391466705572748e-016
  22 +EndPos(1,0)=-61.602408326043872
  23 +EndPos(1,1)=4.2184025144074082e-016
  24 +EndPos(1,2)=-4.556586682229099e-016
  25 +
  26 +[Tooltip2MarkerXf]
  27 +Scale=1.
  28 +S0=145.66478105979206
  29 +R0,0=-8.4560706371216821e-002
  30 +R1,0=-0.52109081782374
  31 +R2,0=0.84930197604725266
  32 +S1=-13.147426563284974
  33 +R0,1=4.5008904130328986e-002
  34 +R1,1=-0.85348236485506712
  35 +R2,1=-0.5191743940434248
  36 +S2=9.0630880541399712
  37 +R0,2=0.99540126857813949
  38 +R1,2=-5.6756022725552024e-003
  39 +R2,2=9.5624798310161907e-002
navigation/mtc_files/Markers/sptr 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +Name=sptr
  5 +
  6 +[Facet1]
  7 +VectorsCount=2
  8 +
  9 +[Facet1V1]
  10 +EndPos(0,0)=-52.19791804784397
  11 +EndPos(0,1)=1.2304185897405721e-016
  12 +EndPos(0,2)=6.9721287355505385e-016
  13 +EndPos(1,0)=52.19791804784397
  14 +EndPos(1,1)=-7.4104692177650016e-017
  15 +EndPos(1,2)=5.9481366254593747e-016
  16 +
  17 +[Facet1V2]
  18 +EndPos(0,0)=-52.224959539439432
  19 +EndPos(0,1)=11.532261461857525
  20 +EndPos(0,2)=6.9918899867979109e-016
  21 +EndPos(1,0)=-52.19791804784397
  22 +EndPos(1,1)=1.2304185897405721e-016
  23 +EndPos(1,2)=6.9721287355505385e-016
  24 +
  25 +[Tooltip2MarkerXf]
  26 +Scale=1.
  27 +S0=132.5915439593966
  28 +R0,0=-3.3573683844187456e-002
  29 +R1,0=-0.99175827123861893
  30 +R2,0=0.12364602372465228
  31 +S1=-3.8041069565099583
  32 +R0,1=-3.9209531312008813e-002
  33 +R1,1=-0.12231349340138305
  34 +R2,1=-0.9917167045009595
  35 +S2=-0.29142356239092942
  36 +R0,2=0.99866682152128394
  37 +R1,2=-3.8143685738751404e-002
  38 +R2,2=-3.4779862432738673e-002
navigation/mtc_files/Markers/try4 0 → 100644
@@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
  1 +
  2 +[Marker]
  3 +FacetsCount=1
  4 +Name=try4
  5 +
  6 +[Facet1]
  7 +VectorsCount=2
  8 +
  9 +[Facet1V1]
  10 +EndPos(0,0)=-57.030279071976651
  11 +EndPos(0,1)=-1.7053025658242405e-016
  12 +EndPos(0,2)=-3.1832314562052489e-015
  13 +EndPos(1,0)=57.030279071976658
  14 +EndPos(1,1)=1.7053025658242405e-016
  15 +EndPos(1,2)=-1.8189894035458565e-015
  16 +
  17 +[Facet1V2]
  18 +EndPos(0,0)=-56.190229598357341
  19 +EndPos(0,1)=93.77396683857414
  20 +EndPos(0,2)=0.
  21 +EndPos(1,0)=-57.030279071976651
  22 +EndPos(1,1)=-1.7053025658242405e-016
  23 +EndPos(1,2)=-3.1832314562052489e-015
  24 +
  25 +[Tooltip2MarkerXf]
  26 +Scale=1.
  27 +S0=0.
  28 +R0,0=1.
  29 +R1,0=0.
  30 +R2,0=0.
  31 +S1=0.
  32 +R0,1=0.
  33 +R1,1=1.
  34 +R2,1=0.
  35 +S2=0.
  36 +R0,2=0.
  37 +R1,2=0.
  38 +R2,2=1.