From 794ea5034b68ff1c8de8138d88d266bfd9bfbf85 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Junqueira Amorim Date: Fri, 8 Jul 2016 15:39:51 -0300 Subject: [PATCH] Micro ct (#40) --- invesalius/constants.py | 2 +- invesalius/control.py | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- invesalius/data/imagedata_utils.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- invesalius/gui/bitmap_preview_panel.py | 903 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/gui/dialogs.py | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/gui/frame.py | 36 ++++++++++++++++++++++++++++++++++-- invesalius/gui/import_bitmap_panel.py | 484 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ invesalius/gui/task_slice.py | 1 + invesalius/presets.py | 5 +++-- invesalius/reader/bitmap_reader.py | 389 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 2268 insertions(+), 11 deletions(-) create mode 100644 invesalius/gui/bitmap_preview_panel.py create mode 100644 invesalius/gui/import_bitmap_panel.py create mode 100644 invesalius/reader/bitmap_reader.py diff --git a/invesalius/constants.py b/invesalius/constants.py index cd750fb..022cd89 100644 --- a/invesalius/constants.py +++ b/invesalius/constants.py @@ -452,7 +452,7 @@ VTK_WARNING = 0 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE, ID_PROJECT_CLOSE, ID_PROJECT_INFO, ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET, ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_ANALYZE_IMPORT, ID_PREFERENCES, -ID_DICOM_NETWORK] = [wx.NewId() for number in range(13)] +ID_DICOM_NETWORK, ID_TIFF_JPG_PNG] = [wx.NewId() for number in range(14)] ID_EXIT = wx.ID_EXIT ID_ABOUT = wx.ID_ABOUT diff --git a/invesalius/control.py b/invesalius/control.py index f5281ea..044de98 100644 --- a/invesalius/control.py +++ b/invesalius/control.py @@ -35,8 +35,10 @@ import project as prj import reader.analyze_reader as analyze import reader.dicom_grouper as dg import reader.dicom_reader as dcm +import reader.bitmap_reader as bmp import session as ses + import utils import gui.dialogs as dialogs import subprocess @@ -74,13 +76,20 @@ class Controller(): 'Save raycasting preset') Publisher.subscribe(self.OnOpenDicomGroup, 'Open DICOM group') + Publisher.subscribe(self.OnOpenBitmapFiles, + 'Open bitmap files') Publisher.subscribe(self.Progress, "Update dicom load") + Publisher.subscribe(self.Progress, "Update bitmap load") Publisher.subscribe(self.OnLoadImportPanel, "End dicom load") + Publisher.subscribe(self.OnLoadImportBitmapPanel, "End bitmap load") Publisher.subscribe(self.OnCancelImport, 'Cancel DICOM load') + Publisher.subscribe(self.OnCancelImportBitmap, 'Cancel bitmap load') + Publisher.subscribe(self.OnShowDialogCloseProject, 'Close Project') Publisher.subscribe(self.OnOpenProject, 'Open project') Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project') Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog') + Publisher.subscribe(self.OnShowBitmapFile, 'Show bitmap dialog') Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog') @@ -92,7 +101,9 @@ class Controller(): Publisher.sendMessage('Hide import panel') - + def OnCancelImportBitmap(self, pubsub_evt): + #self.cancel_import = True + Publisher.sendMessage('Hide import bitmap panel') ########################### ########################### @@ -119,9 +130,35 @@ class Controller(): self.LoadProject() Publisher.sendMessage("Enable state project", True) - + def OnShowBitmapFile(self, pubsub_evt): + self.ShowDialogImportBitmapFile() ########################### + def ShowDialogImportBitmapFile(self): + # Offer to save current project if necessary + session = ses.Session() + st = session.project_status + if (st == const.PROJ_NEW) or (st == const.PROJ_CHANGE): + filename = session.project_path[1] + answer = dialog.SaveChangesDialog2(filename) + if answer: + self.ShowDialogSaveProject() + self.CloseProject() + #Publisher.sendMessage("Enable state project", False) + Publisher.sendMessage('Set project name') + Publisher.sendMessage("Stop Config Recording") + Publisher.sendMessage("Set slice interaction style", const.STATE_DEFAULT) + + # Import TIFF, BMP, JPEG or PNG + dirpath = dialog.ShowImportBitmapDirDialog() + + if dirpath and not os.listdir(dirpath): + dialog.ImportEmptyDirectory(dirpath) + elif dirpath: + self.StartImportBitmapPanel(dirpath) + # Publisher.sendMessage("Load data to import panel", dirpath) + + def ShowDialogImportDirectory(self): # Offer to save current project if necessary session = ses.Session() @@ -291,6 +328,12 @@ class Controller(): ########################### + def StartImportBitmapPanel(self, path): + # retrieve DICOM files splited into groups + reader = bmp.ProgressBitmapReader() + reader.SetWindowEvent(self.frame) + reader.SetDirectoryPath(path) + Publisher.sendMessage('End busy cursor') def StartImportPanel(self, path): @@ -324,6 +367,26 @@ class Controller(): Publisher.sendMessage('Show import panel') Publisher.sendMessage("Show import panel in frame") + def OnLoadImportBitmapPanel(self, evt): + data = evt.data + ok = self.LoadImportBitmapPanel(data) + if ok: + Publisher.sendMessage('Show import bitmap panel in frame') + #Publisher.sendMessage("Show import panel in frame") + + def LoadImportBitmapPanel(self, data): + #if patient_series and isinstance(patient_series, list): + #Publisher.sendMessage("Load import panel", patient_series) + #first_patient = patient_series[0] + #Publisher.sendMessage("Load bitmap preview", first_patient) + if data: + Publisher.sendMessage("Load import bitmap panel", data) + + return True + else: + dialog.ImportInvalidFiles() + return False + def LoadImportPanel(self, patient_series): if patient_series and isinstance(patient_series, list): @@ -335,6 +398,9 @@ class Controller(): dialog.ImportInvalidFiles() return False + + #----------- to import by command line --------------------------------------------------- + def OnImportMedicalImages(self, pubsub_evt): directory = pubsub_evt.data self.ImportMedicalImages(directory) @@ -358,6 +424,8 @@ class Controller(): self.LoadProject() Publisher.sendMessage("Enable state project", True) + #------------------------------------------------------------------------------------- + def LoadProject(self): proj = prj.Project() @@ -401,9 +469,13 @@ class Controller(): Publisher.sendMessage('Show mask', (mask_index, True)) else: mask_name = const.MASK_NAME_PATTERN % (1,) - thresh = const.THRESHOLD_RANGE - colour = const.MASK_COLOUR[0] + if proj.modality != "UNKNOWN": + thresh = const.THRESHOLD_RANGE + else: + thresh = proj.threshold_range + + colour = const.MASK_COLOUR[0] Publisher.sendMessage('Create new mask', (mask_name, thresh, colour)) @@ -483,6 +555,138 @@ class Controller(): dirpath = session.CreateProject(filename) #proj.SavePlistProject(dirpath, filename) + + def CreateBitmapProject(self, bmp_data, rec_data, matrix, matrix_filename): + name_to_const = {"AXIAL":const.AXIAL, + "CORONAL":const.CORONAL, + "SAGITTAL":const.SAGITAL} + + name = rec_data[0] + orientation = rec_data[1] + sp_x = float(rec_data[2]) + sp_y = float(rec_data[3]) + sp_z = float(rec_data[4]) + interval = int(rec_data[5]) + + bits = bmp_data.GetFirstPixelSize() + sx, sy = size = bmp_data.GetFirstBitmapSize() + + proj = prj.Project() + proj.name = name + proj.modality = 'UNKNOWN' + proj.SetAcquisitionModality(proj.modality) + proj.matrix_shape = matrix.shape + proj.matrix_dtype = matrix.dtype.name + proj.matrix_filename = matrix_filename + #proj.imagedata = imagedata + #proj.dicom_sample = dicom + proj.original_orientation =\ + name_to_const[orientation.upper()] + proj.window = float(matrix.max()) + proj.level = float(matrix.max()/2) + + proj.threshold_range = int(matrix.min()), int(matrix.max()) + #const.THRESHOLD_RANGE = proj.threshold_range + + proj.spacing = self.Slice.spacing + + ###### + session = ses.Session() + filename = proj.name+".inv3" + + filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM + + dirpath = session.CreateProject(filename) + + def OnOpenBitmapFiles(self, pubsub_evt): + rec_data = pubsub_evt.data + bmp_data = bmp.BitmapData() + matrix, matrix_filename = self.OpenBitmapFiles(bmp_data, rec_data) + + self.CreateBitmapProject(bmp_data, rec_data, matrix, matrix_filename) + + self.LoadProject() + Publisher.sendMessage("Enable state project", True) + + + def OpenBitmapFiles(self, bmp_data, rec_data): + + if bmp_data.IsAllBitmapSameSize(): + + name = rec_data[0] + orientation = rec_data[1] + sp_x = float(rec_data[2]) + sp_y = float(rec_data[3]) + sp_z = float(rec_data[4]) + interval = int(rec_data[5]) + + interval += 1 + + filelist = bmp_data.GetOnlyBitmapPath()[::interval] + bits = bmp_data.GetFirstPixelSize() + + sx, sy = size = bmp_data.GetFirstBitmapSize() + n_slices = len(filelist) + resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8) + + zspacing = sp_z * interval + xyspacing = (sp_y, sp_x) + + if resolution_percentage < 1.0: + + re_dialog = dialog.ResizeImageDialog() + re_dialog.SetValue(int(resolution_percentage*100)) + re_dialog_value = re_dialog.ShowModal() + re_dialog.Close() + + if re_dialog_value == wx.ID_OK: + percentage = re_dialog.GetValue() + resolution_percentage = percentage / 100.0 + else: + return + + xyspacing = xyspacing[0] / resolution_percentage, xyspacing[1] / resolution_percentage + + + + self.matrix, scalar_range, self.filename = image_utils.bitmap2memmap(filelist, size, + orientation, (sp_z, sp_y, sp_x),resolution_percentage) + + + self.Slice = sl.Slice() + self.Slice.matrix = self.matrix + self.Slice.matrix_filename = self.filename + + if orientation == 'AXIAL': + self.Slice.spacing = xyspacing[0], xyspacing[1], zspacing + elif orientation == 'CORONAL': + self.Slice.spacing = xyspacing[0], zspacing, xyspacing[1] + elif orientation == 'SAGITTAL': + self.Slice.spacing = zspacing, xyspacing[1], xyspacing[0] + + # 1(a): Fix gantry tilt, if any + #tilt_value = dicom.acquisition.tilt + #if (tilt_value) and (gui): + # # Tell user gantry tilt and fix, according to answer + # message = _("Fix gantry tilt applying the degrees below") + # value = -1*tilt_value + # tilt_value = dialog.ShowNumberDialog(message, value) + # image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) + #elif (tilt_value) and not (gui): + # tilt_value = -1*tilt_value + # image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) + + self.Slice.window_level = float(self.matrix.max()/2) + self.Slice.window_width = float(self.matrix.max()) + + scalar_range = int(self.matrix.min()), int(self.matrix.max()) + Publisher.sendMessage('Update threshold limits list', scalar_range) + + return self.matrix, self.filename#, dicom + + else: + print "Error: All slices must be of the same size." + def OnOpenDicomGroup(self, pubsub_evt): group, interval, file_range = pubsub_evt.data matrix, matrix_filename, dicom = self.OpenDicomGroup(group, interval, file_range, gui=True) diff --git a/invesalius/data/imagedata_utils.py b/invesalius/data/imagedata_utils.py index 0ba01a1..8987413 100644 --- a/invesalius/data/imagedata_utils.py +++ b/invesalius/data/imagedata_utils.py @@ -32,7 +32,9 @@ from vtk.util import numpy_support import constants as const from data import vtk_utils +from reader import bitmap_reader import utils +import converters # TODO: Test cases which are originally in sagittal/coronal orientation # and have gantry @@ -416,6 +418,90 @@ class ImageCreator: return imagedata +def bitmap2memmap(files, slice_size, orientation, spacing, resolution_percentage): + """ + From a list of dicom files it creates memmap file in the temp folder and + returns it and its related filename. + """ + message = _("Generating multiplanar visualization...") + update_progress= vtk_utils.ShowProgress(len(files) - 1, dialog_type = "ProgressDialog") + + temp_file = tempfile.mktemp() + + if orientation == 'SAGITTAL': + if resolution_percentage == 1.0: + shape = slice_size[0], slice_size[1], len(files) + else: + shape = math.ceil(slice_size[0]*resolution_percentage),\ + math.ceil(slice_size[1]*resolution_percentage), len(files) + + elif orientation == 'CORONAL': + if resolution_percentage == 1.0: + shape = slice_size[1], len(files), slice_size[0] + else: + shape = math.ceil(slice_size[1]*resolution_percentage), len(files),\ + math.ceil(slice_size[0]*resolution_percentage) + else: + if resolution_percentage == 1.0: + shape = len(files), slice_size[1], slice_size[0] + else: + shape = len(files), math.ceil(slice_size[1]*resolution_percentage),\ + math.ceil(slice_size[0]*resolution_percentage) + + matrix = numpy.memmap(temp_file, mode='w+', dtype='int16', shape=shape) + cont = 0 + max_scalar = None + min_scalar = None + + for n, f in enumerate(files): + image_as_array = bitmap_reader.ReadBitmap(f) + + image = converters.to_vtk(image_as_array, spacing=spacing,\ + slice_number=1, orientation=orientation.upper()) + + if resolution_percentage != 1.0: + + + image_resized = ResampleImage2D(image, px=None, py=None,\ + resolution_percentage = resolution_percentage, update_progress = None) + + image = image_resized + + min_aux, max_aux = image.GetScalarRange() + if min_scalar is None or min_aux < min_scalar: + min_scalar = min_aux + + if max_scalar is None or max_aux > max_scalar: + max_scalar = max_aux + + array = numpy_support.vtk_to_numpy(image.GetPointData().GetScalars()) + array = array.astype("int16") + + array = image_as_array + + if orientation == 'CORONAL': + array.shape = matrix.shape[0], matrix.shape[2] + matrix[:, n, :] = array + elif orientation == 'SAGITTAL': + array.shape = matrix.shape[0], matrix.shape[1] + # TODO: Verify if it's necessary to add the slices swapped only in + # sagittal rmi or only in # Rasiane's case or is necessary in all + # sagittal cases. + matrix[:, :, n] = array + else: + print array.shape, matrix.shape + array.shape = matrix.shape[1], matrix.shape[2] + matrix[n] = array + update_progress(cont,message) + cont += 1 + + matrix.flush() + scalar_range = min_scalar, max_scalar + + return matrix, scalar_range, temp_file + + + def dcm2memmap(files, slice_size, orientation, resolution_percentage): """ 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): resolution_percentage = resolution_percentage, update_progress = None) image = image_resized - print ">>>>>>>>>", image.GetDimensions() min_aux, max_aux = image.GetScalarRange() if min_scalar is None or min_aux < min_scalar: @@ -482,7 +567,6 @@ def dcm2memmap(files, slice_size, orientation, resolution_percentage): # sagittal cases. matrix[:, :, n] = array else: - print array.shape, matrix.shape array.shape = matrix.shape[1], matrix.shape[2] matrix[n] = array update_progress(cont,message) diff --git a/invesalius/gui/bitmap_preview_panel.py b/invesalius/gui/bitmap_preview_panel.py new file mode 100644 index 0000000..9b1b621 --- /dev/null +++ b/invesalius/gui/bitmap_preview_panel.py @@ -0,0 +1,903 @@ +import wx +import vtk +import vtkgdcm +import time + +from vtk.util import numpy_support +from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor +from wx.lib.pubsub import pub as Publisher + +import constants as const +import data.vtk_utils as vtku +from data import converters +from reader import bitmap_reader +import utils + +NROWS = 3 +NCOLS = 6 +NUM_PREVIEWS = NCOLS*NROWS +PREVIEW_WIDTH = 70 +PREVIEW_HEIGTH = 70 + +PREVIEW_BACKGROUND = (255, 255, 255) # White + +STR_SIZE = _("Image size: %d x %d") +STR_SPC = _("Spacing: %.2f") +STR_LOCAL = _("Location: %.2f") +STR_PATIENT = "%s\n%s" +STR_ACQ = _("%s %s\nMade in InVesalius") + +myEVT_PREVIEW_CLICK = wx.NewEventType() +EVT_PREVIEW_CLICK = wx.PyEventBinder(myEVT_PREVIEW_CLICK, 1) + +myEVT_PREVIEW_DBLCLICK = wx.NewEventType() +EVT_PREVIEW_DBLCLICK = wx.PyEventBinder(myEVT_PREVIEW_DBLCLICK, 1) + +myEVT_CLICK_SLICE = wx.NewEventType() +# This event occurs when the user select a preview +EVT_CLICK_SLICE = wx.PyEventBinder(myEVT_CLICK_SLICE, 1) + +myEVT_CLICK_SERIE = wx.NewEventType() +# This event occurs when the user select a preview +EVT_CLICK_SERIE = wx.PyEventBinder(myEVT_CLICK_SERIE, 1) + +myEVT_CLICK = wx.NewEventType() +EVT_CLICK = wx.PyEventBinder(myEVT_CLICK, 1) + + +class SelectionEvent(wx.PyCommandEvent): + pass + + +class PreviewEvent(wx.PyCommandEvent): + def __init__(self , evtType, id): + super(PreviewEvent, self).__init__(evtType, id) + + def GetSelectID(self): + return self.SelectedID + + def SetSelectedID(self, id): + self.SelectedID = id + + def GetItemData(self): + return self.data + + def GetPressedShift(self): + return self.pressed_shift + + def SetItemData(self, data): + self.data = data + + def SetShiftStatus(self, status): + self.pressed_shift = status + + +class SerieEvent(PreviewEvent): + def __init__(self , evtType, id): + super(SerieEvent, self).__init__(evtType, id) + + +class BitmapInfo(object): + """ + Keep the informations and the image used by preview. + """ + def __init__(self, data): + #self.id = id + self.id = data[7] + self.title = data[6] + self.data = data + self.pos = data[8] + #self.subtitle = subtitle + self._preview = None + self.selected = False + #self.filename = "" + self.thumbnail_path = data[1] + + @property + def preview(self): + + if not self._preview: + bmp = wx.Bitmap(self.thumbnail_path, wx.BITMAP_TYPE_PNG) + self._preview = bmp.ConvertToImage() + return self._preview + + def release_thumbnail(self): + self._preview = None + +class DicomPaintPanel(wx.Panel): + def __init__(self, parent): + super(DicomPaintPanel, self).__init__(parent) + self._bind_events() + self.image = None + self.last_size = (10,10) + + def _bind_events(self): + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_SIZE, self.OnSize) + + def _build_bitmap(self, image): + bmp = wx.BitmapFromImage(image) + return bmp + + def _image_resize(self, image): + self.Update() + self.Layout() + new_size = self.GetSize() + # This is necessary due to darwin problem # + if new_size != (0,0): + self.last_size = new_size + return image.Scale(*new_size) + else: + return image.Scale(*self.last_size) + + def SetImage(self, image): + self.image = image + r_img = self._image_resize(image) + self.bmp = self._build_bitmap(r_img) + self.Refresh() + + def OnPaint(self, evt): + if self.image: + dc = wx.PaintDC(self) + dc.Clear() + dc.DrawBitmap(self.bmp, 0, 0) + + def OnSize(self, evt): + if self.image: + self.bmp = self._build_bitmap(self._image_resize(self.image)) + self.Refresh() + evt.Skip() + + +class Preview(wx.Panel): + """ + The little previews. + """ + def __init__(self, parent): + super(Preview, self).__init__(parent) + # Will it be white? + self.select_on = False + self.bitmap_info = None + self._init_ui() + self._bind_events() + + def _init_ui(self): + self.SetBackgroundColour(PREVIEW_BACKGROUND) + + self.title = wx.StaticText(self, -1, _("Image")) + self.subtitle = wx.StaticText(self, -1, _("Image")) + self.image_viewer = DicomPaintPanel(self) + + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.sizer.Add(self.title, 0, + wx.ALIGN_CENTER_HORIZONTAL) + self.sizer.Add(self.subtitle, 0, + wx.ALIGN_CENTER_HORIZONTAL) + self.sizer.Add(self.image_viewer, 1, wx.ALIGN_CENTRE_HORIZONTAL \ + | wx.SHAPED | wx.ALL, 5) + self.sizer.Fit(self) + + self.SetSizer(self.sizer) + + self.Layout() + self.Update() + self.Fit() + self.SetAutoLayout(1) + + def _bind_events(self): + self.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) + #self.interactor.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) + #self.panel.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) + #self.title.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) + #self.subtitle.Bind( wx.EVT_LEFT_DCLICK, self.OnDClick) + + self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) + #self.interactor.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) + #self.panel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) + #self.title.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) + #self.subtitle.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) + + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + #self.interactor.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + #self.panel.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + #self.title.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + #self.subtitle.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + + self.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) + self.title.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) + self.subtitle.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) + self.image_viewer.Bind(wx.EVT_LEFT_DOWN, self.OnSelect) + + #self.Bind(wx.EVT_SIZE, self.OnSize) + + def SetBitmapToPreview(self, bitmap_info): + """ + Set a dicom to preview. + """ + + """ + self.dicom_info = dicom_info + self.SetTitle(dicom_info.title) + self.SetSubtitle(dicom_info.subtitle) + self.ID = dicom_info.id + dicom_info.size = self.image_viewer.GetSize() + image = dicom_info.preview + self.image_viewer.SetImage(image) + self.data = dicom_info.id + self.select_on = dicom_info.selected + self.Select() + self.Update() + """ + + + + if self.bitmap_info: + self.bitmap_info.release_thumbnail() + + self.bitmap_info = bitmap_info + self.SetTitle(self.bitmap_info.title[-10:]) + self.SetSubtitle('') + + ##self.ID = bitmap_info.id + ##bitmap_info.size = self.image_viewer.GetSize() + image = self.bitmap_info.preview + + self.image_viewer.SetImage(image) + #self.data = bitmap_info.id + self.select_on = bitmap_info.selected + self.Select() + self.Update() + + def SetTitle(self, title): + self.title.SetLabel(title) + + def SetSubtitle(self, subtitle): + self.subtitle.SetLabel(subtitle) + + def OnEnter(self, evt): + if not self.select_on: + #c = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DHILIGHT) + c = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE) + self.SetBackgroundColour(c) + + def OnLeave(self, evt): + if not self.select_on: + c = (PREVIEW_BACKGROUND) + self.SetBackgroundColour(c) + + def OnSelect(self, evt): + + shift_pressed = False + if evt.m_shiftDown: + shift_pressed = True + + dicom_id = self.bitmap_info.id + self.select_on = True + self.bitmap_info.selected = True + self.Select() + + # Generating a EVT_PREVIEW_CLICK event + my_evt = SerieEvent(myEVT_PREVIEW_CLICK, self.GetId()) + + my_evt.SetSelectedID(self.bitmap_info.id) + my_evt.SetItemData(self.bitmap_info.data) + + my_evt.SetShiftStatus(shift_pressed) + my_evt.SetEventObject(self) + self.GetEventHandler().ProcessEvent(my_evt) + + Publisher.sendMessage('Set bitmap in preview panel', self.bitmap_info.pos) + + evt.Skip() + + + def OnSize(self, evt): + if self.bitmap_info: + self.SetBitmapToPreview(self.bitmap_info) + evt.Skip() + + def Select(self, on=True): + if self.select_on: + c = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT) + else: + c = (PREVIEW_BACKGROUND) + self.SetBackgroundColour(c) + self.Refresh() + + def OnDClick(self, evt): + my_evt = SerieEvent(myEVT_PREVIEW_DBLCLICK, self.GetId()) + my_evt.SetSelectedID(self.bitmap_info.id) + my_evt.SetItemData(self.bitmap_info.data) + my_evt.SetEventObject(self) + self.GetEventHandler().ProcessEvent(my_evt) + evt.Skip() + + +class BitmapPreviewSeries(wx.Panel): + """A dicom series preview panel""" + def __init__(self, parent): + super(BitmapPreviewSeries, self).__init__(parent) + # TODO: 3 pixels between the previews is a good idea? + # I have to test. + #self.sizer = wx.BoxSizer(wx.HORIZONTAL) + #self.SetSizer(self.sizer) + self.displayed_position = 0 + self.nhidden_last_display = 0 + self.selected_dicom = None + self.selected_panel = None + self._init_ui() + + def _init_ui(self): + scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) + self.scroll = scroll + + self.grid = wx.GridSizer(rows=NROWS, cols=NCOLS, vgap=3, hgap=3) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.AddSizer(self.grid, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) + + background_sizer = wx.BoxSizer(wx.HORIZONTAL) + background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) + background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) + self.SetSizer(background_sizer) + background_sizer.Fit(self) + + self.Layout() + self.Update() + self.SetAutoLayout(1) + + self.sizer = background_sizer + + self._Add_Panels_Preview() + self._bind_events() + + def _Add_Panels_Preview(self): + self.previews = [] + for i in xrange(NROWS): + for j in xrange(NCOLS): + p = Preview(self) + p.Bind(EVT_PREVIEW_CLICK, self.OnSelect) + #if (i == j == 0): + #self._show_shadow(p) + #p.Hide() + self.previews.append(p) + self.grid.Add(p, 1, flag=wx.EXPAND) + + #def _show_shadow(self, preview): + # preview.ShowShadow() + + def _bind_events(self): + # When the user scrolls the window + self.Bind(wx.EVT_SCROLL, self.OnScroll) + self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) + + def OnSelect(self, evt): + my_evt = SerieEvent(myEVT_CLICK_SERIE, self.GetId()) + my_evt.SetSelectedID(evt.GetSelectID()) + my_evt.SetItemData(evt.GetItemData()) + + if self.selected_dicom: + self.selected_dicom.selected = self.selected_dicom is \ + evt.GetEventObject().bitmap_info + self.selected_panel.select_on = self.selected_panel is evt.GetEventObject() + self.selected_panel.Select() + self.selected_panel = evt.GetEventObject() + self.selected_dicom = self.selected_panel.bitmap_info + self.GetEventHandler().ProcessEvent(my_evt) + evt.Skip() + + def SetBitmapFiles(self, data): + #self.files = data + self.files = [] + + bitmap = bitmap_reader.BitmapData() + bitmap.SetData(data) + + pos = 0 + for d in data: + d.append(pos) + info = BitmapInfo(d) + self.files.append(info) + pos += 1 + + scroll_range = len(self.files)/NCOLS + if scroll_range * NCOLS < len(self.files): + scroll_range +=1 + self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) + self._display_previews() + + + #def SetPatientGroups(self, patient): + # self.files = [] + # self.displayed_position = 0 + # self.nhidden_last_display = 0 + # group_list = patient.GetGroups() + # self.group_list = group_list + # n = 0 + # for group in group_list: + # info = BitmapInfo((group.dicom.patient.id, + # group.dicom.acquisition.serie_number), + # group.dicom, + # group.title, + # _("%d images") %(group.nslices)) + # self.files.append(info) + # n+=1 + # scroll_range = len(self.files)/NCOLS + # if scroll_range * NCOLS < len(self.files): + # scroll_range +=1 + # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) + # self._display_previews() + + + def _display_previews(self): + initial = self.displayed_position * NCOLS + final = initial + NUM_PREVIEWS + if len(self.files) < final: + for i in xrange(final-len(self.files)): + try: + self.previews[-i-1].Hide() + except IndexError: + utils.debug("doesn't exist!") + pass + self.nhidden_last_display = final-len(self.files) + else: + if self.nhidden_last_display: + for i in xrange(self.nhidden_last_display): + try: + self.previews[-i-1].Show() + except IndexError: + utils.debug("doesn't exist!") + pass + self.nhidden_last_display = 0 + + for f, p in zip(self.files[initial:final], self.previews): + p.SetBitmapToPreview(f) + if f.selected: + self.selected_panel = p + + for f, p in zip(self.files[initial:final], self.previews): + p.Show() + + + def OnScroll(self, evt=None): + if evt: + if self.displayed_position != evt.GetPosition(): + self.displayed_position = evt.GetPosition() + else: + if self.displayed_position != self.scroll.GetThumbPosition(): + self.displayed_position = self.scroll.GetThumbPosition() + self._display_previews() + + def OnWheel(self, evt): + d = evt.GetWheelDelta() / evt.GetWheelRotation() + self.scroll.SetThumbPosition(self.scroll.GetThumbPosition() - d) + self.OnScroll() + + +class SingleImagePreview(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + self.actor = None + self.__init_gui() + self.__init_vtk() + self.__bind_evt_gui() + self.__bind_pubsub() + self.dicom_list = [] + self.nimages = 1 + self.current_index = 0 + self.window_width = const.WINDOW_LEVEL[_("Bone")][0] + self.window_level = const.WINDOW_LEVEL[_("Bone")][1] + + def __init_vtk(self): + text_image_size = vtku.Text() + text_image_size.SetPosition(const.TEXT_POS_LEFT_UP) + text_image_size.SetValue("") + text_image_size.SetSize(const.TEXT_SIZE_SMALL) + self.text_image_size = text_image_size + + text_image_location = vtku.Text() + text_image_location.SetVerticalJustificationToBottom() + text_image_location.SetPosition(const.TEXT_POS_LEFT_DOWN) + text_image_location.SetValue("") + text_image_location.SetSize(const.TEXT_SIZE_SMALL) + self.text_image_location = text_image_location + + text_patient = vtku.Text() + text_patient.SetJustificationToRight() + text_patient.SetPosition(const.TEXT_POS_RIGHT_UP) + text_patient.SetValue("") + text_patient.SetSize(const.TEXT_SIZE_SMALL) + self.text_patient = text_patient + + text_acquisition = vtku.Text() + text_acquisition.SetJustificationToRight() + text_acquisition.SetVerticalJustificationToBottom() + text_acquisition.SetPosition(const.TEXT_POS_RIGHT_DOWN) + text_acquisition.SetValue("") + text_acquisition.SetSize(const.TEXT_SIZE_SMALL) + self.text_acquisition = text_acquisition + + renderer = vtk.vtkRenderer() + renderer.AddActor(text_image_size.actor) + renderer.AddActor(text_image_location.actor) + renderer.AddActor(text_patient.actor) + renderer.AddActor(text_acquisition.actor) + self.renderer = renderer + + style = vtk.vtkInteractorStyleImage() + + interactor = wxVTKRenderWindowInteractor(self.panel, -1, + size=wx.Size(340,340)) + interactor.GetRenderWindow().AddRenderer(renderer) + interactor.SetInteractorStyle(style) + interactor.Render() + self.interactor = interactor + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(interactor, 1, wx.GROW|wx.EXPAND) + sizer.Fit(self.panel) + self.panel.SetSizer(sizer) + self.Layout() + self.Update() + + def __init_gui(self): + self.panel = wx.Panel(self, -1) + + slider = wx.Slider(self, + id=-1, + value=0, + minValue=0, + maxValue=99, + style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS) + slider.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + slider.SetTickFreq(1, 1) + self.slider = slider + + checkbox = wx.CheckBox(self, -1, _("Auto-play")) + self.checkbox = checkbox + + in_sizer = wx.BoxSizer(wx.HORIZONTAL) + in_sizer.Add(slider, 1, wx.GROW|wx.EXPAND) + in_sizer.Add(checkbox, 0) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.panel, 20, wx.GROW|wx.EXPAND) + sizer.Add(in_sizer, 1, wx.GROW|wx.EXPAND) + sizer.Fit(self) + + self.SetSizer(sizer) + self.Layout() + self.Update() + self.SetAutoLayout(1) + + def __bind_evt_gui(self): + self.slider.Bind(wx.EVT_SLIDER, self.OnSlider) + self.checkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBox) + + def __bind_pubsub(self): + Publisher.subscribe(self.ShowBitmapByPosition, 'Set bitmap in preview panel') + + def ShowBitmapByPosition(self, evt): + pos = evt.data + self.ShowSlice(pos) + + + def OnSlider(self, evt): + pos = evt.GetInt() + self.ShowSlice(pos) + evt.Skip() + + def OnCheckBox(self, evt): + self.ischecked = evt.IsChecked() + if evt.IsChecked(): + wx.CallAfter(self.OnRun) + evt.Skip() + + def OnRun(self): + pos = self.slider.GetValue() + pos += 1 + if not (self.nimages- pos): + pos = 0 + self.slider.SetValue(pos) + self.ShowSlice(pos) + time.sleep(0.2) + if self.ischecked: + try: + wx.Yield() + #TODO: temporary fix necessary in the Windows XP 64 Bits + #BUG in wxWidgets http://trac.wxwidgets.org/ticket/10896 + except(wx._core.PyAssertionError): + utils.debug("wx._core.PyAssertionError") + finally: + wx.CallAfter(self.OnRun) + + def SetBitmapFiles(self, data): + #self.dicom_list = group.GetHandSortedList() + self.bitmap_list = data + self.current_index = 0 + self.nimages = len(data) + # GUI + self.slider.SetMax(self.nimages-1) + self.slider.SetValue(0) + self.ShowSlice() + + def ShowSlice(self, index = 0): + bitmap = self.bitmap_list[index] + + # UPDATE GUI + ## Text related to size + value = STR_SIZE %(bitmap[3], bitmap[4]) + self.text_image_size.SetValue(value) + + value1 = '' + value2 = '' + + value = "%s\n%s" %(value1, value2) + self.text_image_location.SetValue(value) + + + #self.text_patient.SetValue(value) + self.text_patient.SetValue('') + + #self.text_acquisition.SetValue(value) + self.text_acquisition.SetValue('') + + + + n_array = bitmap_reader.ReadBitmap(bitmap[0]) + + image = converters.to_vtk(n_array, spacing=(1,1,1),\ + slice_number=1, orientation="AXIAL") + + + # ADJUST CONTRAST + window_level = n_array.max()/2 + window_width = n_array.max() + + colorer = vtk.vtkImageMapToWindowLevelColors() + colorer.SetInputData(image) + colorer.SetWindow(float(window_width)) + colorer.SetLevel(float(window_level)) + colorer.Update() + + if self.actor is None: + self.actor = vtk.vtkImageActor() + self.renderer.AddActor(self.actor) + + # PLOT IMAGE INTO VIEWER + self.actor.SetInputData(colorer.GetOutput()) + self.renderer.ResetCamera() + self.interactor.Render() + + # Setting slider position + self.slider.SetValue(index) + + +#class BitmapPreviewSlice(wx.Panel): +# def __init__(self, parent): +# super(BitmapPreviewSlice, self).__init__(parent) +# # TODO: 3 pixels between the previews is a good idea? +# # I have to test. +# self.displayed_position = 0 +# self.nhidden_last_display = 0 +# self.selected_dicom = None +# self.selected_panel = None +# self.first_selection = None +# self.last_selection = None +# self._init_ui() +# +# def _init_ui(self): +# scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL) +# self.scroll = scroll +# +# self.grid = wx.GridSizer(rows=NROWS, cols=NCOLS, vgap=3, hgap=3) +# +# sizer = wx.BoxSizer(wx.HORIZONTAL) +# sizer.AddSizer(self.grid, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) +# +# background_sizer = wx.BoxSizer(wx.HORIZONTAL) +# background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2) +# background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW) +# self.SetSizer(background_sizer) +# background_sizer.Fit(self) +# +# self.Layout() +# self.Update() +# self.SetAutoLayout(1) +# +# self.sizer = background_sizer +# +# self._Add_Panels_Preview() +# self._bind_events() +# +# def _Add_Panels_Preview(self): +# self.previews = [] +# for i in xrange(NROWS): +# for j in xrange(NCOLS): +# p = Preview(self) +# p.Bind(EVT_PREVIEW_CLICK, self.OnPreviewClick) +# #p.Hide() +# self.previews.append(p) +# self.grid.Add(p, 1, flag=wx.EXPAND) +# +# def _bind_events(self): +# # When the user scrolls the window +# self.Bind(wx.EVT_SCROLL, self.OnScroll) +# self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) +# +# def SetDicomDirectory(self, directory): +# utils.debug("Setting Dicom Directory %s" % directory) +# self.directory = directory +# self.series = dicom_reader.GetSeries(directory)[0] +# +# def SetPatientGroups(self, patient): +# self.group_list = patient.GetGroups() +# +# +# #def SetDicomSerie(self, pos): +# # self.files = [] +# # self.displayed_position = 0 +# # self.nhidden_last_display = 0 +# # group = self.group_list[pos] +# # self.group = group +# # #dicom_files = group.GetList() +# # dicom_files = group.GetHandSortedList() +# # n = 0 +# # for dicom in dicom_files: +# # info = BitmapInfo(n, dicom, +# # _("Image %d") % (dicom.image.number), +# # "%.2f" % (dicom.image.position[2])) +# # self.files.append(info) +# # n+=1 +# +# # scroll_range = len(self.files)/NCOLS +# # if scroll_range * NCOLS < len(self.files): +# # scroll_range +=1 +# # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) +# +# # self._display_previews() +# +# #def SetDicomGroup(self, group): +# # self.files = [] +# # self.displayed_position = 0 +# # self.nhidden_last_display = 0 +# # #dicom_files = group.GetList() +# # dicom_files = group.GetHandSortedList() +# # n = 0 +# # for dicom in dicom_files: +# # info = BitmapInfo(n, dicom, +# # _("Image %d") % (dicom.image.number), +# # "%.2f" % (dicom.image.position[2]), +# # ) +# # self.files.append(info) +# # n+=1 +# +# # scroll_range = len(self.files)/NCOLS +# # if scroll_range * NCOLS < len(self.files): +# # scroll_range +=1 +# # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) +# +# # self._display_previews() +# +# #def SetDicomGroup(self, group): +# # self.files = [] +# # self.displayed_position = 0 +# # self.nhidden_last_display = 0 +# # #dicom_files = group.GetList() +# # dicom_files = group.GetHandSortedList() +# # n = 0 +# # for dicom in dicom_files: +# # info = BitmapInfo(n, dicom, +# # _("Image %d") % (dicom.image.number), +# # "%.2f" % (dicom.image.position[2]), +# # ) +# # self.files.append(info) +# # n+=1 +# +# # scroll_range = len(self.files)/NCOLS +# # if scroll_range * NCOLS < len(self.files): +# # scroll_range +=1 +# # self.scroll.SetScrollbar(0, NROWS, scroll_range, NCOLS) +# +# # self._display_previews() +# +# +# def _display_previews(self): +# initial = self.displayed_position * NCOLS +# final = initial + NUM_PREVIEWS +# if len(self.files) < final: +# for i in xrange(final-len(self.files)): +# try: +# self.previews[-i-1].Hide() +# except IndexError: +# utils.debug("doesn't exist!") +# self.nhidden_last_display = final-len(self.files) +# else: +# if self.nhidden_last_display: +# for i in xrange(self.nhidden_last_display): +# try: +# self.previews[-i-1].Show() +# except IndexError: +# utils.debug("doesn't exist!") +# self.nhidden_last_display = 0 +# +# for f, p in zip(self.files[initial:final], self.previews): +# p.SetBitmapToPreview(f) +# if f.selected: +# self.selected_panel = p +# #p.interactor.Render() +# +# for f, p in zip(self.files[initial:final], self.previews): +# p.Show() +# +# def OnPreviewClick(self, evt): +# +# dicom_id = evt.GetSelectID() +# +# if self.first_selection is None: +# self.first_selection = dicom_id +# +# if self.last_selection is None: +# self.last_selection = dicom_id +# +# +# if evt.GetPressedShift(): +# +# if dicom_id < self.first_selection and dicom_id < self.last_selection: +# self.first_selection = dicom_id +# else: +# self.last_selection = dicom_id +# else: +# self.first_selection = dicom_id +# self.last_selection = dicom_id +# +# for i in xrange(len(self.files)): +# +# if i == dicom_id: +# self.files[i].selected = True +# else: +# self.files[i].selected = False +# +# +# my_evt = SerieEvent(myEVT_CLICK_SLICE, self.GetId()) +# my_evt.SetSelectedID(evt.GetSelectID()) +# my_evt.SetItemData(evt.GetItemData()) +# +# if self.selected_dicom: +# self.selected_dicom.selected = self.selected_dicom is \ +# evt.GetEventObject().bitmap_info +# self.selected_panel.select_on = self.selected_panel is evt.GetEventObject() +# +# if self.first_selection != self.last_selection: +# for i in xrange(len(self.files)): +# if i >= self.first_selection and i <= self.last_selection: +# self.files[i].selected = True +# else: +# self.files[i].selected = False +# +# else: +# self.selected_panel.Select() +# +# self._display_previews() +# self.selected_panel = evt.GetEventObject() +# self.selected_dicom = self.selected_panel.bitmap_info +# self.GetEventHandler().ProcessEvent(my_evt) +# +# #Publisher.sendMessage("Selected Import Images", [self.first_selection, \ +# # self.last_selection]) +# +# def OnScroll(self, evt=None): +# if evt: +# if self.displayed_position != evt.GetPosition(): +# self.displayed_position = evt.GetPosition() +# else: +# if self.displayed_position != self.scroll.GetThumbPosition(): +# self.displayed_position = self.scroll.GetThumbPosition() +# self._display_previews() +# +# def OnWheel(self, evt): +# d = evt.GetWheelDelta() / evt.GetWheelRotation() +# self.scroll.SetThumbPosition(self.scroll.GetThumbPosition() - d) +# self.OnScroll() + + diff --git a/invesalius/gui/dialogs.py b/invesalius/gui/dialogs.py index 9ae9d95..22fbbb2 100644 --- a/invesalius/gui/dialogs.py +++ b/invesalius/gui/dialogs.py @@ -39,6 +39,12 @@ from gui.widgets.clut_imagedata import CLUTImageDataWidget, EVT_CLUT_NODE_CHANGE import numpy as np +try: + from agw import floatspin as FS +except ImportError: # if it's not there locally, try the wxPython lib. + import wx.lib.agw.floatspin as FS + + class MaskEvent(wx.PyCommandEvent): def __init__(self , evtType, id, mask_index): wx.PyCommandEvent.__init__(self, evtType, id,) @@ -306,6 +312,48 @@ def ShowImportDirDialog(): os.chdir(current_dir) return path +def ShowImportBitmapDirDialog(): + current_dir = os.path.abspath(".") + + if (sys.platform == 'win32') or (sys.platform == 'linux2'): + session = ses.Session() + + if (session.GetLastDicomFolder()): + folder = session.GetLastDicomFolder() + else: + folder = '' + else: + folder = '' + + dlg = wx.DirDialog(None, _("Choose a folder with TIFF, BMP, JPG or PNG:"), folder, + style=wx.DD_DEFAULT_STYLE + | wx.DD_DIR_MUST_EXIST + | wx.DD_CHANGE_DIR) + + path = None + try: + if dlg.ShowModal() == wx.ID_OK: + # GetPath returns in unicode, if a path has non-ascii characters a + # UnicodeEncodeError is raised. To avoid this, path is encoded in utf-8 + if sys.platform == "win32": + path = dlg.GetPath() + else: + path = dlg.GetPath().encode('utf-8') + + except(wx._core.PyAssertionError): #TODO: error win64 + if (dlg.GetPath()): + path = dlg.GetPath() + + if (sys.platform != 'darwin'): + if (path): + session.SetLastDicomFolder(path) + + # Only destroy a dialog after you're done with it. + dlg.Destroy() + os.chdir(current_dir) + return path + + def ShowSaveAsProjectDialog(default_filename=None): current_dir = os.path.abspath(".") dlg = wx.FileDialog(None, @@ -1630,3 +1678,114 @@ class ReorientImageDialog(wx.Dialog): Publisher.sendMessage('Disable style', const.SLICE_STATE_REORIENT) Publisher.sendMessage('Enable style', const.STATE_DEFAULT) self.Destroy() + + + +class ImportBitmapParameters(wx.Dialog): + + def __init__(self): + pre = wx.PreDialog() + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Parameters"),size=wx.Size(380,230),\ + style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP) + + self.interval = 0 + + self.PostCreate(pre) + + self._init_gui() + + self.bind_evts() + self.CenterOnScreen() + + + def _init_gui(self): + + + p = wx.Panel(self, -1, style = wx.TAB_TRAVERSAL + | wx.CLIP_CHILDREN + | wx.FULL_REPAINT_ON_RESIZE) + + gbs_principal = self.gbs = wx.GridBagSizer(3,1) + + gbs = self.gbs = wx.GridBagSizer(4, 2) + + stx_name = wx.StaticText(p, -1, _(u"Project name:")) + tx_name = self.tx_name = wx.TextCtrl(p, -1, "InVesalius Bitmap", size=wx.Size(220,-1)) + + stx_orientation = wx.StaticText(p, -1, _(u"Slices orientation:")) + cb_orientation_options = [_(u'Axial'), _(u'Coronal'), _(u'Sagital')] + cb_orientation = self.cb_orientation = wx.ComboBox(p, value="Axial", choices=cb_orientation_options,\ + size=wx.Size(160,-1), style=wx.CB_DROPDOWN|wx.CB_READONLY) + + stx_spacing = wx.StaticText(p, -1, _(u"Spacing (mm):")) + + gbs.Add(stx_name, (0,0)) + gbs.Add(tx_name, (0,1)) + + gbs.Add(stx_orientation, (1,0)) + gbs.Add(cb_orientation, (1,1)) + + gbs.Add(stx_spacing, (2,0)) + + #--- spacing -------------- + gbs_spacing = wx.GridBagSizer(2, 6) + + stx_spacing_x = stx_spacing_x = wx.StaticText(p, -1, _(u"X:")) + fsp_spacing_x = self.fsp_spacing_x = FS.FloatSpin(p, -1, min_val=0,\ + increment=0.25, value=1.0, digits=6) + + stx_spacing_y = stx_spacing_y = wx.StaticText(p, -1, _(u"Y:")) + fsp_spacing_y = self.fsp_spacing_y = FS.FloatSpin(p, -1, min_val=0,\ + increment=0.25, value=1.0, digits=6) + + stx_spacing_z = stx_spacing_z = wx.StaticText(p, -1, _(u"Z:")) + fsp_spacing_z = self.fsp_spacing_z = FS.FloatSpin(p, -1, min_val=0,\ + increment=0.25, value=1.0, digits=6) + + gbs_spacing.Add(stx_spacing_x, (0,0)) + gbs_spacing.Add(fsp_spacing_x, (0,1)) + + gbs_spacing.Add(stx_spacing_y, (0,2)) + gbs_spacing.Add(fsp_spacing_y, (0,3)) + + gbs_spacing.Add(stx_spacing_z, (0,4)) + gbs_spacing.Add(fsp_spacing_z, (0,5)) + + #----- buttons ------------------------ + gbs_button = wx.GridBagSizer(1, 4) + + btn_ok = self.btn_ok= wx.Button(p, wx.ID_OK) + btn_ok.SetDefault() + + btn_cancel = wx.Button(p, wx.ID_CANCEL) + + gbs_button.Add(btn_cancel, (1,2)) + gbs_button.Add(btn_ok, (1,3)) + + + gbs_principal.Add(gbs, (0,0)) + gbs_principal.Add(gbs_spacing, (1,0)) + gbs_principal.Add(gbs_button, (2,0), flag = wx.ALIGN_RIGHT) + + box = wx.BoxSizer() + box.Add(gbs_principal, 1, wx.ALL|wx.EXPAND, 10) + + p.SetSizer(box) + + + def bind_evts(self): + self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) + + def SetInterval(self, v): + self.interval = v + + def OnOk(self, evt): + self.Close() + self.Destroy() + + values = [self.tx_name.GetValue(), self.cb_orientation.GetValue().upper(),\ + self.fsp_spacing_x.GetValue(), self.fsp_spacing_y.GetValue(),\ + self.fsp_spacing_z.GetValue(), self.interval] + Publisher.sendMessage('Open bitmap files', values) + + diff --git a/invesalius/gui/frame.py b/invesalius/gui/frame.py index 6fe9755..162f9af 100644 --- a/invesalius/gui/frame.py +++ b/invesalius/gui/frame.py @@ -36,6 +36,7 @@ import default_tasks as tasks import default_viewers as viewers import gui.dialogs as dlg import import_panel as imp +import import_bitmap_panel as imp_bmp import import_network_panel as imp_net import project as prj import session as ses @@ -126,6 +127,7 @@ class Frame(wx.Frame): sub(self._ShowImportPanel, 'Show import panel in frame') #sub(self._ShowHelpMessage, 'Show help message') sub(self._ShowImportNetwork, 'Show retrieve dicom panel') + sub(self._ShowImportBitmap, 'Show import bitmap panel in frame') sub(self._ShowTask, 'Show task panel') sub(self._UpdateAUI, 'Update AUI') sub(self._Exit, 'Exit') @@ -170,8 +172,14 @@ class Frame(wx.Frame): # are shown, this should be hiden caption = _("Preview medical data to be reconstructed") aui_manager.AddPane(imp.Panel(self), wx.aui.AuiPaneInfo(). - Name("Import").Centre().Hide(). - MaximizeButton(True).Floatable(True). + Name("Import").CloseButton(False).Centre().Hide(). + MaximizeButton(False).Floatable(True). + Caption(caption).CaptionVisible(True)) + + caption = _("Preview bitmap to be reconstructed") + aui_manager.AddPane(imp_bmp.Panel(self), wx.aui.AuiPaneInfo(). + Name("ImportBMP").CloseButton(False).Centre().Hide(). + MaximizeButton(False).Floatable(True). Caption(caption).CaptionVisible(True)) ncaption = _("Retrieve DICOM from PACS") @@ -298,6 +306,9 @@ class Frame(wx.Frame): Publisher.sendMessage("Set layout button full") aui_manager = self.aui_manager aui_manager.GetPane("Import").Show(0) + + aui_manager.GetPane("ImportBMP").Show(0) + aui_manager.GetPane("Data").Show(1) aui_manager.GetPane("Tasks").Show(1) aui_manager.Update() @@ -314,6 +325,18 @@ class Frame(wx.Frame): aui_manager.GetPane("Import").Show(0) aui_manager.Update() + def _ShowImportBitmap(self, evt_pubsub): + """ + Show viewers and task, hide import panel. + """ + Publisher.sendMessage("Set layout button full") + aui_manager = self.aui_manager + aui_manager.GetPane("ImportBMP").Show(1) + aui_manager.GetPane("Data").Show(0) + aui_manager.GetPane("Tasks").Show(0) + aui_manager.GetPane("Import").Show(0) + aui_manager.Update() + def _ShowHelpMessage(self, evt_pubsub): aui_manager = self.aui_manager pos = aui_manager.GetPane("Data").window.GetScreenPosition() @@ -371,6 +394,8 @@ class Frame(wx.Frame): self.ShowOpenProject() elif id == const.ID_ANALYZE_IMPORT: self.ShowAnalyzeImporter() + elif id == const.ID_TIFF_JPG_PNG: + self.ShowBitmapImporter() elif id == const.ID_PROJECT_SAVE: session = ses.Session() if session.temp_item: @@ -501,6 +526,12 @@ class Frame(wx.Frame): """ Publisher.sendMessage('Show analyze dialog', True) + def ShowBitmapImporter(self): + """ + Tiff, BMP, JPEG and PNG + """ + Publisher.sendMessage('Show bitmap dialog', True) + def FlipVolume(self, axis): Publisher.sendMessage('Flip volume', axis) Publisher.sendMessage('Reload actual slice') @@ -581,6 +612,7 @@ class MenuBar(wx.MenuBar): #Import Others Files others_file_menu = wx.Menu() others_file_menu.Append(const.ID_ANALYZE_IMPORT, "Analyze") + others_file_menu.Append(const.ID_TIFF_JPG_PNG, "TIFF,BMP,JPG or PNG") # FILE file_menu = wx.Menu() diff --git a/invesalius/gui/import_bitmap_panel.py b/invesalius/gui/import_bitmap_panel.py new file mode 100644 index 0000000..8942658 --- /dev/null +++ b/invesalius/gui/import_bitmap_panel.py @@ -0,0 +1,484 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- +import wx +import wx.gizmos as gizmos +from wx.lib.pubsub import pub as Publisher +import wx.lib.splitter as spl + +import constants as const +import gui.dialogs as dlg +import bitmap_preview_panel as bpp +import reader.dicom_grouper as dcm +from dialogs import ImportBitmapParameters + +myEVT_SELECT_SERIE = wx.NewEventType() +EVT_SELECT_SERIE = wx.PyEventBinder(myEVT_SELECT_SERIE, 1) + +myEVT_SELECT_SLICE = wx.NewEventType() +EVT_SELECT_SLICE = wx.PyEventBinder(myEVT_SELECT_SLICE, 1) + +myEVT_SELECT_PATIENT = wx.NewEventType() +EVT_SELECT_PATIENT = wx.PyEventBinder(myEVT_SELECT_PATIENT, 1) + +myEVT_SELECT_SERIE_TEXT = wx.NewEventType() +EVT_SELECT_SERIE_TEXT = wx.PyEventBinder(myEVT_SELECT_SERIE_TEXT, 1) + +class SelectEvent(wx.PyCommandEvent): + def __init__(self , evtType, id): + super(SelectEvent, self).__init__(evtType, id) + + def GetSelectID(self): + return self.SelectedID + + def SetSelectedID(self, id): + self.SelectedID = id + + def GetItemData(self): + return self.data + + def SetItemData(self, data): + self.data = data + + +class Panel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, pos=wx.Point(5, 5))#, + #size=wx.Size(280, 656)) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(InnerPanel(self), 1, wx.EXPAND|wx.GROW|wx.ALL, 5) + + self.SetSizer(sizer) + sizer.Fit(self) + + self.Layout() + self.Update() + self.SetAutoLayout(1) + + +# Inner fold panel +class InnerPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, pos=wx.Point(5, 5))#, + #size=wx.Size(680, 656)) + + self.patients = [] + self.first_image_selection = None + self.last_image_selection = None + self._init_ui() + self._bind_events() + self._bind_pubsubevt() + + def _init_ui(self): + splitter = spl.MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE) + splitter.SetOrientation(wx.VERTICAL) + self.splitter = splitter + + panel = wx.Panel(self) + self.btn_cancel = wx.Button(panel, wx.ID_CANCEL) + self.btn_ok = wx.Button(panel, wx.ID_OK, _("Import")) + + btnsizer = wx.StdDialogButtonSizer() + btnsizer.AddButton(self.btn_ok) + btnsizer.AddButton(self.btn_cancel) + btnsizer.Realize() + + self.combo_interval = wx.ComboBox(panel, -1, "", choices=const.IMPORT_INTERVAL, + style=wx.CB_DROPDOWN|wx.CB_READONLY) + self.combo_interval.SetSelection(0) + + inner_sizer = wx.BoxSizer(wx.HORIZONTAL) + inner_sizer.AddSizer(btnsizer, 0, wx.LEFT|wx.TOP, 5) + inner_sizer.Add(self.combo_interval, 0, wx.LEFT|wx.RIGHT|wx.TOP, 5) + panel.SetSizer(inner_sizer) + inner_sizer.Fit(panel) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(splitter, 20, wx.EXPAND) + sizer.Add(panel, 0, wx.EXPAND|wx.LEFT, 90) + + self.text_panel = TextPanel(splitter) + splitter.AppendWindow(self.text_panel, 250) + + self.image_panel = ImagePanel(splitter) + splitter.AppendWindow(self.image_panel, 250) + + self.SetSizer(sizer) + sizer.Fit(self) + + self.Layout() + self.Update() + self.SetAutoLayout(1) + + def _bind_pubsubevt(self): + Publisher.subscribe(self.ShowBitmapPreview, "Load import bitmap panel") + Publisher.subscribe(self.GetSelectedImages ,"Selected Import Images") + + def ShowBitmapPreview(self, pubsub_evt): + data = pubsub_evt.data + #self.patients.extend(dicom_groups) + self.text_panel.Populate(data) + + def GetSelectedImages(self, pubsub_evt): + self.first_image_selection = pubsub_evt.data[0] + self.last_image_selection = pubsub_evt.data[1] + + def _bind_events(self): + self.Bind(EVT_SELECT_SERIE, self.OnSelectSerie) + self.Bind(EVT_SELECT_SLICE, self.OnSelectSlice) + self.Bind(EVT_SELECT_PATIENT, self.OnSelectPatient) + self.btn_ok.Bind(wx.EVT_BUTTON, self.OnClickOk) + self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnClickCancel) + self.text_panel.Bind(EVT_SELECT_SERIE_TEXT, self.OnDblClickTextPanel) + + def OnSelectSerie(self, evt): + #patient_id, serie_number = evt.GetSelectID() + #self.text_panel.SelectSerie(evt.GetSelectID()) + #for patient in self.patients: + # if patient_id == patient.GetDicomSample().patient.id: + # for group in patient.GetGroups(): + # if serie_number == group.GetDicomSample().acquisition.serie_number: + # self.image_panel.SetSerie(group) + + pass + + + + def OnSelectSlice(self, evt): + pass + + def OnSelectPatient(self, evt): + pass + + def OnDblClickTextPanel(self, evt): + group = evt.GetItemData() + self.LoadDicom(group) + + def OnClickOk(self, evt): + parm = dlg.ImportBitmapParameters() + parm.SetInterval(self.combo_interval.GetSelection()) + parm.ShowModal() + + group = self.text_panel.GetSelection() + if group: + self.LoadDicom(group) + + def OnClickCancel(self, evt): + Publisher.sendMessage("Cancel DICOM load") + + def LoadDicom(self, group): + #interval = self.combo_interval.GetSelection() + #if not isinstance(group, dcm.DicomGroup): + # group = max(group.GetGroups(), key=lambda g: g.nslices) + + #slice_amont = group.nslices + #if (self.first_image_selection != None) and (self.first_image_selection != self.last_image_selection): + # slice_amont = (self.last_image_selection) - self.first_image_selection + # slice_amont += 1 + # if slice_amont == 0: + # slice_amont = group.nslices + + #nslices_result = slice_amont / (interval + 1) + #if (nslices_result > 1): + # Publisher.sendMessage('Open DICOM group', (group, interval, + # [self.first_image_selection, self.last_image_selection])) + #else: + # dlg.MissingFilesForReconstruction() + pass + + +class TextPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + + self._selected_by_user = True + self.idserie_treeitem = {} + self.treeitem_idpatient = {} + + self.__init_gui() + self.__bind_events_wx() + self.__bind_pubsub_evt() + + def __bind_pubsub_evt(self): + Publisher.subscribe(self.SelectSeries, 'Select series in import panel') + + def __bind_events_wx(self): + self.Bind(wx.EVT_SIZE, self.OnSize) + + def __init_gui(self): + tree = gizmos.TreeListCtrl(self, -1, style = + wx.TR_DEFAULT_STYLE + | wx.TR_HIDE_ROOT + | wx.TR_ROW_LINES + | wx.TR_COLUMN_LINES + | wx.TR_FULL_ROW_HIGHLIGHT + | wx.TR_SINGLE + | wx.TR_HIDE_ROOT + ) + + + tree.AddColumn(_("Path")) + tree.AddColumn(_("Type")) + tree.AddColumn(_("Width x Height")) + + + tree.SetMainColumn(0) + tree.SetColumnWidth(0, 880) + tree.SetColumnWidth(1, 60) + tree.SetColumnWidth(2, 130) + + self.root = tree.AddRoot(_("InVesalius Database")) + self.tree = tree + + def SelectSeries(self, pubsub_evt): + group_index = pubsub_evt.data + + def Populate(self, data): + tree = self.tree + for value in data: + parent = tree.AppendItem(self.root, value[0]) + self.tree.SetItemText(parent, value[2], 1) + self.tree.SetItemText(parent, value[5], 2) + + tree.Expand(self.root) + #tree.SelectItem(parent_select) + tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivate) + tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged) + + Publisher.sendMessage('Load bitmap into import panel', data) + + + def OnSelChanged(self, evt): + item = self.tree.GetSelection() + if self._selected_by_user: + group = self.tree.GetItemPyData(item) + if isinstance(group, dcm.DicomGroup): + Publisher.sendMessage('Load group into import panel', + group) + + elif isinstance(group, dcm.PatientGroup): + id = group.GetDicomSample().patient.id + my_evt = SelectEvent(myEVT_SELECT_PATIENT, self.GetId()) + my_evt.SetSelectedID(id) + self.GetEventHandler().ProcessEvent(my_evt) + + Publisher.sendMessage('Load bitmap into import panel', + + group) + else: + parent_id = self.tree.GetItemParent(item) + self.tree.Expand(parent_id) + evt.Skip() + + def OnActivate(self, evt): + item = evt.GetItem() + group = self.tree.GetItemPyData(item) + my_evt = SelectEvent(myEVT_SELECT_SERIE_TEXT, self.GetId()) + my_evt.SetItemData(group) + self.GetEventHandler().ProcessEvent(my_evt) + + def OnSize(self, evt): + self.tree.SetSize(self.GetSize()) + + def SelectSerie(self, serie): + self._selected_by_user = False + item = self.idserie_treeitem[serie] + self.tree.SelectItem(item) + self._selected_by_user = True + + def GetSelection(self): + """Get selected item""" + item = self.tree.GetSelection() + group = self.tree.GetItemPyData(item) + return group + + +class ImagePanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + self._init_ui() + self._bind_events() + + def _init_ui(self): + splitter = spl.MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE) + splitter.SetOrientation(wx.HORIZONTAL) + self.splitter = splitter + + splitter.ContainingSizer = wx.BoxSizer(wx.HORIZONTAL) + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(splitter, 1, wx.EXPAND) + self.SetSizer(sizer) + + self.text_panel = SeriesPanel(splitter) + splitter.AppendWindow(self.text_panel, 600) + + self.image_panel = SlicePanel(splitter) + splitter.AppendWindow(self.image_panel, 250) + + self.SetSizer(sizer) + sizer.Fit(self) + + self.Layout() + self.Update() + self.SetAutoLayout(1) + + def _bind_events(self): + self.text_panel.Bind(EVT_SELECT_SERIE, self.OnSelectSerie) + self.text_panel.Bind(EVT_SELECT_SLICE, self.OnSelectSlice) + + def OnSelectSerie(self, evt): + evt.Skip() + + def OnSelectSlice(self, evt): + self.image_panel.bitmap_preview.ShowSlice(evt.GetSelectID()) + evt.Skip() + + def SetSerie(self, serie): + self.image_panel.bitmap_preview.SetDicomGroup(serie) + + +class SeriesPanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + #self.SetBackgroundColour((0,0,0)) + + self.thumbnail_preview = bpp.BitmapPreviewSeries(self) + #self.bitmap_preview = bpp.BitmapPreviewSlice(self) + #self.bitmap_preview.Show(0) + + + self.sizer = wx.BoxSizer(wx.HORIZONTAL) + self.sizer.Add(self.thumbnail_preview, 1, wx.EXPAND | wx.ALL, 5) + #self.sizer.Add(self.bitmap_preview, 1, wx.EXPAND | wx.ALL, 5) + self.sizer.Fit(self) + + self.SetSizer(self.sizer) + + self.Layout() + self.Update() + self.SetAutoLayout(1) + + self.__bind_evt() + self._bind_gui_evt() + + def __bind_evt(self): + #Publisher.subscribe(self.ShowDicomSeries, 'Load bitmap preview') + #Publisher.subscribe(self.SetDicomSeries, 'Load group into import panel') + Publisher.subscribe(self.SetBitmapFiles, 'Load bitmap into import panel') + + def _bind_gui_evt(self): + self.thumbnail_preview.Bind(bpp.EVT_CLICK_SERIE, self.OnSelectSerie) + #self.bitmap_preview.Bind(bpp.EVT_CLICK_SLICE, self.OnSelectSlice) + + #def SetDicomSeries(self, pubsub_evt): + # group = pubsub_evt.data + # self.bitmap_preview.SetDicomGroup(group) + # self.bitmap_preview.Show(1) + # self.thumbnail_preview.Show(0) + # self.sizer.Layout() + # self.Update() + + def GetSelectedImagesRange(self): + return [self.bitmap_preview.first_selected, self.dicom_preview_last_selection] + + def SetBitmapFiles(self, pubsub_evt): + + + bitmap = pubsub_evt.data + #self.bitmap_preview.Show(0) + self.thumbnail_preview.Show(1) + + self.thumbnail_preview.SetBitmapFiles(bitmap) + #self.bitmap_preview.SetPatientGroups(patient) + + self.Update() + + def OnSelectSerie(self, evt): + data = evt.GetItemData() + + my_evt = SelectEvent(myEVT_SELECT_SERIE, self.GetId()) + my_evt.SetSelectedID(evt.GetSelectID()) + my_evt.SetItemData(evt.GetItemData()) + self.GetEventHandler().ProcessEvent(my_evt) + + #self.bitmap_preview.SetDicomGroup(data) + #self.bitmap_preview.Show(1) + #self.thumbnail_preview.Show(0) + self.sizer.Layout() + self.Show() + self.Update() + + def OnSelectSlice(self, evt): + my_evt = SelectEvent(myEVT_SELECT_SLICE, self.GetId()) + my_evt.SetSelectedID(evt.GetSelectID()) + my_evt.SetItemData(evt.GetItemData()) + self.GetEventHandler().ProcessEvent(my_evt) + + + #def ShowDicomSeries(self, pubsub_evt): + # patient = pubsub_evt.data + # if isinstance(patient, dcm.PatientGroup): + # self.thumbnail_preview.SetPatientGroups(patient) + # self.bitmap_preview.SetPatientGroups(patient) + + +class SlicePanel(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1) + self.__init_gui() + self.__bind_evt() + + def __bind_evt(self): + #Publisher.subscribe(self.ShowDicomSeries, 'Load bitmap preview') + #Publisher.subscribe(self.SetDicomSeries, 'Load group into import panel') + Publisher.subscribe(self.SetBitmapFiles, 'Load bitmap into import panel') + + def __init_gui(self): + self.SetBackgroundColour((255,255,255)) + self.bitmap_preview = bpp.SingleImagePreview(self) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.bitmap_preview, 1, wx.GROW|wx.EXPAND) + sizer.Fit(self) + self.SetSizer(sizer) + self.Layout() + self.Update() + self.SetAutoLayout(1) + self.sizer = sizer + + def SetBitmapFiles(self, pubsub_evt): + data = pubsub_evt.data + self.bitmap_preview.SetBitmapFiles(data) + self.sizer.Layout() + self.Update() + + #def SetDicomSeries(self, evt): + # group = evt.data + # self.bitmap_preview.SetDicomGroup(group) + # self.sizer.Layout() + # self.Update() + + #def ShowDicomSeries(self, pubsub_evt): + # patient = pubsub_evt.data + # group = patient.GetGroups()[0] + # self.bitmap_preview.SetDicomGroup(group) + # self.sizer.Layout() + # self.Update() + diff --git a/invesalius/gui/task_slice.py b/invesalius/gui/task_slice.py index 41d3ebf..2e74870 100644 --- a/invesalius/gui/task_slice.py +++ b/invesalius/gui/task_slice.py @@ -524,6 +524,7 @@ class MaskProperties(wx.Panel): self.bind_evt_gradient = False self.gradient.SetMinValue(thresh_min) self.gradient.SetMaxValue(thresh_max) + print ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", thresh_min, thresh_max self.bind_evt_gradient = True thresh = (thresh_min, thresh_max) if thresh in Project().threshold_modes.values(): diff --git a/invesalius/presets.py b/invesalius/presets.py index 7e89ea1..0a23ef0 100644 --- a/invesalius/presets.py +++ b/invesalius/presets.py @@ -73,12 +73,13 @@ class Presets(): def UpdateThresholdModes(self, evt): thresh_min, thresh_max = evt.data - presets_list = (self.thresh_ct, self.thresh_mri) for presets in presets_list: for key in presets: (t_min, t_max) = presets[key] + + if (t_min is None) or (t_max is None): # setting custom preset t_min = thresh_min t_max = thresh_max @@ -93,7 +94,7 @@ class Presets(): t_min = thresh_min if (t_max < thresh_min): t_max = thresh_max - + presets[key] = (t_min, t_max) Publisher.sendMessage('Update threshold limits', (thresh_min, diff --git a/invesalius/reader/bitmap_reader.py b/invesalius/reader/bitmap_reader.py new file mode 100644 index 0000000..207c763 --- /dev/null +++ b/invesalius/reader/bitmap_reader.py @@ -0,0 +1,389 @@ +#-------------------------------------------------------------------------- +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer +# Homepage: http://www.softwarepublico.gov.br +# Contact: invesalius@cti.gov.br +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) +#-------------------------------------------------------------------------- +# Este programa e software livre; voce pode redistribui-lo e/ou +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme +# publicada pela Free Software Foundation; de acordo com a versao 2 +# da Licenca. +# +# Este programa eh distribuido na expectativa de ser util, mas SEM +# QUALQUER GARANTIA; sem mesmo a garantia implicita de +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais +# detalhes. +#-------------------------------------------------------------------------- +import os +import Queue +import threading +import tempfile +import sys +import vtk +import re +import constants as const +import wx + +from wx.lib.pubsub import pub as Publisher +from multiprocessing import cpu_count + +from vtk.util import numpy_support +from scipy import misc +import numpy +import imghdr + +from data import converters + +#flag to control vtk error in read files +no_error = True +vtk_error = False + +class Singleton: + + def __init__(self,klass): + self.klass = klass + self.instance = None + + def __call__(self,*args,**kwds): + if self.instance == None: + self.instance = self.klass(*args,**kwds) + return self.instance + +@Singleton +class BitmapData: + + def __init__(self): + self.data = None + + def GetData(self): + return self.data + + def SetData(self, data): + self.data = data + + def GetOnlyBitmapPath(self): + paths = [item[0] for item in self.data] + return paths + + def GetFirstBitmapSize(self): + return (self.data[0][3], self.data[0][4]) + + def IsAllBitmapSameSize(self): + sizes = [item[5] for item in self.data] + + k = {} + for v in sizes: + k[v] = '' + + if len(k.keys()) > 1: + return False + else: + return True + + def GetFirstPixelSize(self): + + path = self.data[0][0] + size = ReadBitmap(path).dtype.itemsize * 8 + + return size + + + +class BitmapFiles: + + def __init__(self): + self.bitmapfiles = [] + + def Add(self, bmp): + self.bitmapfiles.append(bmp) + + def Sort(self, x): + + c_re = re.compile('\d+') + + if len(c_re.findall(x[0])) > 0: + return c_re.findall(x[0])[-1] + else: + return '0' + + def GetValues(self): + bmpfile = self.bitmapfiles + bmpfile.sort(key = self.Sort) + + bmp_data = BitmapData() + bmp_data.data = bmpfile + + return bmpfile + +class LoadBitmap: + + def __init__(self, bmp_file, filepath): + self.bmp_file = bmp_file + if sys.platform == 'win32': + self.filepath = filepath.encode(utils.get_system_encoding()) + else: + self.filepath = filepath + + self.run() + + def run(self): + global vtk_error + + #----- verify extension ------------------ + #ex = self.filepath.split('.')[-1] + extension = VerifyDataType(self.filepath) + + file_name = self.filepath.split(os.path.sep)[-1] + + #if extension == 'bmp': + # reader = vtk.vtkBMPReader() + n_array = ReadBitmap(self.filepath) + + if not(isinstance(n_array, numpy.ndarray)): + return False + + image = converters.to_vtk(n_array, spacing=(1,1,1),\ + slice_number=1, orientation="AXIAL") + + + #reader.SetFileName(self.filepath) + #reader.Update() + + dim = image.GetDimensions() + x = dim[0] + y = dim[1] + + img = vtk.vtkImageResample() + img.SetInputData(image) + img.SetAxisMagnificationFactor ( 0, 0.25 ) + img.SetAxisMagnificationFactor ( 1, 0.25 ) + img.SetAxisMagnificationFactor ( 2, 1 ) + img.Update() + + tp = img.GetOutput().GetScalarTypeAsString() + + image_copy = vtk.vtkImageData() + image_copy.DeepCopy(img.GetOutput()) + + thumbnail_path = tempfile.mktemp() + + write_png = vtk.vtkPNGWriter() + write_png.SetInputConnection(img.GetOutputPort()) + write_png.AddObserver("WarningEvent", VtkErrorPNGWriter) + write_png.SetFileName(thumbnail_path) + write_png.Write() + + if vtk_error: + img = vtk.vtkImageCast() + img.SetInputData(image_copy) + img.SetOutputScalarTypeToUnsignedShort() + #img.SetClampOverflow(1) + img.Update() + + write_png = vtk.vtkPNGWriter() + write_png.SetInputConnection(img.GetOutputPort()) + write_png.SetFileName(thumbnail_path) + write_png.Write() + + vtk_error = False + + id = wx.NewId() + + bmp_item = [self.filepath, thumbnail_path, extension, x, y,\ + str(x) + ' x ' + str(y), file_name, id] + self.bmp_file.Add(bmp_item) + + +def yGetBitmaps(directory, recursive=True, gui=True): + """ + Return all full paths to DICOM files inside given directory. + """ + nfiles = 0 + # Find total number of files + if recursive: + for dirpath, dirnames, filenames in os.walk(directory): + nfiles += len(filenames) + else: + dirpath, dirnames, filenames = os.walk(directory) + nfiles = len(filenames) + + + counter = 0 + bmp_file = BitmapFiles() + + # Retrieve only TIFF, BMP, JPEG and PNG files + if recursive: + for dirpath, dirnames, filenames in os.walk(directory): + for name in filenames: + filepath = os.path.join(dirpath, name) + counter += 1 + if gui: + yield (counter,nfiles) + #LoadDicom(grouper, filepath) + LoadBitmap(bmp_file, filepath) + else: + dirpath, dirnames, filenames = os.walk(directory) + for name in filenames: + filepath = str(os.path.join(dirpath, name)) + counter += 1 + if gui: + yield (counter,nfiles) + #q.put(filepath) + + #for t in threads: + # q.put(0) + + #for t in threads: + # t.join() + + #TODO: Is this commented update necessary? + #grouper.Update() + yield bmp_file.GetValues() + + +class ProgressBitmapReader: + def __init__(self): + Publisher.subscribe(self.CancelLoad, "Cancel bitmap load") + + def CancelLoad(self, evt_pubsub): + self.running = False + self.stoped = True + + def SetWindowEvent(self, frame): + self.frame = frame + + def SetDirectoryPath(self, path,recursive=True): + self.running = True + self.stoped = False + self.GetBitmaps(path,recursive) + + def UpdateLoadFileProgress(self,cont_progress): + Publisher.sendMessage("Update bitmap load", cont_progress) + + def EndLoadFile(self, bitmap_list): + Publisher.sendMessage("End bitmap load", bitmap_list) + + def GetBitmaps(self, path, recursive): + + #if not const.VTK_WARNING: + # log_path = os.path.join(const.LOG_FOLDER, 'vtkoutput.txt') + # fow = vtk.vtkFileOutputWindow() + # fow.SetFileName(log_path) + # ow = vtk.vtkOutputWindow() + # ow.SetInstance(fow) + + y = yGetBitmaps(path, recursive) + for value_progress in y: + if not self.running: + break + if isinstance(value_progress, tuple): + self.UpdateLoadFileProgress(value_progress) + else: + self.EndLoadFile(value_progress) + + #Is necessary in the case user cancel + #the load, ensure that dicomdialog is closed + if(self.stoped): + self.UpdateLoadFileProgress(None) + self.stoped = False + +def VtkErrorPNGWriter(obj, f): + global vtk_error + vtk_error = True + +def ScipyRead(filepath): + try: + r = misc.imread(filepath, flatten=True) + dt = r.dtype + + if dt == "float" or dt == "float16"\ + or dt == "float32" or dt == "float64": + shift=-r.max()/2 + simage = numpy.zeros_like(r, dtype='int16') + simage[:] = r.astype('int32') + shift + + return simage + else: + return r + except(IOError): + return False + +def VtkRead(filepath, t): + + global no_error + + if t == "bmp": + reader = vtk.vtkBMPReader() + + elif t == "tiff" or t == "tif": + reader = vtk.vtkTIFFReader() + + elif t == "png": + reader = vtk.vtkPNGReader() + + elif t == "jpeg" or t == "jpg": + reader = vtk.vtkJPEGReader() + + else: + return False + + reader.AddObserver("ErrorEvent", VtkErrorToPy) + reader.SetFileName(filepath) + reader.Update() + + if no_error: + image = reader.GetOutput() + dim = image.GetDimensions() + + if reader.GetNumberOfScalarComponents() > 1: + luminanceFilter = vtk.vtkImageLuminance() + luminanceFilter.SetInputData(image) + luminanceFilter.Update() + + image = vtk.vtkImageData() + image.DeepCopy(luminanceFilter.GetOutput()) + + img_array = numpy_support.vtk_to_numpy(image.GetPointData().GetScalars()) + img_array.shape = (dim[1], dim[0]) + + return img_array + else: + no_error = True + return False + + +def ReadBitmap(filepath): + + t = VerifyDataType(filepath) + + if t == False: + return False + + img_array = VtkRead(filepath, t) + + if not(isinstance(img_array, numpy.ndarray)): + + no_error = True + + img_array = ScipyRead(filepath) + + if not(isinstance(img_array, numpy.ndarray)): + return False + + return img_array + + +def VtkErrorToPy(obj, evt): + global no_error + no_error = False + + +def VerifyDataType(filepath): + try: + t = imghdr.what(filepath) + return t + except IOError: + return False + -- libgit2 0.21.2