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