Commit 794ea5034b68ff1c8de8138d88d266bfd9bfbf85
Committed by
GitHub
1 parent
335b3a7c
Exists in
master
Micro ct (#40)
ADD: This branch is for InVesalius to read BMP, TIF, JPEG and PNG files.
Showing
10 changed files
with
2268 additions
and
11 deletions
Show diff stats
invesalius/constants.py
... | ... | @@ -452,7 +452,7 @@ VTK_WARNING = 0 |
452 | 452 | [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE, |
453 | 453 | ID_PROJECT_CLOSE, ID_PROJECT_INFO, ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET, |
454 | 454 | ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_ANALYZE_IMPORT, ID_PREFERENCES, |
455 | -ID_DICOM_NETWORK] = [wx.NewId() for number in range(13)] | |
455 | +ID_DICOM_NETWORK, ID_TIFF_JPG_PNG] = [wx.NewId() for number in range(14)] | |
456 | 456 | ID_EXIT = wx.ID_EXIT |
457 | 457 | ID_ABOUT = wx.ID_ABOUT |
458 | 458 | ... | ... |
invesalius/control.py
... | ... | @@ -35,8 +35,10 @@ import project as prj |
35 | 35 | import reader.analyze_reader as analyze |
36 | 36 | import reader.dicom_grouper as dg |
37 | 37 | import reader.dicom_reader as dcm |
38 | +import reader.bitmap_reader as bmp | |
38 | 39 | import session as ses |
39 | 40 | |
41 | + | |
40 | 42 | import utils |
41 | 43 | import gui.dialogs as dialogs |
42 | 44 | import subprocess |
... | ... | @@ -74,13 +76,20 @@ class Controller(): |
74 | 76 | 'Save raycasting preset') |
75 | 77 | Publisher.subscribe(self.OnOpenDicomGroup, |
76 | 78 | 'Open DICOM group') |
79 | + Publisher.subscribe(self.OnOpenBitmapFiles, | |
80 | + 'Open bitmap files') | |
77 | 81 | Publisher.subscribe(self.Progress, "Update dicom load") |
82 | + Publisher.subscribe(self.Progress, "Update bitmap load") | |
78 | 83 | Publisher.subscribe(self.OnLoadImportPanel, "End dicom load") |
84 | + Publisher.subscribe(self.OnLoadImportBitmapPanel, "End bitmap load") | |
79 | 85 | Publisher.subscribe(self.OnCancelImport, 'Cancel DICOM load') |
86 | + Publisher.subscribe(self.OnCancelImportBitmap, 'Cancel bitmap load') | |
87 | + | |
80 | 88 | Publisher.subscribe(self.OnShowDialogCloseProject, 'Close Project') |
81 | 89 | Publisher.subscribe(self.OnOpenProject, 'Open project') |
82 | 90 | Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project') |
83 | 91 | Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog') |
92 | + Publisher.subscribe(self.OnShowBitmapFile, 'Show bitmap dialog') | |
84 | 93 | |
85 | 94 | Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog') |
86 | 95 | |
... | ... | @@ -92,7 +101,9 @@ class Controller(): |
92 | 101 | Publisher.sendMessage('Hide import panel') |
93 | 102 | |
94 | 103 | |
95 | - | |
104 | + def OnCancelImportBitmap(self, pubsub_evt): | |
105 | + #self.cancel_import = True | |
106 | + Publisher.sendMessage('Hide import bitmap panel') | |
96 | 107 | |
97 | 108 | ########################### |
98 | 109 | ########################### |
... | ... | @@ -119,9 +130,35 @@ class Controller(): |
119 | 130 | self.LoadProject() |
120 | 131 | Publisher.sendMessage("Enable state project", True) |
121 | 132 | |
122 | - | |
133 | + def OnShowBitmapFile(self, pubsub_evt): | |
134 | + self.ShowDialogImportBitmapFile() | |
123 | 135 | ########################### |
124 | 136 | |
137 | + def ShowDialogImportBitmapFile(self): | |
138 | + # Offer to save current project if necessary | |
139 | + session = ses.Session() | |
140 | + st = session.project_status | |
141 | + if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE): | |
142 | + filename = session.project_path[1] | |
143 | + answer = dialog.SaveChangesDialog2(filename) | |
144 | + if answer: | |
145 | + self.ShowDialogSaveProject() | |
146 | + self.CloseProject() | |
147 | + #Publisher.sendMessage("Enable state project", False) | |
148 | + Publisher.sendMessage('Set project name') | |
149 | + Publisher.sendMessage("Stop Config Recording") | |
150 | + Publisher.sendMessage("Set slice interaction style", const.STATE_DEFAULT) | |
151 | + | |
152 | + # Import TIFF, BMP, JPEG or PNG | |
153 | + dirpath = dialog.ShowImportBitmapDirDialog() | |
154 | + | |
155 | + if dirpath and not os.listdir(dirpath): | |
156 | + dialog.ImportEmptyDirectory(dirpath) | |
157 | + elif dirpath: | |
158 | + self.StartImportBitmapPanel(dirpath) | |
159 | + # Publisher.sendMessage("Load data to import panel", dirpath) | |
160 | + | |
161 | + | |
125 | 162 | def ShowDialogImportDirectory(self): |
126 | 163 | # Offer to save current project if necessary |
127 | 164 | session = ses.Session() |
... | ... | @@ -291,6 +328,12 @@ class Controller(): |
291 | 328 | |
292 | 329 | ########################### |
293 | 330 | |
331 | + def StartImportBitmapPanel(self, path): | |
332 | + # retrieve DICOM files splited into groups | |
333 | + reader = bmp.ProgressBitmapReader() | |
334 | + reader.SetWindowEvent(self.frame) | |
335 | + reader.SetDirectoryPath(path) | |
336 | + Publisher.sendMessage('End busy cursor') | |
294 | 337 | |
295 | 338 | def StartImportPanel(self, path): |
296 | 339 | |
... | ... | @@ -324,6 +367,26 @@ class Controller(): |
324 | 367 | Publisher.sendMessage('Show import panel') |
325 | 368 | Publisher.sendMessage("Show import panel in frame") |
326 | 369 | |
370 | + def OnLoadImportBitmapPanel(self, evt): | |
371 | + data = evt.data | |
372 | + ok = self.LoadImportBitmapPanel(data) | |
373 | + if ok: | |
374 | + Publisher.sendMessage('Show import bitmap panel in frame') | |
375 | + #Publisher.sendMessage("Show import panel in frame") | |
376 | + | |
377 | + def LoadImportBitmapPanel(self, data): | |
378 | + #if patient_series and isinstance(patient_series, list): | |
379 | + #Publisher.sendMessage("Load import panel", patient_series) | |
380 | + #first_patient = patient_series[0] | |
381 | + #Publisher.sendMessage("Load bitmap preview", first_patient) | |
382 | + if data: | |
383 | + Publisher.sendMessage("Load import bitmap panel", data) | |
384 | + | |
385 | + return True | |
386 | + else: | |
387 | + dialog.ImportInvalidFiles() | |
388 | + return False | |
389 | + | |
327 | 390 | |
328 | 391 | def LoadImportPanel(self, patient_series): |
329 | 392 | if patient_series and isinstance(patient_series, list): |
... | ... | @@ -335,6 +398,9 @@ class Controller(): |
335 | 398 | dialog.ImportInvalidFiles() |
336 | 399 | return False |
337 | 400 | |
401 | + | |
402 | + #----------- to import by command line --------------------------------------------------- | |
403 | + | |
338 | 404 | def OnImportMedicalImages(self, pubsub_evt): |
339 | 405 | directory = pubsub_evt.data |
340 | 406 | self.ImportMedicalImages(directory) |
... | ... | @@ -358,6 +424,8 @@ class Controller(): |
358 | 424 | self.LoadProject() |
359 | 425 | Publisher.sendMessage("Enable state project", True) |
360 | 426 | |
427 | + #------------------------------------------------------------------------------------- | |
428 | + | |
361 | 429 | def LoadProject(self): |
362 | 430 | proj = prj.Project() |
363 | 431 | |
... | ... | @@ -401,9 +469,13 @@ class Controller(): |
401 | 469 | Publisher.sendMessage('Show mask', (mask_index, True)) |
402 | 470 | else: |
403 | 471 | mask_name = const.MASK_NAME_PATTERN % (1,) |
404 | - thresh = const.THRESHOLD_RANGE | |
405 | - colour = const.MASK_COLOUR[0] | |
406 | 472 | |
473 | + if proj.modality != "UNKNOWN": | |
474 | + thresh = const.THRESHOLD_RANGE | |
475 | + else: | |
476 | + thresh = proj.threshold_range | |
477 | + | |
478 | + colour = const.MASK_COLOUR[0] | |
407 | 479 | Publisher.sendMessage('Create new mask', |
408 | 480 | (mask_name, thresh, colour)) |
409 | 481 | |
... | ... | @@ -483,6 +555,138 @@ class Controller(): |
483 | 555 | dirpath = session.CreateProject(filename) |
484 | 556 | #proj.SavePlistProject(dirpath, filename) |
485 | 557 | |
558 | + | |
559 | + def CreateBitmapProject(self, bmp_data, rec_data, matrix, matrix_filename): | |
560 | + name_to_const = {"AXIAL":const.AXIAL, | |
561 | + "CORONAL":const.CORONAL, | |
562 | + "SAGITTAL":const.SAGITAL} | |
563 | + | |
564 | + name = rec_data[0] | |
565 | + orientation = rec_data[1] | |
566 | + sp_x = float(rec_data[2]) | |
567 | + sp_y = float(rec_data[3]) | |
568 | + sp_z = float(rec_data[4]) | |
569 | + interval = int(rec_data[5]) | |
570 | + | |
571 | + bits = bmp_data.GetFirstPixelSize() | |
572 | + sx, sy = size = bmp_data.GetFirstBitmapSize() | |
573 | + | |
574 | + proj = prj.Project() | |
575 | + proj.name = name | |
576 | + proj.modality = 'UNKNOWN' | |
577 | + proj.SetAcquisitionModality(proj.modality) | |
578 | + proj.matrix_shape = matrix.shape | |
579 | + proj.matrix_dtype = matrix.dtype.name | |
580 | + proj.matrix_filename = matrix_filename | |
581 | + #proj.imagedata = imagedata | |
582 | + #proj.dicom_sample = dicom | |
583 | + proj.original_orientation =\ | |
584 | + name_to_const[orientation.upper()] | |
585 | + proj.window = float(matrix.max()) | |
586 | + proj.level = float(matrix.max()/2) | |
587 | + | |
588 | + proj.threshold_range = int(matrix.min()), int(matrix.max()) | |
589 | + #const.THRESHOLD_RANGE = proj.threshold_range | |
590 | + | |
591 | + proj.spacing = self.Slice.spacing | |
592 | + | |
593 | + ###### | |
594 | + session = ses.Session() | |
595 | + filename = proj.name+".inv3" | |
596 | + | |
597 | + filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM | |
598 | + | |
599 | + dirpath = session.CreateProject(filename) | |
600 | + | |
601 | + def OnOpenBitmapFiles(self, pubsub_evt): | |
602 | + rec_data = pubsub_evt.data | |
603 | + bmp_data = bmp.BitmapData() | |
604 | + matrix, matrix_filename = self.OpenBitmapFiles(bmp_data, rec_data) | |
605 | + | |
606 | + self.CreateBitmapProject(bmp_data, rec_data, matrix, matrix_filename) | |
607 | + | |
608 | + self.LoadProject() | |
609 | + Publisher.sendMessage("Enable state project", True) | |
610 | + | |
611 | + | |
612 | + def OpenBitmapFiles(self, bmp_data, rec_data): | |
613 | + | |
614 | + if bmp_data.IsAllBitmapSameSize(): | |
615 | + | |
616 | + name = rec_data[0] | |
617 | + orientation = rec_data[1] | |
618 | + sp_x = float(rec_data[2]) | |
619 | + sp_y = float(rec_data[3]) | |
620 | + sp_z = float(rec_data[4]) | |
621 | + interval = int(rec_data[5]) | |
622 | + | |
623 | + interval += 1 | |
624 | + | |
625 | + filelist = bmp_data.GetOnlyBitmapPath()[::interval] | |
626 | + bits = bmp_data.GetFirstPixelSize() | |
627 | + | |
628 | + sx, sy = size = bmp_data.GetFirstBitmapSize() | |
629 | + n_slices = len(filelist) | |
630 | + resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8) | |
631 | + | |
632 | + zspacing = sp_z * interval | |
633 | + xyspacing = (sp_y, sp_x) | |
634 | + | |
635 | + if resolution_percentage < 1.0: | |
636 | + | |
637 | + re_dialog = dialog.ResizeImageDialog() | |
638 | + re_dialog.SetValue(int(resolution_percentage*100)) | |
639 | + re_dialog_value = re_dialog.ShowModal() | |
640 | + re_dialog.Close() | |
641 | + | |
642 | + if re_dialog_value == wx.ID_OK: | |
643 | + percentage = re_dialog.GetValue() | |
644 | + resolution_percentage = percentage / 100.0 | |
645 | + else: | |
646 | + return | |
647 | + | |
648 | + xyspacing = xyspacing[0] / resolution_percentage, xyspacing[1] / resolution_percentage | |
649 | + | |
650 | + | |
651 | + | |
652 | + self.matrix, scalar_range, self.filename = image_utils.bitmap2memmap(filelist, size, | |
653 | + orientation, (sp_z, sp_y, sp_x),resolution_percentage) | |
654 | + | |
655 | + | |
656 | + self.Slice = sl.Slice() | |
657 | + self.Slice.matrix = self.matrix | |
658 | + self.Slice.matrix_filename = self.filename | |
659 | + | |
660 | + if orientation == 'AXIAL': | |
661 | + self.Slice.spacing = xyspacing[0], xyspacing[1], zspacing | |
662 | + elif orientation == 'CORONAL': | |
663 | + self.Slice.spacing = xyspacing[0], zspacing, xyspacing[1] | |
664 | + elif orientation == 'SAGITTAL': | |
665 | + self.Slice.spacing = zspacing, xyspacing[1], xyspacing[0] | |
666 | + | |
667 | + # 1(a): Fix gantry tilt, if any | |
668 | + #tilt_value = dicom.acquisition.tilt | |
669 | + #if (tilt_value) and (gui): | |
670 | + # # Tell user gantry tilt and fix, according to answer | |
671 | + # message = _("Fix gantry tilt applying the degrees below") | |
672 | + # value = -1*tilt_value | |
673 | + # tilt_value = dialog.ShowNumberDialog(message, value) | |
674 | + # image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) | |
675 | + #elif (tilt_value) and not (gui): | |
676 | + # tilt_value = -1*tilt_value | |
677 | + # image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) | |
678 | + | |
679 | + self.Slice.window_level = float(self.matrix.max()/2) | |
680 | + self.Slice.window_width = float(self.matrix.max()) | |
681 | + | |
682 | + scalar_range = int(self.matrix.min()), int(self.matrix.max()) | |
683 | + Publisher.sendMessage('Update threshold limits list', scalar_range) | |
684 | + | |
685 | + return self.matrix, self.filename#, dicom | |
686 | + | |
687 | + else: | |
688 | + print "Error: All slices must be of the same size." | |
689 | + | |
486 | 690 | def OnOpenDicomGroup(self, pubsub_evt): |
487 | 691 | group, interval, file_range = pubsub_evt.data |
488 | 692 | matrix, matrix_filename, dicom = self.OpenDicomGroup(group, interval, file_range, gui=True) | ... | ... |
invesalius/data/imagedata_utils.py
... | ... | @@ -32,7 +32,9 @@ from vtk.util import numpy_support |
32 | 32 | |
33 | 33 | import constants as const |
34 | 34 | from data import vtk_utils |
35 | +from reader import bitmap_reader | |
35 | 36 | import utils |
37 | +import converters | |
36 | 38 | |
37 | 39 | # TODO: Test cases which are originally in sagittal/coronal orientation |
38 | 40 | # and have gantry |
... | ... | @@ -416,6 +418,90 @@ class ImageCreator: |
416 | 418 | |
417 | 419 | return imagedata |
418 | 420 | |
421 | +def bitmap2memmap(files, slice_size, orientation, spacing, resolution_percentage): | |
422 | + """ | |
423 | + From a list of dicom files it creates memmap file in the temp folder and | |
424 | + returns it and its related filename. | |
425 | + """ | |
426 | + message = _("Generating multiplanar visualization...") | |
427 | + update_progress= vtk_utils.ShowProgress(len(files) - 1, dialog_type = "ProgressDialog") | |
428 | + | |
429 | + temp_file = tempfile.mktemp() | |
430 | + | |
431 | + if orientation == 'SAGITTAL': | |
432 | + if resolution_percentage == 1.0: | |
433 | + shape = slice_size[0], slice_size[1], len(files) | |
434 | + else: | |
435 | + shape = math.ceil(slice_size[0]*resolution_percentage),\ | |
436 | + math.ceil(slice_size[1]*resolution_percentage), len(files) | |
437 | + | |
438 | + elif orientation == 'CORONAL': | |
439 | + if resolution_percentage == 1.0: | |
440 | + shape = slice_size[1], len(files), slice_size[0] | |
441 | + else: | |
442 | + shape = math.ceil(slice_size[1]*resolution_percentage), len(files),\ | |
443 | + math.ceil(slice_size[0]*resolution_percentage) | |
444 | + else: | |
445 | + if resolution_percentage == 1.0: | |
446 | + shape = len(files), slice_size[1], slice_size[0] | |
447 | + else: | |
448 | + shape = len(files), math.ceil(slice_size[1]*resolution_percentage),\ | |
449 | + math.ceil(slice_size[0]*resolution_percentage) | |
450 | + | |
451 | + matrix = numpy.memmap(temp_file, mode='w+', dtype='int16', shape=shape) | |
452 | + cont = 0 | |
453 | + max_scalar = None | |
454 | + min_scalar = None | |
455 | + | |
456 | + for n, f in enumerate(files): | |
457 | + image_as_array = bitmap_reader.ReadBitmap(f) | |
458 | + | |
459 | + image = converters.to_vtk(image_as_array, spacing=spacing,\ | |
460 | + slice_number=1, orientation=orientation.upper()) | |
461 | + | |
462 | + if resolution_percentage != 1.0: | |
463 | + | |
464 | + | |
465 | + image_resized = ResampleImage2D(image, px=None, py=None,\ | |
466 | + resolution_percentage = resolution_percentage, update_progress = None) | |
467 | + | |
468 | + image = image_resized | |
469 | + | |
470 | + min_aux, max_aux = image.GetScalarRange() | |
471 | + if min_scalar is None or min_aux < min_scalar: | |
472 | + min_scalar = min_aux | |
473 | + | |
474 | + if max_scalar is None or max_aux > max_scalar: | |
475 | + max_scalar = max_aux | |
476 | + | |
477 | + array = numpy_support.vtk_to_numpy(image.GetPointData().GetScalars()) | |
478 | + array = array.astype("int16") | |
479 | + | |
480 | + array = image_as_array | |
481 | + | |
482 | + if orientation == 'CORONAL': | |
483 | + array.shape = matrix.shape[0], matrix.shape[2] | |
484 | + matrix[:, n, :] = array | |
485 | + elif orientation == 'SAGITTAL': | |
486 | + array.shape = matrix.shape[0], matrix.shape[1] | |
487 | + # TODO: Verify if it's necessary to add the slices swapped only in | |
488 | + # sagittal rmi or only in # Rasiane's case or is necessary in all | |
489 | + # sagittal cases. | |
490 | + matrix[:, :, n] = array | |
491 | + else: | |
492 | + print array.shape, matrix.shape | |
493 | + array.shape = matrix.shape[1], matrix.shape[2] | |
494 | + matrix[n] = array | |
495 | + update_progress(cont,message) | |
496 | + cont += 1 | |
497 | + | |
498 | + matrix.flush() | |
499 | + scalar_range = min_scalar, max_scalar | |
500 | + | |
501 | + return matrix, scalar_range, temp_file | |
502 | + | |
503 | + | |
504 | + | |
419 | 505 | def dcm2memmap(files, slice_size, orientation, resolution_percentage): |
420 | 506 | """ |
421 | 507 | From a list of dicom files it creates memmap file in the temp folder and |
... | ... | @@ -462,7 +548,6 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage): |
462 | 548 | resolution_percentage = resolution_percentage, update_progress = None) |
463 | 549 | |
464 | 550 | image = image_resized |
465 | - print ">>>>>>>>>", image.GetDimensions() | |
466 | 551 | |
467 | 552 | min_aux, max_aux = image.GetScalarRange() |
468 | 553 | if min_scalar is None or min_aux < min_scalar: |
... | ... | @@ -482,7 +567,6 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage): |
482 | 567 | # sagittal cases. |
483 | 568 | matrix[:, :, n] = array |
484 | 569 | else: |
485 | - print array.shape, matrix.shape | |
486 | 570 | array.shape = matrix.shape[1], matrix.shape[2] |
487 | 571 | matrix[n] = array |
488 | 572 | update_progress(cont,message) | ... | ... |
... | ... | @@ -0,0 +1,903 @@ |
1 | +import wx | |
2 | +import vtk | |
3 | +import vtkgdcm | |
4 | +import time | |
5 | + | |
6 | +from vtk.util import numpy_support | |
7 | +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor | |
8 | +from wx.lib.pubsub import pub as Publisher | |
9 | + | |
10 | +import constants as const | |
11 | +import data.vtk_utils as vtku | |
12 | +from data import converters | |
13 | +from reader import bitmap_reader | |
14 | +import utils | |
15 | + | |
16 | +NROWS = 3 | |
17 | +NCOLS = 6 | |
18 | +NUM_PREVIEWS = NCOLS*NROWS | |
19 | +PREVIEW_WIDTH = 70 | |
20 | +PREVIEW_HEIGTH = 70 | |
21 | + | |
22 | +PREVIEW_BACKGROUND = (255, 255, 255) # White | |
23 | + | |
24 | +STR_SIZE = _("Image size: %d x %d") | |
25 | +STR_SPC = _("Spacing: %.2f") | |
26 | +STR_LOCAL = _("Location: %.2f") | |
27 | +STR_PATIENT = "%s\n%s" | |
28 | +STR_ACQ = _("%s %s\nMade in InVesalius") | |
29 | + | |
30 | +myEVT_PREVIEW_CLICK = wx.NewEventType() | |
31 | +EVT_PREVIEW_CLICK = wx.PyEventBinder(myEVT_PREVIEW_CLICK, 1) | |
32 | + | |
33 | +myEVT_PREVIEW_DBLCLICK = wx.NewEventType() | |
34 | +EVT_PREVIEW_DBLCLICK = wx.PyEventBinder(myEVT_PREVIEW_DBLCLICK, 1) | |
35 | + | |
36 | +myEVT_CLICK_SLICE = wx.NewEventType() | |
37 | +# This event occurs when the user select a preview | |
38 | +EVT_CLICK_SLICE = wx.PyEventBinder(myEVT_CLICK_SLICE, 1) | |
39 | + | |
40 | +myEVT_CLICK_SERIE = wx.NewEventType() | |
41 | +# This event occurs when the user select a preview | |
42 | +EVT_CLICK_SERIE = wx.PyEventBinder(myEVT_CLICK_SERIE, 1) | |
43 | + | |
44 | +myEVT_CLICK = wx.NewEventType() | |
45 | +EVT_CLICK = wx.PyEventBinder(myEVT_CLICK, 1) | |
46 | + | |
47 | + | |
48 | +class SelectionEvent(wx.PyCommandEvent): | |
49 | + pass | |
50 | + | |
51 | + | |
52 | +class PreviewEvent(wx.PyCommandEvent): | |
53 | + def __init__(self , evtType, id): | |
54 | + super(PreviewEvent, self).__init__(evtType, id) | |
55 | + | |
56 | + def GetSelectID(self): | |
57 | + return self.SelectedID | |
58 | + | |
59 | + def SetSelectedID(self, id): | |
60 | + self.SelectedID = id | |
61 | + | |
62 | + def GetItemData(self): | |
63 | + return self.data | |
64 | + | |
65 | + def GetPressedShift(self): | |
66 | + return self.pressed_shift | |
67 | + | |
68 | + def SetItemData(self, data): | |
69 | + self.data = data | |
70 | + | |
71 | + def SetShiftStatus(self, status): | |
72 | + self.pressed_shift = status | |
73 | + | |
74 | + | |
75 | +class SerieEvent(PreviewEvent): | |
76 | + def __init__(self , evtType, id): | |
77 | + super(SerieEvent, self).__init__(evtType, id) | |
78 | + | |
79 | + | |
80 | +class BitmapInfo(object): | |
81 | + """ | |
82 | + Keep the informations and the image used by preview. | |
83 | + """ | |
84 | + def __init__(self, data): | |
85 | + #self.id = id | |
86 | + self.id = data[7] | |
87 | + self.title = data[6] | |
88 | + self.data = data | |
89 | + self.pos = data[8] | |
90 | + #self.subtitle = subtitle | |
91 | + self._preview = None | |
92 | + self.selected = False | |
93 | + #self.filename = "" | |
94 | + self.thumbnail_path = data[1] | |
95 | + | |
96 | + @property | |
97 | + def preview(self): | |
98 | + | |
99 | + if not self._preview: | |
100 | + bmp = wx.Bitmap(self.thumbnail_path, wx.BITMAP_TYPE_PNG) | |
101 | + self._preview = bmp.ConvertToImage() | |
102 | + return self._preview | |
103 | + | |
104 | + def release_thumbnail(self): | |
105 | + self._preview = None | |
106 | + | |
107 | +class DicomPaintPanel(wx.Panel): | |
108 | + def __init__(self, parent): | |
109 | + super(DicomPaintPanel, self).__init__(parent) | |
110 | + self._bind_events() | |
111 | + self.image = None | |
112 | + self.last_size = (10,10) | |
113 | + | |
114 | + def _bind_events(self): | |
115 | + self.Bind(wx.EVT_PAINT, self.OnPaint) | |
116 | + self.Bind(wx.EVT_SIZE, self.OnSize) | |
117 | + | |
118 | + def _build_bitmap(self, image): | |
119 | + bmp = wx.BitmapFromImage(image) | |
120 | + return bmp | |
121 | + | |
122 | + def _image_resize(self, image): | |
123 | + self.Update() | |
124 | + self.Layout() | |
125 | + new_size = self.GetSize() | |
126 | + # This is necessary due to darwin problem # | |
127 | + if new_size != (0,0): | |
128 | + self.last_size = new_size | |
129 | + return image.Scale(*new_size) | |
130 | + else: | |
131 | + return image.Scale(*self.last_size) | |
132 | + | |
133 | + def SetImage(self, image): | |
134 | + self.image = image | |
135 | + r_img = self._image_resize(image) | |
136 | + self.bmp = self._build_bitmap(r_img) | |
137 | + self.Refresh() | |
138 | + | |
139 | + def OnPaint(self, evt): | |
140 | + if self.image: | |
141 | + dc = wx.PaintDC(self) | |
142 | + dc.Clear() | |
143 | + dc.DrawBitmap(self.bmp, 0, 0) | |
144 | + | |
145 | + def OnSize(self, evt): | |
146 | + if self.image: | |
147 | + self.bmp = self._build_bitmap(self._image_resize(self.image)) | |
148 | + self.Refresh() | |
149 | + evt.Skip() | |
150 | + | |
151 | + | |
152 | +class Preview(wx.Panel): | |
153 | + """ | |
154 | + The little previews. | |
155 | + """ | |
156 | + def __init__(self, parent): | |
157 | + super(Preview, self).__init__(parent) | |
158 | + # Will it be white? | |
159 | + self.select_on = False | |
160 | + self.bitmap_info = None | |
161 | + self._init_ui() | |
162 | + self._bind_events() | |
163 | + | |
164 | + def _init_ui(self): | |
165 | + self.SetBackgroundColour(PREVIEW_BACKGROUND) | |
166 | + | |
167 | + self.title = wx.StaticText(self, -1, _("Image")) | |
168 | + self.subtitle = wx.StaticText(self, -1, _("Image")) | |
169 | + self.image_viewer = DicomPaintPanel(self) | |
170 | + | |
171 | + self.sizer = wx.BoxSizer(wx.VERTICAL) | |
172 | + self.sizer.Add(self.title, 0, | |
173 | + wx.ALIGN_CENTER_HORIZONTAL) | |
174 | + self.sizer.Add(self.subtitle, 0, | |
175 | + wx.ALIGN_CENTER_HORIZONTAL) | |
176 | + self.sizer.Add(self.image_viewer, 1, wx.ALIGN_CENTRE_HORIZONTAL \ | |
177 | + | wx.SHAPED | wx.ALL, 5) | |
178 | + self.sizer.Fit(self) | |
179 | + | |
180 | + self.SetSizer(self.sizer) | |
181 | + | |
182 | + self.Layout() | |
183 | + self.Update() | |
184 | + self.Fit() | |
185 | + self.SetAutoLayout(1) | |
186 | + | |
187 | + def _bind_events(self): | |
188 | + self.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) | |
189 | + #self.interactor.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) | |
190 | + #self.panel.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) | |
191 | + #self.title.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) | |
192 | + #self.subtitle.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) | |
193 | + | |
194 | + self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) | |
195 | + #self.interactor.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) | |
196 | + #self.panel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) | |
197 | + #self.title.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) | |
198 | + #self.subtitle.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) | |
199 | + | |
200 | + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) | |
201 | + #self.interactor.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) | |
202 | + #self.panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) | |
203 | + #self.title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) | |
204 | + #self.subtitle.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) | |
205 | + | |
206 | + self.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) | |
207 | + self.title.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) | |
208 | + self.subtitle.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) | |
209 | + self.image_viewer.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) | |
210 | + | |
211 | + #self.Bind(wx.EVT_SIZE, self.OnSize) | |
212 | + | |
213 | + def SetBitmapToPreview(self, bitmap_info): | |
214 | + """ | |
215 | + Set a dicom to preview. | |
216 | + """ | |
217 | + | |
218 | + """ | |
219 | + self.dicom_info = dicom_info | |
220 | + self.SetTitle(dicom_info.title) | |
221 | + self.SetSubtitle(dicom_info.subtitle) | |
222 | + self.ID = dicom_info.id | |
223 | + dicom_info.size = self.image_viewer.GetSize() | |
224 | + image = dicom_info.preview | |
225 | + self.image_viewer.SetImage(image) | |
226 | + self.data = dicom_info.id | |
227 | + self.select_on = dicom_info.selected | |
228 | + self.Select() | |
229 | + self.Update() | |
230 | + """ | |
231 | + | |
232 | + | |
233 | + | |
234 | + if self.bitmap_info: | |
235 | + self.bitmap_info.release_thumbnail() | |
236 | + | |
237 | + self.bitmap_info = bitmap_info | |
238 | + self.SetTitle(self.bitmap_info.title[-10:]) | |
239 | + self.SetSubtitle('') | |
240 | + | |
241 | + ##self.ID = bitmap_info.id | |
242 | + ##bitmap_info.size = self.image_viewer.GetSize() | |
243 | + image = self.bitmap_info.preview | |
244 | + | |
245 | + self.image_viewer.SetImage(image) | |
246 | + #self.data = bitmap_info.id | |
247 | + self.select_on = bitmap_info.selected | |
248 | + self.Select() | |
249 | + self.Update() | |
250 | + | |
251 | + def SetTitle(self, title): | |
252 | + self.title.SetLabel(title) | |
253 | + | |
254 | + def SetSubtitle(self, subtitle): | |
255 | + self.subtitle.SetLabel(subtitle) | |
256 | + | |
257 | + def OnEnter(self, evt): | |
258 | + if not self.select_on: | |
259 | + #c = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DHILIGHT) | |
260 | + c = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE) | |
261 | + self.SetBackgroundColour(c) | |
262 | + | |
263 | + def OnLeave(self, evt): | |
264 | + if not self.select_on: | |
265 | + c = (PREVIEW_BACKGROUND) | |
266 | + self.SetBackgroundColour(c) | |
267 | + | |
268 | + def OnSelect(self, evt): | |
269 | + | |
270 | + shift_pressed = False | |
271 | + if evt.m_shiftDown: | |
272 | + shift_pressed = True | |
273 | + | |
274 | + dicom_id = self.bitmap_info.id | |
275 | + self.select_on = True | |
276 | + self.bitmap_info.selected = True | |
277 | + self.Select() | |
278 | + | |
279 | + # Generating a EVT_PREVIEW_CLICK event | |
280 | + my_evt = SerieEvent(myEVT_PREVIEW_CLICK, self.GetId()) | |
281 | + | |
282 | + my_evt.SetSelectedID(self.bitmap_info.id) | |
283 | + my_evt.SetItemData(self.bitmap_info.data) | |
284 | + | |
285 | + my_evt.SetShiftStatus(shift_pressed) | |
286 | + my_evt.SetEventObject(self) | |
287 | + self.GetEventHandler().ProcessEvent(my_evt) | |
288 | + | |
289 | + Publisher.sendMessage('Set bitmap in preview panel', self.bitmap_info.pos) | |
290 | + | |
291 | + evt.Skip() | |
292 | + | |
293 | + | |
294 | + def OnSize(self, evt): | |
295 | + if self.bitmap_info: | |
296 | + self.SetBitmapToPreview(self.bitmap_info) | |
297 | + evt.Skip() | |
298 | + | |
299 | + def Select(self, on=True): | |
300 | + if self.select_on: | |
301 | + c = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT) | |
302 | + else: | |
303 | + c = (PREVIEW_BACKGROUND) | |
304 | + self.SetBackgroundColour(c) | |
305 | + self.Refresh() | |
306 | + | |
307 | + def OnDClick(self, evt): | |
308 | + my_evt = SerieEvent(myEVT_PREVIEW_DBLCLICK, self.GetId()) | |
309 | + my_evt.SetSelectedID(self.bitmap_info.id) | |
310 | + my_evt.SetItemData(self.bitmap_info.data) | |
311 | + my_evt.SetEventObject(self) | |
312 | + self.GetEventHandler().ProcessEvent(my_evt) | |
313 | + evt.Skip() | |
314 | + | |
315 | + | |
316 | +class BitmapPreviewSeries(wx.Panel): | |
317 | + """A dicom series preview panel""" | |
318 | + def __init__(self, parent): | |
319 | + super(BitmapPreviewSeries, self).__init__(parent) | |
320 | + # TODO: 3 pixels between the previews is a good idea? | |
321 | + # I have to test. | |
322 | + #self.sizer = wx.BoxSizer(wx.HORIZONTAL) | |
323 | + #self.SetSizer(self.sizer) | |
324 | + self.displayed_position = 0 | |
325 | + self.nhidden_last_display = 0 | |
326 | + self.selected_dicom = None | |
327 | + self.selected_panel = None | |
328 | + self._init_ui() | |
329 | + | |
330 | + def _init_ui(self): | |
331 | + scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) | |
332 | + self.scroll = scroll | |
333 | + | |
334 | + self.grid = wx.GridSizer(rows=NROWS, cols=NCOLS, vgap=3, hgap=3) | |
335 | + | |
336 | + sizer = wx.BoxSizer(wx.HORIZONTAL) | |
337 | + sizer.AddSizer(self.grid, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) | |
338 | + | |
339 | + background_sizer = wx.BoxSizer(wx.HORIZONTAL) | |
340 | + background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) | |
341 | + background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) | |
342 | + self.SetSizer(background_sizer) | |
343 | + background_sizer.Fit(self) | |
344 | + | |
345 | + self.Layout() | |
346 | + self.Update() | |
347 | + self.SetAutoLayout(1) | |
348 | + | |
349 | + self.sizer = background_sizer | |
350 | + | |
351 | + self._Add_Panels_Preview() | |
352 | + self._bind_events() | |
353 | + | |
354 | + def _Add_Panels_Preview(self): | |
355 | + self.previews = [] | |
356 | + for i in xrange(NROWS): | |
357 | + for j in xrange(NCOLS): | |
358 | + p = Preview(self) | |
359 | + p.Bind(EVT_PREVIEW_CLICK, self.OnSelect) | |
360 | + #if (i == j == 0): | |
361 | + #self._show_shadow(p) | |
362 | + #p.Hide() | |
363 | + self.previews.append(p) | |
364 | + self.grid.Add(p, 1, flag=wx.EXPAND) | |
365 | + | |
366 | + #def _show_shadow(self, preview): | |
367 | + # preview.ShowShadow() | |
368 | + | |
369 | + def _bind_events(self): | |
370 | + # When the user scrolls the window | |
371 | + self.Bind(wx.EVT_SCROLL, self.OnScroll) | |
372 | + self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) | |
373 | + | |
374 | + def OnSelect(self, evt): | |
375 | + my_evt = SerieEvent(myEVT_CLICK_SERIE, self.GetId()) | |
376 | + my_evt.SetSelectedID(evt.GetSelectID()) | |
377 | + my_evt.SetItemData(evt.GetItemData()) | |
378 | + | |
379 | + if self.selected_dicom: | |
380 | + self.selected_dicom.selected = self.selected_dicom is \ | |
381 | + evt.GetEventObject().bitmap_info | |
382 | + self.selected_panel.select_on = self.selected_panel is evt.GetEventObject() | |
383 | + self.selected_panel.Select() | |
384 | + self.selected_panel = evt.GetEventObject() | |
385 | + self.selected_dicom = self.selected_panel.bitmap_info | |
386 | + self.GetEventHandler().ProcessEvent(my_evt) | |
387 | + evt.Skip() | |
388 | + | |
389 | + def SetBitmapFiles(self, data): | |
390 | + #self.files = data | |
391 | + self.files = [] | |
392 | + | |
393 | + bitmap = bitmap_reader.BitmapData() | |
394 | + bitmap.SetData(data) | |
395 | + | |
396 | + pos = 0 | |
397 | + for d in data: | |
398 | + d.append(pos) | |
399 | + info = BitmapInfo(d) | |
400 | + self.files.append(info) | |
401 | + pos += 1 | |
402 | + | |
403 | + scroll_range = len(self.files)/NCOLS | |
404 | + if scroll_range * NCOLS < len(self.files): | |
405 | + scroll_range +=1 | |
406 | + self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) | |
407 | + self._display_previews() | |
408 | + | |
409 | + | |
410 | + #def SetPatientGroups(self, patient): | |
411 | + # self.files = [] | |
412 | + # self.displayed_position = 0 | |
413 | + # self.nhidden_last_display = 0 | |
414 | + # group_list = patient.GetGroups() | |
415 | + # self.group_list = group_list | |
416 | + # n = 0 | |
417 | + # for group in group_list: | |
418 | + # info = BitmapInfo((group.dicom.patient.id, | |
419 | + # group.dicom.acquisition.serie_number), | |
420 | + # group.dicom, | |
421 | + # group.title, | |
422 | + # _("%d images") %(group.nslices)) | |
423 | + # self.files.append(info) | |
424 | + # n+=1 | |
425 | + # scroll_range = len(self.files)/NCOLS | |
426 | + # if scroll_range * NCOLS < len(self.files): | |
427 | + # scroll_range +=1 | |
428 | + # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) | |
429 | + # self._display_previews() | |
430 | + | |
431 | + | |
432 | + def _display_previews(self): | |
433 | + initial = self.displayed_position * NCOLS | |
434 | + final = initial + NUM_PREVIEWS | |
435 | + if len(self.files) < final: | |
436 | + for i in xrange(final-len(self.files)): | |
437 | + try: | |
438 | + self.previews[-i-1].Hide() | |
439 | + except IndexError: | |
440 | + utils.debug("doesn't exist!") | |
441 | + pass | |
442 | + self.nhidden_last_display = final-len(self.files) | |
443 | + else: | |
444 | + if self.nhidden_last_display: | |
445 | + for i in xrange(self.nhidden_last_display): | |
446 | + try: | |
447 | + self.previews[-i-1].Show() | |
448 | + except IndexError: | |
449 | + utils.debug("doesn't exist!") | |
450 | + pass | |
451 | + self.nhidden_last_display = 0 | |
452 | + | |
453 | + for f, p in zip(self.files[initial:final], self.previews): | |
454 | + p.SetBitmapToPreview(f) | |
455 | + if f.selected: | |
456 | + self.selected_panel = p | |
457 | + | |
458 | + for f, p in zip(self.files[initial:final], self.previews): | |
459 | + p.Show() | |
460 | + | |
461 | + | |
462 | + def OnScroll(self, evt=None): | |
463 | + if evt: | |
464 | + if self.displayed_position != evt.GetPosition(): | |
465 | + self.displayed_position = evt.GetPosition() | |
466 | + else: | |
467 | + if self.displayed_position != self.scroll.GetThumbPosition(): | |
468 | + self.displayed_position = self.scroll.GetThumbPosition() | |
469 | + self._display_previews() | |
470 | + | |
471 | + def OnWheel(self, evt): | |
472 | + d = evt.GetWheelDelta() / evt.GetWheelRotation() | |
473 | + self.scroll.SetThumbPosition(self.scroll.GetThumbPosition() - d) | |
474 | + self.OnScroll() | |
475 | + | |
476 | + | |
477 | +class SingleImagePreview(wx.Panel): | |
478 | + def __init__(self, parent): | |
479 | + wx.Panel.__init__(self, parent, -1) | |
480 | + self.actor = None | |
481 | + self.__init_gui() | |
482 | + self.__init_vtk() | |
483 | + self.__bind_evt_gui() | |
484 | + self.__bind_pubsub() | |
485 | + self.dicom_list = [] | |
486 | + self.nimages = 1 | |
487 | + self.current_index = 0 | |
488 | + self.window_width = const.WINDOW_LEVEL[_("Bone")][0] | |
489 | + self.window_level = const.WINDOW_LEVEL[_("Bone")][1] | |
490 | + | |
491 | + def __init_vtk(self): | |
492 | + text_image_size = vtku.Text() | |
493 | + text_image_size.SetPosition(const.TEXT_POS_LEFT_UP) | |
494 | + text_image_size.SetValue("") | |
495 | + text_image_size.SetSize(const.TEXT_SIZE_SMALL) | |
496 | + self.text_image_size = text_image_size | |
497 | + | |
498 | + text_image_location = vtku.Text() | |
499 | + text_image_location.SetVerticalJustificationToBottom() | |
500 | + text_image_location.SetPosition(const.TEXT_POS_LEFT_DOWN) | |
501 | + text_image_location.SetValue("") | |
502 | + text_image_location.SetSize(const.TEXT_SIZE_SMALL) | |
503 | + self.text_image_location = text_image_location | |
504 | + | |
505 | + text_patient = vtku.Text() | |
506 | + text_patient.SetJustificationToRight() | |
507 | + text_patient.SetPosition(const.TEXT_POS_RIGHT_UP) | |
508 | + text_patient.SetValue("") | |
509 | + text_patient.SetSize(const.TEXT_SIZE_SMALL) | |
510 | + self.text_patient = text_patient | |
511 | + | |
512 | + text_acquisition = vtku.Text() | |
513 | + text_acquisition.SetJustificationToRight() | |
514 | + text_acquisition.SetVerticalJustificationToBottom() | |
515 | + text_acquisition.SetPosition(const.TEXT_POS_RIGHT_DOWN) | |
516 | + text_acquisition.SetValue("") | |
517 | + text_acquisition.SetSize(const.TEXT_SIZE_SMALL) | |
518 | + self.text_acquisition = text_acquisition | |
519 | + | |
520 | + renderer = vtk.vtkRenderer() | |
521 | + renderer.AddActor(text_image_size.actor) | |
522 | + renderer.AddActor(text_image_location.actor) | |
523 | + renderer.AddActor(text_patient.actor) | |
524 | + renderer.AddActor(text_acquisition.actor) | |
525 | + self.renderer = renderer | |
526 | + | |
527 | + style = vtk.vtkInteractorStyleImage() | |
528 | + | |
529 | + interactor = wxVTKRenderWindowInteractor(self.panel, -1, | |
530 | + size=wx.Size(340,340)) | |
531 | + interactor.GetRenderWindow().AddRenderer(renderer) | |
532 | + interactor.SetInteractorStyle(style) | |
533 | + interactor.Render() | |
534 | + self.interactor = interactor | |
535 | + | |
536 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
537 | + sizer.Add(interactor, 1, wx.GROW|wx.EXPAND) | |
538 | + sizer.Fit(self.panel) | |
539 | + self.panel.SetSizer(sizer) | |
540 | + self.Layout() | |
541 | + self.Update() | |
542 | + | |
543 | + def __init_gui(self): | |
544 | + self.panel = wx.Panel(self, -1) | |
545 | + | |
546 | + slider = wx.Slider(self, | |
547 | + id=-1, | |
548 | + value=0, | |
549 | + minValue=0, | |
550 | + maxValue=99, | |
551 | + style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS) | |
552 | + slider.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
553 | + slider.SetTickFreq(1, 1) | |
554 | + self.slider = slider | |
555 | + | |
556 | + checkbox = wx.CheckBox(self, -1, _("Auto-play")) | |
557 | + self.checkbox = checkbox | |
558 | + | |
559 | + in_sizer = wx.BoxSizer(wx.HORIZONTAL) | |
560 | + in_sizer.Add(slider, 1, wx.GROW|wx.EXPAND) | |
561 | + in_sizer.Add(checkbox, 0) | |
562 | + | |
563 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
564 | + sizer.Add(self.panel, 20, wx.GROW|wx.EXPAND) | |
565 | + sizer.Add(in_sizer, 1, wx.GROW|wx.EXPAND) | |
566 | + sizer.Fit(self) | |
567 | + | |
568 | + self.SetSizer(sizer) | |
569 | + self.Layout() | |
570 | + self.Update() | |
571 | + self.SetAutoLayout(1) | |
572 | + | |
573 | + def __bind_evt_gui(self): | |
574 | + self.slider.Bind(wx.EVT_SLIDER, self.OnSlider) | |
575 | + self.checkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBox) | |
576 | + | |
577 | + def __bind_pubsub(self): | |
578 | + Publisher.subscribe(self.ShowBitmapByPosition, 'Set bitmap in preview panel') | |
579 | + | |
580 | + def ShowBitmapByPosition(self, evt): | |
581 | + pos = evt.data | |
582 | + self.ShowSlice(pos) | |
583 | + | |
584 | + | |
585 | + def OnSlider(self, evt): | |
586 | + pos = evt.GetInt() | |
587 | + self.ShowSlice(pos) | |
588 | + evt.Skip() | |
589 | + | |
590 | + def OnCheckBox(self, evt): | |
591 | + self.ischecked = evt.IsChecked() | |
592 | + if evt.IsChecked(): | |
593 | + wx.CallAfter(self.OnRun) | |
594 | + evt.Skip() | |
595 | + | |
596 | + def OnRun(self): | |
597 | + pos = self.slider.GetValue() | |
598 | + pos += 1 | |
599 | + if not (self.nimages- pos): | |
600 | + pos = 0 | |
601 | + self.slider.SetValue(pos) | |
602 | + self.ShowSlice(pos) | |
603 | + time.sleep(0.2) | |
604 | + if self.ischecked: | |
605 | + try: | |
606 | + wx.Yield() | |
607 | + #TODO: temporary fix necessary in the Windows XP 64 Bits | |
608 | + #BUG in wxWidgets http://trac.wxwidgets.org/ticket/10896 | |
609 | + except(wx._core.PyAssertionError): | |
610 | + utils.debug("wx._core.PyAssertionError") | |
611 | + finally: | |
612 | + wx.CallAfter(self.OnRun) | |
613 | + | |
614 | + def SetBitmapFiles(self, data): | |
615 | + #self.dicom_list = group.GetHandSortedList() | |
616 | + self.bitmap_list = data | |
617 | + self.current_index = 0 | |
618 | + self.nimages = len(data) | |
619 | + # GUI | |
620 | + self.slider.SetMax(self.nimages-1) | |
621 | + self.slider.SetValue(0) | |
622 | + self.ShowSlice() | |
623 | + | |
624 | + def ShowSlice(self, index = 0): | |
625 | + bitmap = self.bitmap_list[index] | |
626 | + | |
627 | + # UPDATE GUI | |
628 | + ## Text related to size | |
629 | + value = STR_SIZE %(bitmap[3], bitmap[4]) | |
630 | + self.text_image_size.SetValue(value) | |
631 | + | |
632 | + value1 = '' | |
633 | + value2 = '' | |
634 | + | |
635 | + value = "%s\n%s" %(value1, value2) | |
636 | + self.text_image_location.SetValue(value) | |
637 | + | |
638 | + | |
639 | + #self.text_patient.SetValue(value) | |
640 | + self.text_patient.SetValue('') | |
641 | + | |
642 | + #self.text_acquisition.SetValue(value) | |
643 | + self.text_acquisition.SetValue('') | |
644 | + | |
645 | + | |
646 | + | |
647 | + n_array = bitmap_reader.ReadBitmap(bitmap[0]) | |
648 | + | |
649 | + image = converters.to_vtk(n_array, spacing=(1,1,1),\ | |
650 | + slice_number=1, orientation="AXIAL") | |
651 | + | |
652 | + | |
653 | + # ADJUST CONTRAST | |
654 | + window_level = n_array.max()/2 | |
655 | + window_width = n_array.max() | |
656 | + | |
657 | + colorer = vtk.vtkImageMapToWindowLevelColors() | |
658 | + colorer.SetInputData(image) | |
659 | + colorer.SetWindow(float(window_width)) | |
660 | + colorer.SetLevel(float(window_level)) | |
661 | + colorer.Update() | |
662 | + | |
663 | + if self.actor is None: | |
664 | + self.actor = vtk.vtkImageActor() | |
665 | + self.renderer.AddActor(self.actor) | |
666 | + | |
667 | + # PLOT IMAGE INTO VIEWER | |
668 | + self.actor.SetInputData(colorer.GetOutput()) | |
669 | + self.renderer.ResetCamera() | |
670 | + self.interactor.Render() | |
671 | + | |
672 | + # Setting slider position | |
673 | + self.slider.SetValue(index) | |
674 | + | |
675 | + | |
676 | +#class BitmapPreviewSlice(wx.Panel): | |
677 | +# def __init__(self, parent): | |
678 | +# super(BitmapPreviewSlice, self).__init__(parent) | |
679 | +# # TODO: 3 pixels between the previews is a good idea? | |
680 | +# # I have to test. | |
681 | +# self.displayed_position = 0 | |
682 | +# self.nhidden_last_display = 0 | |
683 | +# self.selected_dicom = None | |
684 | +# self.selected_panel = None | |
685 | +# self.first_selection = None | |
686 | +# self.last_selection = None | |
687 | +# self._init_ui() | |
688 | +# | |
689 | +# def _init_ui(self): | |
690 | +# scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) | |
691 | +# self.scroll = scroll | |
692 | +# | |
693 | +# self.grid = wx.GridSizer(rows=NROWS, cols=NCOLS, vgap=3, hgap=3) | |
694 | +# | |
695 | +# sizer = wx.BoxSizer(wx.HORIZONTAL) | |
696 | +# sizer.AddSizer(self.grid, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) | |
697 | +# | |
698 | +# background_sizer = wx.BoxSizer(wx.HORIZONTAL) | |
699 | +# background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) | |
700 | +# background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) | |
701 | +# self.SetSizer(background_sizer) | |
702 | +# background_sizer.Fit(self) | |
703 | +# | |
704 | +# self.Layout() | |
705 | +# self.Update() | |
706 | +# self.SetAutoLayout(1) | |
707 | +# | |
708 | +# self.sizer = background_sizer | |
709 | +# | |
710 | +# self._Add_Panels_Preview() | |
711 | +# self._bind_events() | |
712 | +# | |
713 | +# def _Add_Panels_Preview(self): | |
714 | +# self.previews = [] | |
715 | +# for i in xrange(NROWS): | |
716 | +# for j in xrange(NCOLS): | |
717 | +# p = Preview(self) | |
718 | +# p.Bind(EVT_PREVIEW_CLICK, self.OnPreviewClick) | |
719 | +# #p.Hide() | |
720 | +# self.previews.append(p) | |
721 | +# self.grid.Add(p, 1, flag=wx.EXPAND) | |
722 | +# | |
723 | +# def _bind_events(self): | |
724 | +# # When the user scrolls the window | |
725 | +# self.Bind(wx.EVT_SCROLL, self.OnScroll) | |
726 | +# self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) | |
727 | +# | |
728 | +# def SetDicomDirectory(self, directory): | |
729 | +# utils.debug("Setting Dicom Directory %s" % directory) | |
730 | +# self.directory = directory | |
731 | +# self.series = dicom_reader.GetSeries(directory)[0] | |
732 | +# | |
733 | +# def SetPatientGroups(self, patient): | |
734 | +# self.group_list = patient.GetGroups() | |
735 | +# | |
736 | +# | |
737 | +# #def SetDicomSerie(self, pos): | |
738 | +# # self.files = [] | |
739 | +# # self.displayed_position = 0 | |
740 | +# # self.nhidden_last_display = 0 | |
741 | +# # group = self.group_list[pos] | |
742 | +# # self.group = group | |
743 | +# # #dicom_files = group.GetList() | |
744 | +# # dicom_files = group.GetHandSortedList() | |
745 | +# # n = 0 | |
746 | +# # for dicom in dicom_files: | |
747 | +# # info = BitmapInfo(n, dicom, | |
748 | +# # _("Image %d") % (dicom.image.number), | |
749 | +# # "%.2f" % (dicom.image.position[2])) | |
750 | +# # self.files.append(info) | |
751 | +# # n+=1 | |
752 | +# | |
753 | +# # scroll_range = len(self.files)/NCOLS | |
754 | +# # if scroll_range * NCOLS < len(self.files): | |
755 | +# # scroll_range +=1 | |
756 | +# # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) | |
757 | +# | |
758 | +# # self._display_previews() | |
759 | +# | |
760 | +# #def SetDicomGroup(self, group): | |
761 | +# # self.files = [] | |
762 | +# # self.displayed_position = 0 | |
763 | +# # self.nhidden_last_display = 0 | |
764 | +# # #dicom_files = group.GetList() | |
765 | +# # dicom_files = group.GetHandSortedList() | |
766 | +# # n = 0 | |
767 | +# # for dicom in dicom_files: | |
768 | +# # info = BitmapInfo(n, dicom, | |
769 | +# # _("Image %d") % (dicom.image.number), | |
770 | +# # "%.2f" % (dicom.image.position[2]), | |
771 | +# # ) | |
772 | +# # self.files.append(info) | |
773 | +# # n+=1 | |
774 | +# | |
775 | +# # scroll_range = len(self.files)/NCOLS | |
776 | +# # if scroll_range * NCOLS < len(self.files): | |
777 | +# # scroll_range +=1 | |
778 | +# # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) | |
779 | +# | |
780 | +# # self._display_previews() | |
781 | +# | |
782 | +# #def SetDicomGroup(self, group): | |
783 | +# # self.files = [] | |
784 | +# # self.displayed_position = 0 | |
785 | +# # self.nhidden_last_display = 0 | |
786 | +# # #dicom_files = group.GetList() | |
787 | +# # dicom_files = group.GetHandSortedList() | |
788 | +# # n = 0 | |
789 | +# # for dicom in dicom_files: | |
790 | +# # info = BitmapInfo(n, dicom, | |
791 | +# # _("Image %d") % (dicom.image.number), | |
792 | +# # "%.2f" % (dicom.image.position[2]), | |
793 | +# # ) | |
794 | +# # self.files.append(info) | |
795 | +# # n+=1 | |
796 | +# | |
797 | +# # scroll_range = len(self.files)/NCOLS | |
798 | +# # if scroll_range * NCOLS < len(self.files): | |
799 | +# # scroll_range +=1 | |
800 | +# # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) | |
801 | +# | |
802 | +# # self._display_previews() | |
803 | +# | |
804 | +# | |
805 | +# def _display_previews(self): | |
806 | +# initial = self.displayed_position * NCOLS | |
807 | +# final = initial + NUM_PREVIEWS | |
808 | +# if len(self.files) < final: | |
809 | +# for i in xrange(final-len(self.files)): | |
810 | +# try: | |
811 | +# self.previews[-i-1].Hide() | |
812 | +# except IndexError: | |
813 | +# utils.debug("doesn't exist!") | |
814 | +# self.nhidden_last_display = final-len(self.files) | |
815 | +# else: | |
816 | +# if self.nhidden_last_display: | |
817 | +# for i in xrange(self.nhidden_last_display): | |
818 | +# try: | |
819 | +# self.previews[-i-1].Show() | |
820 | +# except IndexError: | |
821 | +# utils.debug("doesn't exist!") | |
822 | +# self.nhidden_last_display = 0 | |
823 | +# | |
824 | +# for f, p in zip(self.files[initial:final], self.previews): | |
825 | +# p.SetBitmapToPreview(f) | |
826 | +# if f.selected: | |
827 | +# self.selected_panel = p | |
828 | +# #p.interactor.Render() | |
829 | +# | |
830 | +# for f, p in zip(self.files[initial:final], self.previews): | |
831 | +# p.Show() | |
832 | +# | |
833 | +# def OnPreviewClick(self, evt): | |
834 | +# | |
835 | +# dicom_id = evt.GetSelectID() | |
836 | +# | |
837 | +# if self.first_selection is None: | |
838 | +# self.first_selection = dicom_id | |
839 | +# | |
840 | +# if self.last_selection is None: | |
841 | +# self.last_selection = dicom_id | |
842 | +# | |
843 | +# | |
844 | +# if evt.GetPressedShift(): | |
845 | +# | |
846 | +# if dicom_id < self.first_selection and dicom_id < self.last_selection: | |
847 | +# self.first_selection = dicom_id | |
848 | +# else: | |
849 | +# self.last_selection = dicom_id | |
850 | +# else: | |
851 | +# self.first_selection = dicom_id | |
852 | +# self.last_selection = dicom_id | |
853 | +# | |
854 | +# for i in xrange(len(self.files)): | |
855 | +# | |
856 | +# if i == dicom_id: | |
857 | +# self.files[i].selected = True | |
858 | +# else: | |
859 | +# self.files[i].selected = False | |
860 | +# | |
861 | +# | |
862 | +# my_evt = SerieEvent(myEVT_CLICK_SLICE, self.GetId()) | |
863 | +# my_evt.SetSelectedID(evt.GetSelectID()) | |
864 | +# my_evt.SetItemData(evt.GetItemData()) | |
865 | +# | |
866 | +# if self.selected_dicom: | |
867 | +# self.selected_dicom.selected = self.selected_dicom is \ | |
868 | +# evt.GetEventObject().bitmap_info | |
869 | +# self.selected_panel.select_on = self.selected_panel is evt.GetEventObject() | |
870 | +# | |
871 | +# if self.first_selection != self.last_selection: | |
872 | +# for i in xrange(len(self.files)): | |
873 | +# if i >= self.first_selection and i <= self.last_selection: | |
874 | +# self.files[i].selected = True | |
875 | +# else: | |
876 | +# self.files[i].selected = False | |
877 | +# | |
878 | +# else: | |
879 | +# self.selected_panel.Select() | |
880 | +# | |
881 | +# self._display_previews() | |
882 | +# self.selected_panel = evt.GetEventObject() | |
883 | +# self.selected_dicom = self.selected_panel.bitmap_info | |
884 | +# self.GetEventHandler().ProcessEvent(my_evt) | |
885 | +# | |
886 | +# #Publisher.sendMessage("Selected Import Images", [self.first_selection, \ | |
887 | +# # self.last_selection]) | |
888 | +# | |
889 | +# def OnScroll(self, evt=None): | |
890 | +# if evt: | |
891 | +# if self.displayed_position != evt.GetPosition(): | |
892 | +# self.displayed_position = evt.GetPosition() | |
893 | +# else: | |
894 | +# if self.displayed_position != self.scroll.GetThumbPosition(): | |
895 | +# self.displayed_position = self.scroll.GetThumbPosition() | |
896 | +# self._display_previews() | |
897 | +# | |
898 | +# def OnWheel(self, evt): | |
899 | +# d = evt.GetWheelDelta() / evt.GetWheelRotation() | |
900 | +# self.scroll.SetThumbPosition(self.scroll.GetThumbPosition() - d) | |
901 | +# self.OnScroll() | |
902 | + | |
903 | + | ... | ... |
invesalius/gui/dialogs.py
... | ... | @@ -39,6 +39,12 @@ from gui.widgets.clut_imagedata import CLUTImageDataWidget, EVT_CLUT_NODE_CHANGE |
39 | 39 | |
40 | 40 | import numpy as np |
41 | 41 | |
42 | +try: | |
43 | + from agw import floatspin as FS | |
44 | +except ImportError: # if it's not there locally, try the wxPython lib. | |
45 | + import wx.lib.agw.floatspin as FS | |
46 | + | |
47 | + | |
42 | 48 | class MaskEvent(wx.PyCommandEvent): |
43 | 49 | def __init__(self , evtType, id, mask_index): |
44 | 50 | wx.PyCommandEvent.__init__(self, evtType, id,) |
... | ... | @@ -306,6 +312,48 @@ def ShowImportDirDialog(): |
306 | 312 | os.chdir(current_dir) |
307 | 313 | return path |
308 | 314 | |
315 | +def ShowImportBitmapDirDialog(): | |
316 | + current_dir = os.path.abspath(".") | |
317 | + | |
318 | + if (sys.platform == 'win32') or (sys.platform == 'linux2'): | |
319 | + session = ses.Session() | |
320 | + | |
321 | + if (session.GetLastDicomFolder()): | |
322 | + folder = session.GetLastDicomFolder() | |
323 | + else: | |
324 | + folder = '' | |
325 | + else: | |
326 | + folder = '' | |
327 | + | |
328 | + dlg = wx.DirDialog(None, _("Choose a folder with TIFF, BMP, JPG or PNG:"), folder, | |
329 | + style=wx.DD_DEFAULT_STYLE | |
330 | + | wx.DD_DIR_MUST_EXIST | |
331 | + | wx.DD_CHANGE_DIR) | |
332 | + | |
333 | + path = None | |
334 | + try: | |
335 | + if dlg.ShowModal() == wx.ID_OK: | |
336 | + # GetPath returns in unicode, if a path has non-ascii characters a | |
337 | + # UnicodeEncodeError is raised. To avoid this, path is encoded in utf-8 | |
338 | + if sys.platform == "win32": | |
339 | + path = dlg.GetPath() | |
340 | + else: | |
341 | + path = dlg.GetPath().encode('utf-8') | |
342 | + | |
343 | + except(wx._core.PyAssertionError): #TODO: error win64 | |
344 | + if (dlg.GetPath()): | |
345 | + path = dlg.GetPath() | |
346 | + | |
347 | + if (sys.platform != 'darwin'): | |
348 | + if (path): | |
349 | + session.SetLastDicomFolder(path) | |
350 | + | |
351 | + # Only destroy a dialog after you're done with it. | |
352 | + dlg.Destroy() | |
353 | + os.chdir(current_dir) | |
354 | + return path | |
355 | + | |
356 | + | |
309 | 357 | def ShowSaveAsProjectDialog(default_filename=None): |
310 | 358 | current_dir = os.path.abspath(".") |
311 | 359 | dlg = wx.FileDialog(None, |
... | ... | @@ -1630,3 +1678,114 @@ class ReorientImageDialog(wx.Dialog): |
1630 | 1678 | Publisher.sendMessage('Disable style', const.SLICE_STATE_REORIENT) |
1631 | 1679 | Publisher.sendMessage('Enable style', const.STATE_DEFAULT) |
1632 | 1680 | self.Destroy() |
1681 | + | |
1682 | + | |
1683 | + | |
1684 | +class ImportBitmapParameters(wx.Dialog): | |
1685 | + | |
1686 | + def __init__(self): | |
1687 | + pre = wx.PreDialog() | |
1688 | + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Parameters"),size=wx.Size(380,230),\ | |
1689 | + style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP) | |
1690 | + | |
1691 | + self.interval = 0 | |
1692 | + | |
1693 | + self.PostCreate(pre) | |
1694 | + | |
1695 | + self._init_gui() | |
1696 | + | |
1697 | + self.bind_evts() | |
1698 | + self.CenterOnScreen() | |
1699 | + | |
1700 | + | |
1701 | + def _init_gui(self): | |
1702 | + | |
1703 | + | |
1704 | + p = wx.Panel(self, -1, style = wx.TAB_TRAVERSAL | |
1705 | + | wx.CLIP_CHILDREN | |
1706 | + | wx.FULL_REPAINT_ON_RESIZE) | |
1707 | + | |
1708 | + gbs_principal = self.gbs = wx.GridBagSizer(3,1) | |
1709 | + | |
1710 | + gbs = self.gbs = wx.GridBagSizer(4, 2) | |
1711 | + | |
1712 | + stx_name = wx.StaticText(p, -1, _(u"Project name:")) | |
1713 | + tx_name = self.tx_name = wx.TextCtrl(p, -1, "InVesalius Bitmap", size=wx.Size(220,-1)) | |
1714 | + | |
1715 | + stx_orientation = wx.StaticText(p, -1, _(u"Slices orientation:")) | |
1716 | + cb_orientation_options = [_(u'Axial'), _(u'Coronal'), _(u'Sagital')] | |
1717 | + cb_orientation = self.cb_orientation = wx.ComboBox(p, value="Axial", choices=cb_orientation_options,\ | |
1718 | + size=wx.Size(160,-1), style=wx.CB_DROPDOWN|wx.CB_READONLY) | |
1719 | + | |
1720 | + stx_spacing = wx.StaticText(p, -1, _(u"Spacing (mm):")) | |
1721 | + | |
1722 | + gbs.Add(stx_name, (0,0)) | |
1723 | + gbs.Add(tx_name, (0,1)) | |
1724 | + | |
1725 | + gbs.Add(stx_orientation, (1,0)) | |
1726 | + gbs.Add(cb_orientation, (1,1)) | |
1727 | + | |
1728 | + gbs.Add(stx_spacing, (2,0)) | |
1729 | + | |
1730 | + #--- spacing -------------- | |
1731 | + gbs_spacing = wx.GridBagSizer(2, 6) | |
1732 | + | |
1733 | + stx_spacing_x = stx_spacing_x = wx.StaticText(p, -1, _(u"X:")) | |
1734 | + fsp_spacing_x = self.fsp_spacing_x = FS.FloatSpin(p, -1, min_val=0,\ | |
1735 | + increment=0.25, value=1.0, digits=6) | |
1736 | + | |
1737 | + stx_spacing_y = stx_spacing_y = wx.StaticText(p, -1, _(u"Y:")) | |
1738 | + fsp_spacing_y = self.fsp_spacing_y = FS.FloatSpin(p, -1, min_val=0,\ | |
1739 | + increment=0.25, value=1.0, digits=6) | |
1740 | + | |
1741 | + stx_spacing_z = stx_spacing_z = wx.StaticText(p, -1, _(u"Z:")) | |
1742 | + fsp_spacing_z = self.fsp_spacing_z = FS.FloatSpin(p, -1, min_val=0,\ | |
1743 | + increment=0.25, value=1.0, digits=6) | |
1744 | + | |
1745 | + gbs_spacing.Add(stx_spacing_x, (0,0)) | |
1746 | + gbs_spacing.Add(fsp_spacing_x, (0,1)) | |
1747 | + | |
1748 | + gbs_spacing.Add(stx_spacing_y, (0,2)) | |
1749 | + gbs_spacing.Add(fsp_spacing_y, (0,3)) | |
1750 | + | |
1751 | + gbs_spacing.Add(stx_spacing_z, (0,4)) | |
1752 | + gbs_spacing.Add(fsp_spacing_z, (0,5)) | |
1753 | + | |
1754 | + #----- buttons ------------------------ | |
1755 | + gbs_button = wx.GridBagSizer(1, 4) | |
1756 | + | |
1757 | + btn_ok = self.btn_ok= wx.Button(p, wx.ID_OK) | |
1758 | + btn_ok.SetDefault() | |
1759 | + | |
1760 | + btn_cancel = wx.Button(p, wx.ID_CANCEL) | |
1761 | + | |
1762 | + gbs_button.Add(btn_cancel, (1,2)) | |
1763 | + gbs_button.Add(btn_ok, (1,3)) | |
1764 | + | |
1765 | + | |
1766 | + gbs_principal.Add(gbs, (0,0)) | |
1767 | + gbs_principal.Add(gbs_spacing, (1,0)) | |
1768 | + gbs_principal.Add(gbs_button, (2,0), flag = wx.ALIGN_RIGHT) | |
1769 | + | |
1770 | + box = wx.BoxSizer() | |
1771 | + box.Add(gbs_principal, 1, wx.ALL|wx.EXPAND, 10) | |
1772 | + | |
1773 | + p.SetSizer(box) | |
1774 | + | |
1775 | + | |
1776 | + def bind_evts(self): | |
1777 | + self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) | |
1778 | + | |
1779 | + def SetInterval(self, v): | |
1780 | + self.interval = v | |
1781 | + | |
1782 | + def OnOk(self, evt): | |
1783 | + self.Close() | |
1784 | + self.Destroy() | |
1785 | + | |
1786 | + values = [self.tx_name.GetValue(), self.cb_orientation.GetValue().upper(),\ | |
1787 | + self.fsp_spacing_x.GetValue(), self.fsp_spacing_y.GetValue(),\ | |
1788 | + self.fsp_spacing_z.GetValue(), self.interval] | |
1789 | + Publisher.sendMessage('Open bitmap files', values) | |
1790 | + | |
1791 | + | ... | ... |
invesalius/gui/frame.py
... | ... | @@ -36,6 +36,7 @@ import default_tasks as tasks |
36 | 36 | import default_viewers as viewers |
37 | 37 | import gui.dialogs as dlg |
38 | 38 | import import_panel as imp |
39 | +import import_bitmap_panel as imp_bmp | |
39 | 40 | import import_network_panel as imp_net |
40 | 41 | import project as prj |
41 | 42 | import session as ses |
... | ... | @@ -126,6 +127,7 @@ class Frame(wx.Frame): |
126 | 127 | sub(self._ShowImportPanel, 'Show import panel in frame') |
127 | 128 | #sub(self._ShowHelpMessage, 'Show help message') |
128 | 129 | sub(self._ShowImportNetwork, 'Show retrieve dicom panel') |
130 | + sub(self._ShowImportBitmap, 'Show import bitmap panel in frame') | |
129 | 131 | sub(self._ShowTask, 'Show task panel') |
130 | 132 | sub(self._UpdateAUI, 'Update AUI') |
131 | 133 | sub(self._Exit, 'Exit') |
... | ... | @@ -170,8 +172,14 @@ class Frame(wx.Frame): |
170 | 172 | # are shown, this should be hiden |
171 | 173 | caption = _("Preview medical data to be reconstructed") |
172 | 174 | aui_manager.AddPane(imp.Panel(self), wx.aui.AuiPaneInfo(). |
173 | - Name("Import").Centre().Hide(). | |
174 | - MaximizeButton(True).Floatable(True). | |
175 | + Name("Import").CloseButton(False).Centre().Hide(). | |
176 | + MaximizeButton(False).Floatable(True). | |
177 | + Caption(caption).CaptionVisible(True)) | |
178 | + | |
179 | + caption = _("Preview bitmap to be reconstructed") | |
180 | + aui_manager.AddPane(imp_bmp.Panel(self), wx.aui.AuiPaneInfo(). | |
181 | + Name("ImportBMP").CloseButton(False).Centre().Hide(). | |
182 | + MaximizeButton(False).Floatable(True). | |
175 | 183 | Caption(caption).CaptionVisible(True)) |
176 | 184 | |
177 | 185 | ncaption = _("Retrieve DICOM from PACS") |
... | ... | @@ -298,6 +306,9 @@ class Frame(wx.Frame): |
298 | 306 | Publisher.sendMessage("Set layout button full") |
299 | 307 | aui_manager = self.aui_manager |
300 | 308 | aui_manager.GetPane("Import").Show(0) |
309 | + | |
310 | + aui_manager.GetPane("ImportBMP").Show(0) | |
311 | + | |
301 | 312 | aui_manager.GetPane("Data").Show(1) |
302 | 313 | aui_manager.GetPane("Tasks").Show(1) |
303 | 314 | aui_manager.Update() |
... | ... | @@ -314,6 +325,18 @@ class Frame(wx.Frame): |
314 | 325 | aui_manager.GetPane("Import").Show(0) |
315 | 326 | aui_manager.Update() |
316 | 327 | |
328 | + def _ShowImportBitmap(self, evt_pubsub): | |
329 | + """ | |
330 | + Show viewers and task, hide import panel. | |
331 | + """ | |
332 | + Publisher.sendMessage("Set layout button full") | |
333 | + aui_manager = self.aui_manager | |
334 | + aui_manager.GetPane("ImportBMP").Show(1) | |
335 | + aui_manager.GetPane("Data").Show(0) | |
336 | + aui_manager.GetPane("Tasks").Show(0) | |
337 | + aui_manager.GetPane("Import").Show(0) | |
338 | + aui_manager.Update() | |
339 | + | |
317 | 340 | def _ShowHelpMessage(self, evt_pubsub): |
318 | 341 | aui_manager = self.aui_manager |
319 | 342 | pos = aui_manager.GetPane("Data").window.GetScreenPosition() |
... | ... | @@ -371,6 +394,8 @@ class Frame(wx.Frame): |
371 | 394 | self.ShowOpenProject() |
372 | 395 | elif id == const.ID_ANALYZE_IMPORT: |
373 | 396 | self.ShowAnalyzeImporter() |
397 | + elif id == const.ID_TIFF_JPG_PNG: | |
398 | + self.ShowBitmapImporter() | |
374 | 399 | elif id == const.ID_PROJECT_SAVE: |
375 | 400 | session = ses.Session() |
376 | 401 | if session.temp_item: |
... | ... | @@ -501,6 +526,12 @@ class Frame(wx.Frame): |
501 | 526 | """ |
502 | 527 | Publisher.sendMessage('Show analyze dialog', True) |
503 | 528 | |
529 | + def ShowBitmapImporter(self): | |
530 | + """ | |
531 | + Tiff, BMP, JPEG and PNG | |
532 | + """ | |
533 | + Publisher.sendMessage('Show bitmap dialog', True) | |
534 | + | |
504 | 535 | def FlipVolume(self, axis): |
505 | 536 | Publisher.sendMessage('Flip volume', axis) |
506 | 537 | Publisher.sendMessage('Reload actual slice') |
... | ... | @@ -581,6 +612,7 @@ class MenuBar(wx.MenuBar): |
581 | 612 | #Import Others Files |
582 | 613 | others_file_menu = wx.Menu() |
583 | 614 | others_file_menu.Append(const.ID_ANALYZE_IMPORT, "Analyze") |
615 | + others_file_menu.Append(const.ID_TIFF_JPG_PNG, "TIFF,BMP,JPG or PNG") | |
584 | 616 | |
585 | 617 | # FILE |
586 | 618 | file_menu = wx.Menu() | ... | ... |
... | ... | @@ -0,0 +1,484 @@ |
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 | +import wx | |
20 | +import wx.gizmos as gizmos | |
21 | +from wx.lib.pubsub import pub as Publisher | |
22 | +import wx.lib.splitter as spl | |
23 | + | |
24 | +import constants as const | |
25 | +import gui.dialogs as dlg | |
26 | +import bitmap_preview_panel as bpp | |
27 | +import reader.dicom_grouper as dcm | |
28 | +from dialogs import ImportBitmapParameters | |
29 | + | |
30 | +myEVT_SELECT_SERIE = wx.NewEventType() | |
31 | +EVT_SELECT_SERIE = wx.PyEventBinder(myEVT_SELECT_SERIE, 1) | |
32 | + | |
33 | +myEVT_SELECT_SLICE = wx.NewEventType() | |
34 | +EVT_SELECT_SLICE = wx.PyEventBinder(myEVT_SELECT_SLICE, 1) | |
35 | + | |
36 | +myEVT_SELECT_PATIENT = wx.NewEventType() | |
37 | +EVT_SELECT_PATIENT = wx.PyEventBinder(myEVT_SELECT_PATIENT, 1) | |
38 | + | |
39 | +myEVT_SELECT_SERIE_TEXT = wx.NewEventType() | |
40 | +EVT_SELECT_SERIE_TEXT = wx.PyEventBinder(myEVT_SELECT_SERIE_TEXT, 1) | |
41 | + | |
42 | +class SelectEvent(wx.PyCommandEvent): | |
43 | + def __init__(self , evtType, id): | |
44 | + super(SelectEvent, self).__init__(evtType, id) | |
45 | + | |
46 | + def GetSelectID(self): | |
47 | + return self.SelectedID | |
48 | + | |
49 | + def SetSelectedID(self, id): | |
50 | + self.SelectedID = id | |
51 | + | |
52 | + def GetItemData(self): | |
53 | + return self.data | |
54 | + | |
55 | + def SetItemData(self, data): | |
56 | + self.data = data | |
57 | + | |
58 | + | |
59 | +class Panel(wx.Panel): | |
60 | + def __init__(self, parent): | |
61 | + wx.Panel.__init__(self, parent, pos=wx.Point(5, 5))#, | |
62 | + #size=wx.Size(280, 656)) | |
63 | + | |
64 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
65 | + sizer.Add(InnerPanel(self), 1, wx.EXPAND|wx.GROW|wx.ALL, 5) | |
66 | + | |
67 | + self.SetSizer(sizer) | |
68 | + sizer.Fit(self) | |
69 | + | |
70 | + self.Layout() | |
71 | + self.Update() | |
72 | + self.SetAutoLayout(1) | |
73 | + | |
74 | + | |
75 | +# Inner fold panel | |
76 | +class InnerPanel(wx.Panel): | |
77 | + def __init__(self, parent): | |
78 | + wx.Panel.__init__(self, parent, pos=wx.Point(5, 5))#, | |
79 | + #size=wx.Size(680, 656)) | |
80 | + | |
81 | + self.patients = [] | |
82 | + self.first_image_selection = None | |
83 | + self.last_image_selection = None | |
84 | + self._init_ui() | |
85 | + self._bind_events() | |
86 | + self._bind_pubsubevt() | |
87 | + | |
88 | + def _init_ui(self): | |
89 | + splitter = spl.MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE) | |
90 | + splitter.SetOrientation(wx.VERTICAL) | |
91 | + self.splitter = splitter | |
92 | + | |
93 | + panel = wx.Panel(self) | |
94 | + self.btn_cancel = wx.Button(panel, wx.ID_CANCEL) | |
95 | + self.btn_ok = wx.Button(panel, wx.ID_OK, _("Import")) | |
96 | + | |
97 | + btnsizer = wx.StdDialogButtonSizer() | |
98 | + btnsizer.AddButton(self.btn_ok) | |
99 | + btnsizer.AddButton(self.btn_cancel) | |
100 | + btnsizer.Realize() | |
101 | + | |
102 | + self.combo_interval = wx.ComboBox(panel, -1, "", choices=const.IMPORT_INTERVAL, | |
103 | + style=wx.CB_DROPDOWN|wx.CB_READONLY) | |
104 | + self.combo_interval.SetSelection(0) | |
105 | + | |
106 | + inner_sizer = wx.BoxSizer(wx.HORIZONTAL) | |
107 | + inner_sizer.AddSizer(btnsizer, 0, wx.LEFT|wx.TOP, 5) | |
108 | + inner_sizer.Add(self.combo_interval, 0, wx.LEFT|wx.RIGHT|wx.TOP, 5) | |
109 | + panel.SetSizer(inner_sizer) | |
110 | + inner_sizer.Fit(panel) | |
111 | + | |
112 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
113 | + sizer.Add(splitter, 20, wx.EXPAND) | |
114 | + sizer.Add(panel, 0, wx.EXPAND|wx.LEFT, 90) | |
115 | + | |
116 | + self.text_panel = TextPanel(splitter) | |
117 | + splitter.AppendWindow(self.text_panel, 250) | |
118 | + | |
119 | + self.image_panel = ImagePanel(splitter) | |
120 | + splitter.AppendWindow(self.image_panel, 250) | |
121 | + | |
122 | + self.SetSizer(sizer) | |
123 | + sizer.Fit(self) | |
124 | + | |
125 | + self.Layout() | |
126 | + self.Update() | |
127 | + self.SetAutoLayout(1) | |
128 | + | |
129 | + def _bind_pubsubevt(self): | |
130 | + Publisher.subscribe(self.ShowBitmapPreview, "Load import bitmap panel") | |
131 | + Publisher.subscribe(self.GetSelectedImages ,"Selected Import Images") | |
132 | + | |
133 | + def ShowBitmapPreview(self, pubsub_evt): | |
134 | + data = pubsub_evt.data | |
135 | + #self.patients.extend(dicom_groups) | |
136 | + self.text_panel.Populate(data) | |
137 | + | |
138 | + def GetSelectedImages(self, pubsub_evt): | |
139 | + self.first_image_selection = pubsub_evt.data[0] | |
140 | + self.last_image_selection = pubsub_evt.data[1] | |
141 | + | |
142 | + def _bind_events(self): | |
143 | + self.Bind(EVT_SELECT_SERIE, self.OnSelectSerie) | |
144 | + self.Bind(EVT_SELECT_SLICE, self.OnSelectSlice) | |
145 | + self.Bind(EVT_SELECT_PATIENT, self.OnSelectPatient) | |
146 | + self.btn_ok.Bind(wx.EVT_BUTTON, self.OnClickOk) | |
147 | + self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnClickCancel) | |
148 | + self.text_panel.Bind(EVT_SELECT_SERIE_TEXT, self.OnDblClickTextPanel) | |
149 | + | |
150 | + def OnSelectSerie(self, evt): | |
151 | + #patient_id, serie_number = evt.GetSelectID() | |
152 | + #self.text_panel.SelectSerie(evt.GetSelectID()) | |
153 | + #for patient in self.patients: | |
154 | + # if patient_id == patient.GetDicomSample().patient.id: | |
155 | + # for group in patient.GetGroups(): | |
156 | + # if serie_number == group.GetDicomSample().acquisition.serie_number: | |
157 | + # self.image_panel.SetSerie(group) | |
158 | + | |
159 | + pass | |
160 | + | |
161 | + | |
162 | + | |
163 | + def OnSelectSlice(self, evt): | |
164 | + pass | |
165 | + | |
166 | + def OnSelectPatient(self, evt): | |
167 | + pass | |
168 | + | |
169 | + def OnDblClickTextPanel(self, evt): | |
170 | + group = evt.GetItemData() | |
171 | + self.LoadDicom(group) | |
172 | + | |
173 | + def OnClickOk(self, evt): | |
174 | + parm = dlg.ImportBitmapParameters() | |
175 | + parm.SetInterval(self.combo_interval.GetSelection()) | |
176 | + parm.ShowModal() | |
177 | + | |
178 | + group = self.text_panel.GetSelection() | |
179 | + if group: | |
180 | + self.LoadDicom(group) | |
181 | + | |
182 | + def OnClickCancel(self, evt): | |
183 | + Publisher.sendMessage("Cancel DICOM load") | |
184 | + | |
185 | + def LoadDicom(self, group): | |
186 | + #interval = self.combo_interval.GetSelection() | |
187 | + #if not isinstance(group, dcm.DicomGroup): | |
188 | + # group = max(group.GetGroups(), key=lambda g: g.nslices) | |
189 | + | |
190 | + #slice_amont = group.nslices | |
191 | + #if (self.first_image_selection != None) and (self.first_image_selection != self.last_image_selection): | |
192 | + # slice_amont = (self.last_image_selection) - self.first_image_selection | |
193 | + # slice_amont += 1 | |
194 | + # if slice_amont == 0: | |
195 | + # slice_amont = group.nslices | |
196 | + | |
197 | + #nslices_result = slice_amont / (interval + 1) | |
198 | + #if (nslices_result > 1): | |
199 | + # Publisher.sendMessage('Open DICOM group', (group, interval, | |
200 | + # [self.first_image_selection, self.last_image_selection])) | |
201 | + #else: | |
202 | + # dlg.MissingFilesForReconstruction() | |
203 | + pass | |
204 | + | |
205 | + | |
206 | +class TextPanel(wx.Panel): | |
207 | + def __init__(self, parent): | |
208 | + wx.Panel.__init__(self, parent, -1) | |
209 | + | |
210 | + self._selected_by_user = True | |
211 | + self.idserie_treeitem = {} | |
212 | + self.treeitem_idpatient = {} | |
213 | + | |
214 | + self.__init_gui() | |
215 | + self.__bind_events_wx() | |
216 | + self.__bind_pubsub_evt() | |
217 | + | |
218 | + def __bind_pubsub_evt(self): | |
219 | + Publisher.subscribe(self.SelectSeries, 'Select series in import panel') | |
220 | + | |
221 | + def __bind_events_wx(self): | |
222 | + self.Bind(wx.EVT_SIZE, self.OnSize) | |
223 | + | |
224 | + def __init_gui(self): | |
225 | + tree = gizmos.TreeListCtrl(self, -1, style = | |
226 | + wx.TR_DEFAULT_STYLE | |
227 | + | wx.TR_HIDE_ROOT | |
228 | + | wx.TR_ROW_LINES | |
229 | + | wx.TR_COLUMN_LINES | |
230 | + | wx.TR_FULL_ROW_HIGHLIGHT | |
231 | + | wx.TR_SINGLE | |
232 | + | wx.TR_HIDE_ROOT | |
233 | + ) | |
234 | + | |
235 | + | |
236 | + tree.AddColumn(_("Path")) | |
237 | + tree.AddColumn(_("Type")) | |
238 | + tree.AddColumn(_("Width x Height")) | |
239 | + | |
240 | + | |
241 | + tree.SetMainColumn(0) | |
242 | + tree.SetColumnWidth(0, 880) | |
243 | + tree.SetColumnWidth(1, 60) | |
244 | + tree.SetColumnWidth(2, 130) | |
245 | + | |
246 | + self.root = tree.AddRoot(_("InVesalius Database")) | |
247 | + self.tree = tree | |
248 | + | |
249 | + def SelectSeries(self, pubsub_evt): | |
250 | + group_index = pubsub_evt.data | |
251 | + | |
252 | + def Populate(self, data): | |
253 | + tree = self.tree | |
254 | + for value in data: | |
255 | + parent = tree.AppendItem(self.root, value[0]) | |
256 | + self.tree.SetItemText(parent, value[2], 1) | |
257 | + self.tree.SetItemText(parent, value[5], 2) | |
258 | + | |
259 | + tree.Expand(self.root) | |
260 | + #tree.SelectItem(parent_select) | |
261 | + tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivate) | |
262 | + tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged) | |
263 | + | |
264 | + Publisher.sendMessage('Load bitmap into import panel', data) | |
265 | + | |
266 | + | |
267 | + def OnSelChanged(self, evt): | |
268 | + item = self.tree.GetSelection() | |
269 | + if self._selected_by_user: | |
270 | + group = self.tree.GetItemPyData(item) | |
271 | + if isinstance(group, dcm.DicomGroup): | |
272 | + Publisher.sendMessage('Load group into import panel', | |
273 | + group) | |
274 | + | |
275 | + elif isinstance(group, dcm.PatientGroup): | |
276 | + id = group.GetDicomSample().patient.id | |
277 | + my_evt = SelectEvent(myEVT_SELECT_PATIENT, self.GetId()) | |
278 | + my_evt.SetSelectedID(id) | |
279 | + self.GetEventHandler().ProcessEvent(my_evt) | |
280 | + | |
281 | + Publisher.sendMessage('Load bitmap into import panel', | |
282 | + | |
283 | + group) | |
284 | + else: | |
285 | + parent_id = self.tree.GetItemParent(item) | |
286 | + self.tree.Expand(parent_id) | |
287 | + evt.Skip() | |
288 | + | |
289 | + def OnActivate(self, evt): | |
290 | + item = evt.GetItem() | |
291 | + group = self.tree.GetItemPyData(item) | |
292 | + my_evt = SelectEvent(myEVT_SELECT_SERIE_TEXT, self.GetId()) | |
293 | + my_evt.SetItemData(group) | |
294 | + self.GetEventHandler().ProcessEvent(my_evt) | |
295 | + | |
296 | + def OnSize(self, evt): | |
297 | + self.tree.SetSize(self.GetSize()) | |
298 | + | |
299 | + def SelectSerie(self, serie): | |
300 | + self._selected_by_user = False | |
301 | + item = self.idserie_treeitem[serie] | |
302 | + self.tree.SelectItem(item) | |
303 | + self._selected_by_user = True | |
304 | + | |
305 | + def GetSelection(self): | |
306 | + """Get selected item""" | |
307 | + item = self.tree.GetSelection() | |
308 | + group = self.tree.GetItemPyData(item) | |
309 | + return group | |
310 | + | |
311 | + | |
312 | +class ImagePanel(wx.Panel): | |
313 | + def __init__(self, parent): | |
314 | + wx.Panel.__init__(self, parent, -1) | |
315 | + self._init_ui() | |
316 | + self._bind_events() | |
317 | + | |
318 | + def _init_ui(self): | |
319 | + splitter = spl.MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE) | |
320 | + splitter.SetOrientation(wx.HORIZONTAL) | |
321 | + self.splitter = splitter | |
322 | + | |
323 | + splitter.ContainingSizer = wx.BoxSizer(wx.HORIZONTAL) | |
324 | + | |
325 | + sizer = wx.BoxSizer(wx.HORIZONTAL) | |
326 | + sizer.Add(splitter, 1, wx.EXPAND) | |
327 | + self.SetSizer(sizer) | |
328 | + | |
329 | + self.text_panel = SeriesPanel(splitter) | |
330 | + splitter.AppendWindow(self.text_panel, 600) | |
331 | + | |
332 | + self.image_panel = SlicePanel(splitter) | |
333 | + splitter.AppendWindow(self.image_panel, 250) | |
334 | + | |
335 | + self.SetSizer(sizer) | |
336 | + sizer.Fit(self) | |
337 | + | |
338 | + self.Layout() | |
339 | + self.Update() | |
340 | + self.SetAutoLayout(1) | |
341 | + | |
342 | + def _bind_events(self): | |
343 | + self.text_panel.Bind(EVT_SELECT_SERIE, self.OnSelectSerie) | |
344 | + self.text_panel.Bind(EVT_SELECT_SLICE, self.OnSelectSlice) | |
345 | + | |
346 | + def OnSelectSerie(self, evt): | |
347 | + evt.Skip() | |
348 | + | |
349 | + def OnSelectSlice(self, evt): | |
350 | + self.image_panel.bitmap_preview.ShowSlice(evt.GetSelectID()) | |
351 | + evt.Skip() | |
352 | + | |
353 | + def SetSerie(self, serie): | |
354 | + self.image_panel.bitmap_preview.SetDicomGroup(serie) | |
355 | + | |
356 | + | |
357 | +class SeriesPanel(wx.Panel): | |
358 | + def __init__(self, parent): | |
359 | + wx.Panel.__init__(self, parent, -1) | |
360 | + #self.SetBackgroundColour((0,0,0)) | |
361 | + | |
362 | + self.thumbnail_preview = bpp.BitmapPreviewSeries(self) | |
363 | + #self.bitmap_preview = bpp.BitmapPreviewSlice(self) | |
364 | + #self.bitmap_preview.Show(0) | |
365 | + | |
366 | + | |
367 | + self.sizer = wx.BoxSizer(wx.HORIZONTAL) | |
368 | + self.sizer.Add(self.thumbnail_preview, 1, wx.EXPAND | wx.ALL, 5) | |
369 | + #self.sizer.Add(self.bitmap_preview, 1, wx.EXPAND | wx.ALL, 5) | |
370 | + self.sizer.Fit(self) | |
371 | + | |
372 | + self.SetSizer(self.sizer) | |
373 | + | |
374 | + self.Layout() | |
375 | + self.Update() | |
376 | + self.SetAutoLayout(1) | |
377 | + | |
378 | + self.__bind_evt() | |
379 | + self._bind_gui_evt() | |
380 | + | |
381 | + def __bind_evt(self): | |
382 | + #Publisher.subscribe(self.ShowDicomSeries, 'Load bitmap preview') | |
383 | + #Publisher.subscribe(self.SetDicomSeries, 'Load group into import panel') | |
384 | + Publisher.subscribe(self.SetBitmapFiles, 'Load bitmap into import panel') | |
385 | + | |
386 | + def _bind_gui_evt(self): | |
387 | + self.thumbnail_preview.Bind(bpp.EVT_CLICK_SERIE, self.OnSelectSerie) | |
388 | + #self.bitmap_preview.Bind(bpp.EVT_CLICK_SLICE, self.OnSelectSlice) | |
389 | + | |
390 | + #def SetDicomSeries(self, pubsub_evt): | |
391 | + # group = pubsub_evt.data | |
392 | + # self.bitmap_preview.SetDicomGroup(group) | |
393 | + # self.bitmap_preview.Show(1) | |
394 | + # self.thumbnail_preview.Show(0) | |
395 | + # self.sizer.Layout() | |
396 | + # self.Update() | |
397 | + | |
398 | + def GetSelectedImagesRange(self): | |
399 | + return [self.bitmap_preview.first_selected, self.dicom_preview_last_selection] | |
400 | + | |
401 | + def SetBitmapFiles(self, pubsub_evt): | |
402 | + | |
403 | + | |
404 | + bitmap = pubsub_evt.data | |
405 | + #self.bitmap_preview.Show(0) | |
406 | + self.thumbnail_preview.Show(1) | |
407 | + | |
408 | + self.thumbnail_preview.SetBitmapFiles(bitmap) | |
409 | + #self.bitmap_preview.SetPatientGroups(patient) | |
410 | + | |
411 | + self.Update() | |
412 | + | |
413 | + def OnSelectSerie(self, evt): | |
414 | + data = evt.GetItemData() | |
415 | + | |
416 | + my_evt = SelectEvent(myEVT_SELECT_SERIE, self.GetId()) | |
417 | + my_evt.SetSelectedID(evt.GetSelectID()) | |
418 | + my_evt.SetItemData(evt.GetItemData()) | |
419 | + self.GetEventHandler().ProcessEvent(my_evt) | |
420 | + | |
421 | + #self.bitmap_preview.SetDicomGroup(data) | |
422 | + #self.bitmap_preview.Show(1) | |
423 | + #self.thumbnail_preview.Show(0) | |
424 | + self.sizer.Layout() | |
425 | + self.Show() | |
426 | + self.Update() | |
427 | + | |
428 | + def OnSelectSlice(self, evt): | |
429 | + my_evt = SelectEvent(myEVT_SELECT_SLICE, self.GetId()) | |
430 | + my_evt.SetSelectedID(evt.GetSelectID()) | |
431 | + my_evt.SetItemData(evt.GetItemData()) | |
432 | + self.GetEventHandler().ProcessEvent(my_evt) | |
433 | + | |
434 | + | |
435 | + #def ShowDicomSeries(self, pubsub_evt): | |
436 | + # patient = pubsub_evt.data | |
437 | + # if isinstance(patient, dcm.PatientGroup): | |
438 | + # self.thumbnail_preview.SetPatientGroups(patient) | |
439 | + # self.bitmap_preview.SetPatientGroups(patient) | |
440 | + | |
441 | + | |
442 | +class SlicePanel(wx.Panel): | |
443 | + def __init__(self, parent): | |
444 | + wx.Panel.__init__(self, parent, -1) | |
445 | + self.__init_gui() | |
446 | + self.__bind_evt() | |
447 | + | |
448 | + def __bind_evt(self): | |
449 | + #Publisher.subscribe(self.ShowDicomSeries, 'Load bitmap preview') | |
450 | + #Publisher.subscribe(self.SetDicomSeries, 'Load group into import panel') | |
451 | + Publisher.subscribe(self.SetBitmapFiles, 'Load bitmap into import panel') | |
452 | + | |
453 | + def __init_gui(self): | |
454 | + self.SetBackgroundColour((255,255,255)) | |
455 | + self.bitmap_preview = bpp.SingleImagePreview(self) | |
456 | + | |
457 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
458 | + sizer.Add(self.bitmap_preview, 1, wx.GROW|wx.EXPAND) | |
459 | + sizer.Fit(self) | |
460 | + self.SetSizer(sizer) | |
461 | + self.Layout() | |
462 | + self.Update() | |
463 | + self.SetAutoLayout(1) | |
464 | + self.sizer = sizer | |
465 | + | |
466 | + def SetBitmapFiles(self, pubsub_evt): | |
467 | + data = pubsub_evt.data | |
468 | + self.bitmap_preview.SetBitmapFiles(data) | |
469 | + self.sizer.Layout() | |
470 | + self.Update() | |
471 | + | |
472 | + #def SetDicomSeries(self, evt): | |
473 | + # group = evt.data | |
474 | + # self.bitmap_preview.SetDicomGroup(group) | |
475 | + # self.sizer.Layout() | |
476 | + # self.Update() | |
477 | + | |
478 | + #def ShowDicomSeries(self, pubsub_evt): | |
479 | + # patient = pubsub_evt.data | |
480 | + # group = patient.GetGroups()[0] | |
481 | + # self.bitmap_preview.SetDicomGroup(group) | |
482 | + # self.sizer.Layout() | |
483 | + # self.Update() | |
484 | + | ... | ... |
invesalius/gui/task_slice.py
... | ... | @@ -524,6 +524,7 @@ class MaskProperties(wx.Panel): |
524 | 524 | self.bind_evt_gradient = False |
525 | 525 | self.gradient.SetMinValue(thresh_min) |
526 | 526 | self.gradient.SetMaxValue(thresh_max) |
527 | + print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", thresh_min, thresh_max | |
527 | 528 | self.bind_evt_gradient = True |
528 | 529 | thresh = (thresh_min, thresh_max) |
529 | 530 | if thresh in Project().threshold_modes.values(): | ... | ... |
invesalius/presets.py
... | ... | @@ -73,12 +73,13 @@ class Presets(): |
73 | 73 | def UpdateThresholdModes(self, evt): |
74 | 74 | |
75 | 75 | thresh_min, thresh_max = evt.data |
76 | - | |
77 | 76 | presets_list = (self.thresh_ct, self.thresh_mri) |
78 | 77 | |
79 | 78 | for presets in presets_list: |
80 | 79 | for key in presets: |
81 | 80 | (t_min, t_max) = presets[key] |
81 | + | |
82 | + | |
82 | 83 | if (t_min is None) or (t_max is None): # setting custom preset |
83 | 84 | t_min = thresh_min |
84 | 85 | t_max = thresh_max |
... | ... | @@ -93,7 +94,7 @@ class Presets(): |
93 | 94 | t_min = thresh_min |
94 | 95 | if (t_max < thresh_min): |
95 | 96 | t_max = thresh_max |
96 | - | |
97 | + | |
97 | 98 | presets[key] = (t_min, t_max) |
98 | 99 | |
99 | 100 | Publisher.sendMessage('Update threshold limits', (thresh_min, | ... | ... |
... | ... | @@ -0,0 +1,389 @@ |
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 | +import os | |
20 | +import Queue | |
21 | +import threading | |
22 | +import tempfile | |
23 | +import sys | |
24 | +import vtk | |
25 | +import re | |
26 | +import constants as const | |
27 | +import wx | |
28 | + | |
29 | +from wx.lib.pubsub import pub as Publisher | |
30 | +from multiprocessing import cpu_count | |
31 | + | |
32 | +from vtk.util import numpy_support | |
33 | +from scipy import misc | |
34 | +import numpy | |
35 | +import imghdr | |
36 | + | |
37 | +from data import converters | |
38 | + | |
39 | +#flag to control vtk error in read files | |
40 | +no_error = True | |
41 | +vtk_error = False | |
42 | + | |
43 | +class Singleton: | |
44 | + | |
45 | + def __init__(self,klass): | |
46 | + self.klass = klass | |
47 | + self.instance = None | |
48 | + | |
49 | + def __call__(self,*args,**kwds): | |
50 | + if self.instance == None: | |
51 | + self.instance = self.klass(*args,**kwds) | |
52 | + return self.instance | |
53 | + | |
54 | +@Singleton | |
55 | +class BitmapData: | |
56 | + | |
57 | + def __init__(self): | |
58 | + self.data = None | |
59 | + | |
60 | + def GetData(self): | |
61 | + return self.data | |
62 | + | |
63 | + def SetData(self, data): | |
64 | + self.data = data | |
65 | + | |
66 | + def GetOnlyBitmapPath(self): | |
67 | + paths = [item[0] for item in self.data] | |
68 | + return paths | |
69 | + | |
70 | + def GetFirstBitmapSize(self): | |
71 | + return (self.data[0][3], self.data[0][4]) | |
72 | + | |
73 | + def IsAllBitmapSameSize(self): | |
74 | + sizes = [item[5] for item in self.data] | |
75 | + | |
76 | + k = {} | |
77 | + for v in sizes: | |
78 | + k[v] = '' | |
79 | + | |
80 | + if len(k.keys()) > 1: | |
81 | + return False | |
82 | + else: | |
83 | + return True | |
84 | + | |
85 | + def GetFirstPixelSize(self): | |
86 | + | |
87 | + path = self.data[0][0] | |
88 | + size = ReadBitmap(path).dtype.itemsize * 8 | |
89 | + | |
90 | + return size | |
91 | + | |
92 | + | |
93 | + | |
94 | +class BitmapFiles: | |
95 | + | |
96 | + def __init__(self): | |
97 | + self.bitmapfiles = [] | |
98 | + | |
99 | + def Add(self, bmp): | |
100 | + self.bitmapfiles.append(bmp) | |
101 | + | |
102 | + def Sort(self, x): | |
103 | + | |
104 | + c_re = re.compile('\d+') | |
105 | + | |
106 | + if len(c_re.findall(x[0])) > 0: | |
107 | + return c_re.findall(x[0])[-1] | |
108 | + else: | |
109 | + return '0' | |
110 | + | |
111 | + def GetValues(self): | |
112 | + bmpfile = self.bitmapfiles | |
113 | + bmpfile.sort(key = self.Sort) | |
114 | + | |
115 | + bmp_data = BitmapData() | |
116 | + bmp_data.data = bmpfile | |
117 | + | |
118 | + return bmpfile | |
119 | + | |
120 | +class LoadBitmap: | |
121 | + | |
122 | + def __init__(self, bmp_file, filepath): | |
123 | + self.bmp_file = bmp_file | |
124 | + if sys.platform == 'win32': | |
125 | + self.filepath = filepath.encode(utils.get_system_encoding()) | |
126 | + else: | |
127 | + self.filepath = filepath | |
128 | + | |
129 | + self.run() | |
130 | + | |
131 | + def run(self): | |
132 | + global vtk_error | |
133 | + | |
134 | + #----- verify extension ------------------ | |
135 | + #ex = self.filepath.split('.')[-1] | |
136 | + extension = VerifyDataType(self.filepath) | |
137 | + | |
138 | + file_name = self.filepath.split(os.path.sep)[-1] | |
139 | + | |
140 | + #if extension == 'bmp': | |
141 | + # reader = vtk.vtkBMPReader() | |
142 | + n_array = ReadBitmap(self.filepath) | |
143 | + | |
144 | + if not(isinstance(n_array, numpy.ndarray)): | |
145 | + return False | |
146 | + | |
147 | + image = converters.to_vtk(n_array, spacing=(1,1,1),\ | |
148 | + slice_number=1, orientation="AXIAL") | |
149 | + | |
150 | + | |
151 | + #reader.SetFileName(self.filepath) | |
152 | + #reader.Update() | |
153 | + | |
154 | + dim = image.GetDimensions() | |
155 | + x = dim[0] | |
156 | + y = dim[1] | |
157 | + | |
158 | + img = vtk.vtkImageResample() | |
159 | + img.SetInputData(image) | |
160 | + img.SetAxisMagnificationFactor ( 0, 0.25 ) | |
161 | + img.SetAxisMagnificationFactor ( 1, 0.25 ) | |
162 | + img.SetAxisMagnificationFactor ( 2, 1 ) | |
163 | + img.Update() | |
164 | + | |
165 | + tp = img.GetOutput().GetScalarTypeAsString() | |
166 | + | |
167 | + image_copy = vtk.vtkImageData() | |
168 | + image_copy.DeepCopy(img.GetOutput()) | |
169 | + | |
170 | + thumbnail_path = tempfile.mktemp() | |
171 | + | |
172 | + write_png = vtk.vtkPNGWriter() | |
173 | + write_png.SetInputConnection(img.GetOutputPort()) | |
174 | + write_png.AddObserver("WarningEvent", VtkErrorPNGWriter) | |
175 | + write_png.SetFileName(thumbnail_path) | |
176 | + write_png.Write() | |
177 | + | |
178 | + if vtk_error: | |
179 | + img = vtk.vtkImageCast() | |
180 | + img.SetInputData(image_copy) | |
181 | + img.SetOutputScalarTypeToUnsignedShort() | |
182 | + #img.SetClampOverflow(1) | |
183 | + img.Update() | |
184 | + | |
185 | + write_png = vtk.vtkPNGWriter() | |
186 | + write_png.SetInputConnection(img.GetOutputPort()) | |
187 | + write_png.SetFileName(thumbnail_path) | |
188 | + write_png.Write() | |
189 | + | |
190 | + vtk_error = False | |
191 | + | |
192 | + id = wx.NewId() | |
193 | + | |
194 | + bmp_item = [self.filepath, thumbnail_path, extension, x, y,\ | |
195 | + str(x) + ' x ' + str(y), file_name, id] | |
196 | + self.bmp_file.Add(bmp_item) | |
197 | + | |
198 | + | |
199 | +def yGetBitmaps(directory, recursive=True, gui=True): | |
200 | + """ | |
201 | + Return all full paths to DICOM files inside given directory. | |
202 | + """ | |
203 | + nfiles = 0 | |
204 | + # Find total number of files | |
205 | + if recursive: | |
206 | + for dirpath, dirnames, filenames in os.walk(directory): | |
207 | + nfiles += len(filenames) | |
208 | + else: | |
209 | + dirpath, dirnames, filenames = os.walk(directory) | |
210 | + nfiles = len(filenames) | |
211 | + | |
212 | + | |
213 | + counter = 0 | |
214 | + bmp_file = BitmapFiles() | |
215 | + | |
216 | + # Retrieve only TIFF, BMP, JPEG and PNG files | |
217 | + if recursive: | |
218 | + for dirpath, dirnames, filenames in os.walk(directory): | |
219 | + for name in filenames: | |
220 | + filepath = os.path.join(dirpath, name) | |
221 | + counter += 1 | |
222 | + if gui: | |
223 | + yield (counter,nfiles) | |
224 | + #LoadDicom(grouper, filepath) | |
225 | + LoadBitmap(bmp_file, filepath) | |
226 | + else: | |
227 | + dirpath, dirnames, filenames = os.walk(directory) | |
228 | + for name in filenames: | |
229 | + filepath = str(os.path.join(dirpath, name)) | |
230 | + counter += 1 | |
231 | + if gui: | |
232 | + yield (counter,nfiles) | |
233 | + #q.put(filepath) | |
234 | + | |
235 | + #for t in threads: | |
236 | + # q.put(0) | |
237 | + | |
238 | + #for t in threads: | |
239 | + # t.join() | |
240 | + | |
241 | + #TODO: Is this commented update necessary? | |
242 | + #grouper.Update() | |
243 | + yield bmp_file.GetValues() | |
244 | + | |
245 | + | |
246 | +class ProgressBitmapReader: | |
247 | + def __init__(self): | |
248 | + Publisher.subscribe(self.CancelLoad, "Cancel bitmap load") | |
249 | + | |
250 | + def CancelLoad(self, evt_pubsub): | |
251 | + self.running = False | |
252 | + self.stoped = True | |
253 | + | |
254 | + def SetWindowEvent(self, frame): | |
255 | + self.frame = frame | |
256 | + | |
257 | + def SetDirectoryPath(self, path,recursive=True): | |
258 | + self.running = True | |
259 | + self.stoped = False | |
260 | + self.GetBitmaps(path,recursive) | |
261 | + | |
262 | + def UpdateLoadFileProgress(self,cont_progress): | |
263 | + Publisher.sendMessage("Update bitmap load", cont_progress) | |
264 | + | |
265 | + def EndLoadFile(self, bitmap_list): | |
266 | + Publisher.sendMessage("End bitmap load", bitmap_list) | |
267 | + | |
268 | + def GetBitmaps(self, path, recursive): | |
269 | + | |
270 | + #if not const.VTK_WARNING: | |
271 | + # log_path = os.path.join(const.LOG_FOLDER, 'vtkoutput.txt') | |
272 | + # fow = vtk.vtkFileOutputWindow() | |
273 | + # fow.SetFileName(log_path) | |
274 | + # ow = vtk.vtkOutputWindow() | |
275 | + # ow.SetInstance(fow) | |
276 | + | |
277 | + y = yGetBitmaps(path, recursive) | |
278 | + for value_progress in y: | |
279 | + if not self.running: | |
280 | + break | |
281 | + if isinstance(value_progress, tuple): | |
282 | + self.UpdateLoadFileProgress(value_progress) | |
283 | + else: | |
284 | + self.EndLoadFile(value_progress) | |
285 | + | |
286 | + #Is necessary in the case user cancel | |
287 | + #the load, ensure that dicomdialog is closed | |
288 | + if(self.stoped): | |
289 | + self.UpdateLoadFileProgress(None) | |
290 | + self.stoped = False | |
291 | + | |
292 | +def VtkErrorPNGWriter(obj, f): | |
293 | + global vtk_error | |
294 | + vtk_error = True | |
295 | + | |
296 | +def ScipyRead(filepath): | |
297 | + try: | |
298 | + r = misc.imread(filepath, flatten=True) | |
299 | + dt = r.dtype | |
300 | + | |
301 | + if dt == "float" or dt == "float16"\ | |
302 | + or dt == "float32" or dt == "float64": | |
303 | + shift=-r.max()/2 | |
304 | + simage = numpy.zeros_like(r, dtype='int16') | |
305 | + simage[:] = r.astype('int32') + shift | |
306 | + | |
307 | + return simage | |
308 | + else: | |
309 | + return r | |
310 | + except(IOError): | |
311 | + return False | |
312 | + | |
313 | +def VtkRead(filepath, t): | |
314 | + | |
315 | + global no_error | |
316 | + | |
317 | + if t == "bmp": | |
318 | + reader = vtk.vtkBMPReader() | |
319 | + | |
320 | + elif t == "tiff" or t == "tif": | |
321 | + reader = vtk.vtkTIFFReader() | |
322 | + | |
323 | + elif t == "png": | |
324 | + reader = vtk.vtkPNGReader() | |
325 | + | |
326 | + elif t == "jpeg" or t == "jpg": | |
327 | + reader = vtk.vtkJPEGReader() | |
328 | + | |
329 | + else: | |
330 | + return False | |
331 | + | |
332 | + reader.AddObserver("ErrorEvent", VtkErrorToPy) | |
333 | + reader.SetFileName(filepath) | |
334 | + reader.Update() | |
335 | + | |
336 | + if no_error: | |
337 | + image = reader.GetOutput() | |
338 | + dim = image.GetDimensions() | |
339 | + | |
340 | + if reader.GetNumberOfScalarComponents() > 1: | |
341 | + luminanceFilter = vtk.vtkImageLuminance() | |
342 | + luminanceFilter.SetInputData(image) | |
343 | + luminanceFilter.Update() | |
344 | + | |
345 | + image = vtk.vtkImageData() | |
346 | + image.DeepCopy(luminanceFilter.GetOutput()) | |
347 | + | |
348 | + img_array = numpy_support.vtk_to_numpy(image.GetPointData().GetScalars()) | |
349 | + img_array.shape = (dim[1], dim[0]) | |
350 | + | |
351 | + return img_array | |
352 | + else: | |
353 | + no_error = True | |
354 | + return False | |
355 | + | |
356 | + | |
357 | +def ReadBitmap(filepath): | |
358 | + | |
359 | + t = VerifyDataType(filepath) | |
360 | + | |
361 | + if t == False: | |
362 | + return False | |
363 | + | |
364 | + img_array = VtkRead(filepath, t) | |
365 | + | |
366 | + if not(isinstance(img_array, numpy.ndarray)): | |
367 | + | |
368 | + no_error = True | |
369 | + | |
370 | + img_array = ScipyRead(filepath) | |
371 | + | |
372 | + if not(isinstance(img_array, numpy.ndarray)): | |
373 | + return False | |
374 | + | |
375 | + return img_array | |
376 | + | |
377 | + | |
378 | +def VtkErrorToPy(obj, evt): | |
379 | + global no_error | |
380 | + no_error = False | |
381 | + | |
382 | + | |
383 | +def VerifyDataType(filepath): | |
384 | + try: | |
385 | + t = imghdr.what(filepath) | |
386 | + return t | |
387 | + except IOError: | |
388 | + return False | |
389 | + | ... | ... |