Commit bed72ec34ae1e89713f64d9abd4f96f8b85d644b

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

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
.gitignore
... ... @@ -8,6 +8,7 @@ invesalius/*.swo
8 8 invesalius/*.swp
9 9 invesalius/data/*.log
10 10 invesalius/data/*.pyc
  11 +invesalius/data/*.pyd
11 12 invesalius/gui/*.log
12 13 invesalius/gui/*.pyc
13 14 invesalius/gui/widgets/*.log
... ... @@ -21,6 +22,7 @@ invesalius/reader/*.pyc
21 22 tags
22 23 *.c
23 24  
  25 +.idea
24 26 build
25 27 *.patch
26 28 *.tgz
... ... @@ -29,4 +31,4 @@ build
29 31 *.cpp
30 32 *.diff
31 33  
32 34 -*.directory
  35 +*.directory
33 36 \ No newline at end of file
... ...
invesalius/constants.py
... ... @@ -487,8 +487,9 @@ VTK_WARNING = 0
487 487  
488 488 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE,
489 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 493 ID_EXIT = wx.ID_EXIT
493 494 ID_ABOUT = wx.ID_ABOUT
494 495  
... ... @@ -637,3 +638,53 @@ BOOLEAN_UNION = 1
637 638 BOOLEAN_DIFF = 2
638 639 BOOLEAN_AND = 3
639 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 16 # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais
17 17 # detalhes.
18 18 #--------------------------------------------------------------------------
19   -import math
20 19 import os
21 20 import plistlib
22 21 import wx
23   -import numpy
24 22 from wx.lib.pubsub import pub as Publisher
25 23  
26 24 import invesalius.constants as const
... ... @@ -32,10 +30,10 @@ import invesalius.data.surface as srf
32 30 import invesalius.data.volume as volume
33 31 import invesalius.gui.dialogs as dialog
34 32 import invesalius.project as prj
35   -import invesalius.reader.analyze_reader as analyze
36 33 import invesalius.reader.dicom_grouper as dg
37 34 import invesalius.reader.dicom_reader as dcm
38 35 import invesalius.reader.bitmap_reader as bmp
  36 +import invesalius.reader.others_reader as oth
39 37 import invesalius.session as ses
40 38  
41 39  
... ... @@ -65,6 +63,8 @@ class Controller():
65 63 Publisher.subscribe(self.OnImportMedicalImages, 'Import directory')
66 64 Publisher.subscribe(self.OnShowDialogImportDirectory,
67 65 'Show import directory dialog')
  66 + Publisher.subscribe(self.OnShowDialogImportOtherFiles,
  67 + 'Show import other files dialog')
68 68 Publisher.subscribe(self.OnShowDialogOpenProject,
69 69 'Show open project dialog')
70 70  
... ... @@ -77,7 +77,9 @@ class Controller():
77 77 Publisher.subscribe(self.OnOpenDicomGroup,
78 78 'Open DICOM group')
79 79 Publisher.subscribe(self.OnOpenBitmapFiles,
80   - 'Open bitmap files')
  80 + 'Open bitmap files')
  81 + Publisher.subscribe(self.OnOpenOtherFiles,
  82 + 'Open other files')
81 83 Publisher.subscribe(self.Progress, "Update dicom load")
82 84 Publisher.subscribe(self.Progress, "Update bitmap load")
83 85 Publisher.subscribe(self.OnLoadImportPanel, "End dicom load")
... ... @@ -88,7 +90,6 @@ class Controller():
88 90 Publisher.subscribe(self.OnShowDialogCloseProject, 'Close Project')
89 91 Publisher.subscribe(self.OnOpenProject, 'Open project')
90 92 Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project')
91   - Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog')
92 93 Publisher.subscribe(self.OnShowBitmapFile, 'Show bitmap dialog')
93 94  
94 95 Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog')
... ... @@ -116,6 +117,10 @@ class Controller():
116 117 def OnShowDialogImportDirectory(self, pubsub_evt):
117 118 self.ShowDialogImportDirectory()
118 119  
  120 + def OnShowDialogImportOtherFiles(self, pubsub_evt):
  121 + id_type = pubsub_evt.data
  122 + self.ShowDialogImportOtherFiles(id_type)
  123 +
119 124 def OnShowDialogOpenProject(self, pubsub_evt):
120 125 self.ShowDialogOpenProject()
121 126  
... ... @@ -126,15 +131,6 @@ class Controller():
126 131 def OnShowDialogCloseProject(self, pubsub_evt):
127 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 134 def OnShowBitmapFile(self, pubsub_evt):
139 135 self.ShowDialogImportBitmapFile()
140 136 ###########################
... ... @@ -163,7 +159,6 @@ class Controller():
163 159 self.StartImportBitmapPanel(dirpath)
164 160 # Publisher.sendMessage("Load data to import panel", dirpath)
165 161  
166   -
167 162 def ShowDialogImportDirectory(self):
168 163 # Offer to save current project if necessary
169 164 session = ses.Session()
... ... @@ -186,6 +181,40 @@ class Controller():
186 181 self.StartImportPanel(dirpath)
187 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 218 def ShowDialogOpenProject(self):
190 219 # Offer to save current project if necessary
191 220 session = ses.Session()
... ... @@ -281,8 +310,6 @@ class Controller():
281 310 else:
282 311 dialog.InexistentPath(filepath)
283 312  
284   -
285   -
286 313 def OpenProject(self, filepath):
287 314 Publisher.sendMessage('Begin busy cursor')
288 315 path = os.path.abspath(filepath)
... ... @@ -342,7 +369,7 @@ class Controller():
342 369  
343 370 def StartImportPanel(self, path):
344 371  
345   - # retrieve DICOM files splited into groups
  372 + # retrieve DICOM files split into groups
346 373 reader = dcm.ProgressDicomReader()
347 374 reader.SetWindowEvent(self.frame)
348 375 reader.SetDirectoryPath(path)
... ... @@ -410,21 +437,33 @@ class Controller():
410 437 self.ImportMedicalImages(directory)
411 438  
412 439 def ImportMedicalImages(self, directory):
413   - # OPTION 1: DICOM?
414 440 patients_groups = dcm.GetDicomGroups(directory)
  441 + name = directory.rpartition('\\')[-1].split('.')
  442 + print "patients: ", patients_groups
  443 +
415 444 if len(patients_groups):
  445 + # OPTION 1: DICOM
416 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 448 self.CreateDicomProject(dicom, matrix, matrix_filename)
419   - # OPTION 2: ANALYZE?
420 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 459 else:
426 460 utils.debug("No medical images found on given directory")
427 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 467 self.LoadProject()
429 468 Publisher.sendMessage("Enable state project", True)
430 469  
... ... @@ -493,43 +532,6 @@ class Controller():
493 532  
494 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 535 def CreateDicomProject(self, dicom, matrix, matrix_filename):
534 536 name_to_const = {"AXIAL":const.AXIAL,
535 537 "CORONAL":const.CORONAL,
... ... @@ -604,6 +606,40 @@ class Controller():
604 606  
605 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 643 def OnOpenBitmapFiles(self, pubsub_evt):
608 644 rec_data = pubsub_evt.data
609 645 bmp_data = bmp.BitmapData()
... ... @@ -688,6 +724,26 @@ class Controller():
688 724 self.LoadProject()
689 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 747 def OpenDicomGroup(self, dicom_group, interval, file_range, gui=True):
692 748 # Retrieve general DICOM headers
693 749 dicom = dicom_group.GetDicomSample()
... ... @@ -773,6 +829,30 @@ class Controller():
773 829  
774 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 856 def LoadImagedataInfo(self):
777 857 proj = prj.Project()
778 858  
... ...
invesalius/data/bases.py
1   -from numpy import *
2 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 128 [0.0, 1.0, 0, point[0, 1]],
80 129 [0.0, 0.0, 1.0, point[0, 2]],
81 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   -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 @@
  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 @@
  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 592  
593 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 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 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 647 \ No newline at end of file
... ...
invesalius/data/slice_.py
... ... @@ -531,10 +531,12 @@ class Slice(object):
531 531 image = self.do_colour_image(ww_wl_image)
532 532 if self.current_mask and self.current_mask.is_shown:
533 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 536 mask = self.buffer_slices[orientation].vtk_mask
536 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 540 n_mask = self.get_mask_slice(orientation, slice_number)
539 541 mask = converters.to_vtk(n_mask, self.spacing, slice_number, orientation)
540 542 mask = self.do_colour_mask(mask, self.opacity)
... ...
invesalius/data/styles.py
... ... @@ -230,10 +230,8 @@ class CrossInteractorStyle(DefaultInteractorStyle):
230 230 coord = self.viewer.calcultate_scroll_position(px, py)
231 231 Publisher.sendMessage('Update cross position', (wx, wy, wz))
232 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 234 Publisher.sendMessage('Set camera in volume', (wx, wy, wz))
236   - Publisher.sendMessage('Render volume viewer')
237 235  
238 236 iren.Render()
239 237  
... ...
invesalius/data/trackers.py 0 → 100644
... ... @@ -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 223 \ No newline at end of file
... ...
invesalius/data/trigger.py 0 → 100644
... ... @@ -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 911 if self.slice_data.cursor:
912 912 self.slice_data.cursor.SetColour(colour_vtk)
913 913  
914   - def Navigation(self, pubsub_evt):
  914 + def UpdateSlicesNavigation(self, pubsub_evt):
915 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 921 self.ScrollSlice(coord)
924   - self.interactor.Render()
  922 + Publisher.sendMessage('Set ball reference position', (wx, wy, wz))
925 923  
926 924 def ScrollSlice(self, coord):
927 925 if self.orientation == "AXIAL":
... ... @@ -1165,8 +1163,8 @@ class Viewer(wx.Panel):
1165 1163 self.orientation))
1166 1164 Publisher.subscribe(self.__update_cross_position,
1167 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 1169 # Publisher.subscribe(self.ChangeBrushColour,
1172 1170 # 'Add mask')
... ...
invesalius/data/viewer_volume.py
... ... @@ -22,7 +22,8 @@
22 22  
23 23 import sys
24 24  
25   -import numpy
  25 +import numpy as np
  26 +from numpy.core.umath_tests import inner1d
26 27 import wx
27 28 import vtk
28 29 from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
... ... @@ -45,10 +46,11 @@ class Viewer(wx.Panel):
45 46  
46 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 54 self.style = style
53 55  
54 56 interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize())
... ... @@ -111,6 +113,9 @@ class Viewer(wx.Panel):
111 113 self.repositioned_coronal_plan = 0
112 114 self.added_actor = 0
113 115  
  116 + self.camera_state = True
  117 +
  118 + self.ball_actor = None
114 119 self._mode_cross = False
115 120 self._to_show_ball = 0
116 121 self._ball_ref_visibility = False
... ... @@ -153,6 +158,7 @@ class Viewer(wx.Panel):
153 158  
154 159 Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range')
155 160 Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume')
  161 + Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state')
156 162  
157 163 Publisher.subscribe(self.OnEnableStyle, 'Enable style')
158 164 Publisher.subscribe(self.OnDisableStyle, 'Disable style')
... ... @@ -174,23 +180,24 @@ class Viewer(wx.Panel):
174 180 Publisher.subscribe(self.OnStartSeed,'Create surface by seeding - start')
175 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 183 Publisher.subscribe(self.SetStereoMode, 'Set stereo mode')
186 184  
187 185 Publisher.subscribe(self.Reposition3DPlane, 'Reposition 3D Plane')
188 186  
189 187 Publisher.subscribe(self.RemoveVolume, 'Remove Volume')
190 188  
  189 + Publisher.subscribe(self.SetBallReferencePosition,
  190 + 'Set ball reference position')
191 191 Publisher.subscribe(self._check_ball_reference, 'Enable style')
192 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 201 def SetStereoMode(self, pubsub_evt):
195 202 mode = pubsub_evt.data
196 203 ren_win = self.interactor.GetRenderWindow()
... ... @@ -220,43 +227,6 @@ class Viewer(wx.Panel):
220 227  
221 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 230 def _check_ball_reference(self, pubsub_evt):
261 231 st = pubsub_evt.data
262 232 if st == const.SLICE_STATE_CROSS:
... ... @@ -279,19 +249,6 @@ class Viewer(wx.Panel):
279 249 self._to_show_ball -= 1
280 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 252 def OnStartSeed(self, pubsub_evt):
296 253 index = pubsub_evt.data
297 254 self.seed_points = []
... ... @@ -402,7 +359,6 @@ class Viewer(wx.Panel):
402 359 actor.PickableOff()
403 360  
404 361 self.ren.AddActor(actor)
405   -
406 362 self.points_reference.append(actor)
407 363  
408 364 def RemoveAllPointsReference(self):
... ... @@ -418,6 +374,113 @@ class Viewer(wx.Panel):
418 374 actor = self.points_reference.pop(point)
419 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 484 def __bind_events_wx(self):
422 485 #self.Bind(wx.EVT_SIZE, self.OnSize)
423 486 pass
... ... @@ -580,30 +643,38 @@ class Viewer(wx.Panel):
580 643 self.ren.ResetCamera()
581 644 self.ren.ResetCameraClippingRange()
582 645  
  646 + def SetVolumeCameraState(self, pubsub_evt):
  647 + self.camera_state = pubsub_evt.data
  648 +
583 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 679 def OnExportSurface(self, pubsub_evt):
609 680 filename, filetype = pubsub_evt.data
... ... @@ -706,19 +777,18 @@ class Viewer(wx.Panel):
706 777 self.interactor.Render()
707 778 self._to_show_ball -= 1
708 779 self._check_and_set_ball_visibility()
709   -
  780 +
710 781 def RemoveAllActor(self, pubsub_evt):
711 782 utils.debug("RemoveAllActor")
712 783 self.ren.RemoveAllProps()
713 784 Publisher.sendMessage('Render volume viewer')
714 785  
715   -
716 786 def LoadSlicePlane(self, pubsub_evt):
717 787 self.slice_plane = SlicePlane()
718 788  
719 789 def LoadVolume(self, pubsub_evt):
720 790 self.raycasting_volume = True
721   - #self._to_show_ball += 1
  791 + self._to_show_ball += 1
722 792  
723 793 volume = pubsub_evt.data[0]
724 794 colour = pubsub_evt.data[1]
... ... @@ -895,14 +965,16 @@ class Viewer(wx.Panel):
895 965 self.repositioned_coronal_plan = 1
896 966  
897 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 970 if self._mode_cross:
899 971 if self._to_show_ball > 0 and not self._ball_ref_visibility:
900   - self.ActivateBallReference(None)
  972 + self.ActivateBallReference()
901 973 self.interactor.Render()
902 974 elif not self._to_show_ball and self._ball_ref_visibility:
903 975 self.RemoveBallReference()
904 976 self.interactor.Render()
905   -
  977 +
906 978  
907 979 class SlicePlane:
908 980 def __init__(self):
... ...
invesalius/gui/default_tasks.py
... ... @@ -250,7 +250,8 @@ class UpperTaskPanel(wx.Panel):
250 250 tasks = [(_("Load data"), importer.TaskPanel),
251 251 (_("Select region of interest"), slice_.TaskPanel),
252 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 256 for i in xrange(len(tasks)):
256 257 (name, panel) = tasks[i]
... ...
invesalius/gui/dialogs.py
... ... @@ -22,8 +22,11 @@ import os
22 22 import random
23 23 import sys
24 24  
  25 +import vtk
25 26 import wx
26 27 import wx.combo
  28 +
  29 +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor
27 30 from wx.lib import masked
28 31 from wx.lib.agw import floatspin
29 32 from wx.lib.wordwrap import wordwrap
... ... @@ -206,48 +209,27 @@ class ProgressDialog(object):
206 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 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 228 # Default system path
247 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 231 defaultDir="",
250   - defaultFile="", wildcard=WILDCARD_ANALYZE,
  232 + defaultFile="", wildcard=WILDCARD_OPEN,
251 233 style=wx.FD_OPEN|wx.FD_CHANGE_DIR)
252 234  
253 235 # inv3 filter is default
... ... @@ -260,7 +242,7 @@ def ShowOpenAnalyzeDialog():
260 242 if dlg.ShowModal() == wx.ID_OK:
261 243 # This returns a Python list of files that were selected.
262 244 filepath = dlg.GetPath()
263   - except(wx._core.PyAssertionError): #FIX: win64
  245 + except(wx._core.PyAssertionError): # FIX: win64
264 246 filepath = dlg.GetPath()
265 247  
266 248 # Destroy the dialog. Don't do this until you are done with it!
... ... @@ -353,6 +335,47 @@ def ShowImportBitmapDirDialog():
353 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 379 def ShowSaveAsProjectDialog(default_filename=None):
357 380 current_dir = os.path.abspath(".")
358 381 dlg = wx.FileDialog(None,
... ... @@ -380,10 +403,70 @@ def ShowSaveAsProjectDialog(default_filename=None):
380 403 if filename.split(".")[-1] != extension:
381 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 438 os.chdir(current_dir)
385 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 470 class MessageDialog(wx.Dialog):
388 471 def __init__(self, message):
389 472 pre = wx.PreDialog()
... ... @@ -509,6 +592,7 @@ def ImportEmptyDirectory(dirpath):
509 592 dlg.ShowModal()
510 593 dlg.Destroy()
511 594  
  595 +
512 596 def ImportInvalidFiles(ftype="DICOM"):
513 597 if ftype == "Bitmap":
514 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 608 dlg.ShowModal()
525 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 625 def InexistentMask():
528 626 msg = _("A mask is needed to create a surface.")
529 627 if sys.platform == 'darwin':
... ... @@ -593,6 +691,86 @@ def SurfaceSelectionRequiredForDuplication():
593 691 dlg.ShowModal()
594 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 774 class NewMask(wx.Dialog):
597 775 def __init__(self,
598 776 parent=None,
... ... @@ -828,6 +1006,8 @@ def ShowAboutDialog(parent):
828 1006 info.Developers = ["Paulo Henrique Junqueira Amorim",
829 1007 "Thiago Franco de Moraes",
830 1008 "Jorge Vicente Lopes da Silva",
  1009 + "Victor Hugo de Oliveira e Souza (navigator)",
  1010 + "Renan Hiroshi Matsuda (navigator)"
831 1011 "Tatiana Al-Chueyr (former)",
832 1012 "Guilherme Cesar Soares Ruppert (former)",
833 1013 "Fabio de Souza Azevedo (former)",
... ...
invesalius/gui/frame.py
... ... @@ -396,7 +396,11 @@ class Frame(wx.Frame):
396 396 elif id == const.ID_PROJECT_OPEN:
397 397 self.ShowOpenProject()
398 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 404 elif id == const.ID_TIFF_JPG_PNG:
401 405 self.ShowBitmapImporter()
402 406 elif id == const.ID_PROJECT_SAVE:
... ... @@ -538,6 +542,12 @@ class Frame(wx.Frame):
538 542 Show import DICOM panel. as dicom """
539 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 551 def ShowRetrieveDicomPanel(self):
542 552 Publisher.sendMessage('Show retrieve dicom panel')
543 553  
... ... @@ -553,12 +563,6 @@ class Frame(wx.Frame):
553 563 """
554 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 566 def ShowBitmapImporter(self):
563 567 """
564 568 Tiff, BMP, JPEG and PNG
... ... @@ -672,7 +676,9 @@ class MenuBar(wx.MenuBar):
672 676  
673 677 #Import Others Files
674 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 682 others_file_menu.Append(const.ID_TIFF_JPG_PNG, u"TIFF,BMP,JPG or PNG (\xb5CT)")
677 683  
678 684 # FILE
... ...
invesalius/gui/task_importer.py
... ... @@ -64,8 +64,8 @@ class InnerTaskPanel(wx.Panel):
64 64 self.float_hyper_list = []
65 65  
66 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 69 link_import_local.SetUnderlines(False, False, False)
70 70 link_import_local.SetBold(True)
71 71 link_import_local.SetColours("BLACK", "BLACK", "BLACK")
... ...
invesalius/gui/task_navigator.py
... ... @@ -17,433 +17,727 @@
17 17 # detalhes.
18 18 #--------------------------------------------------------------------------
19 19  
20   -import os
  20 +from functools import partial
21 21 import sys
22 22  
23   -import serial
  23 +import numpy as np
24 24 import wx
25 25 import wx.lib.hyperlink as hl
26 26 import wx.lib.masked.numctrl
27   -import wx.lib.platebtn as pbtn
28 27 from wx.lib.pubsub import pub as Publisher
29 28  
  29 +import invesalius.constants as const
30 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 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 41 def __init__(self, parent):
49   - # note: don't change this class!!!
50 42 wx.Panel.__init__(self, parent)
51 43  
52 44 inner_panel = InnerTaskPanel(self)
53 45  
54 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 49 sizer.Fit(self)
58 50  
59 51 self.SetSizer(sizer)
60 52 self.Update()
61 53 self.SetAutoLayout(1)
62 54  
  55 +
63 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 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 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 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 308 self.sizer = main_sizer
223 309 self.SetSizer(main_sizer)
224 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 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 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 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   -#--------------------------------------------------------------------------
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 @@
  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 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 @@
  1 +This folder contains all the calibration files for the installed MicronTrackers
0 2 \ No newline at end of file
... ...
navigation/mtc_files/Markers/1Probe 0 → 100644
... ... @@ -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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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.
... ...