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