From 95d0842a0bcaa4281123114c97e10cc56fa4e198 Mon Sep 17 00:00:00 2001 From: Thiago Franco de Moraes Date: Wed, 23 Jan 2019 11:25:41 -0200 Subject: [PATCH] Export slices mask (#166) --- app.py | 18 ++++++++++++++++++ invesalius/constants.py | 9 +++++---- invesalius/data/slice_.py | 20 ++++++++++++++++++++ invesalius/gui/frame.py | 35 +++++++++++++++++++++++++++++++++++ invesalius/project.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index e18aa19..54d8b54 100644 --- a/app.py +++ b/app.py @@ -336,6 +336,13 @@ def parse_comand_line(): parser.add_option("-a", "--export-to-all", help="Export to STL for all mask presets.") + parser.add_option("--export-project", + help="Export slices and mask to HDF5 or Nifti file.") + + parser.add_option("--no-masks", action="store_false", + dest="save_masks", default=True, + help="Make InVesalius not export mask when exporting project.") + options, args = parser.parse_args() return options, args @@ -429,6 +436,17 @@ def check_for_export(options, suffix='', remove_surfaces=False): finally: exit(0) + if options.export_project: + from invesalius.project import Project + prj = Project() + export_filename = options.export_project + if suffix: + export_filename, ext = os.path.splitext(export_filename) + export_filename = u'{}-{}{}'.format(export_filename, suffix, ext) + + prj.export_project(export_filename, save_masks=options.save_masks) + print("Saved {}".format(export_filename)) + def export(path_, threshold_range, remove_surface=False): import invesalius.constants as const diff --git a/invesalius/constants.py b/invesalius/constants.py index 085545c..32a5a25 100644 --- a/invesalius/constants.py +++ b/invesalius/constants.py @@ -506,10 +506,11 @@ 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_PREFERENCES, ID_DICOM_NETWORK, -ID_TIFF_JPG_PNG, ID_VIEW_INTERPOLATED, ID_MODE_NAVIGATION, ID_ANALYZE_IMPORT, -ID_NIFTI_IMPORT, ID_PARREC_IMPORT, ID_MODE_DBS] = [wx.NewId() for number in range(19)] + ID_PROJECT_CLOSE, ID_EXPORT_SLICE, ID_EXPORT_MASK, ID_PROJECT_INFO, + ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET, ID_PRINT_SCREENSHOT, + ID_IMPORT_OTHERS_FILES, ID_PREFERENCES, ID_DICOM_NETWORK, ID_TIFF_JPG_PNG, + ID_VIEW_INTERPOLATED, ID_MODE_NAVIGATION, ID_ANALYZE_IMPORT, ID_NIFTI_IMPORT, + ID_PARREC_IMPORT, ID_MODE_DBS] = [wx.NewId() for number in range(21)] ID_EXIT = wx.ID_EXIT ID_ABOUT = wx.ID_ABOUT diff --git a/invesalius/data/slice_.py b/invesalius/data/slice_.py index af3978b..35bd1eb 100644 --- a/invesalius/data/slice_.py +++ b/invesalius/data/slice_.py @@ -161,6 +161,9 @@ class Slice(with_metaclass(utils.Singleton, object)): Publisher.subscribe(self.__show_current_mask, 'Show current mask') Publisher.subscribe(self.__clean_current_mask, 'Clean current mask') + Publisher.subscribe(self.__export_slice, 'Export slice') + Publisher.subscribe(self.__export_actual_mask, 'Export actual mask') + Publisher.subscribe(self.__set_current_mask_threshold_limits, 'Update threshold limits') @@ -426,6 +429,23 @@ class Slice(with_metaclass(utils.Singleton, object)): session = ses.Session() session.ChangeProject() + def __export_slice(self, filename): + import h5py + f = h5py.File(filename, 'w') + f['data'] = self.matrix + f['spacing'] = self.spacing + f.flush() + f.close() + + def __export_actual_mask(self, filename): + import h5py + f = h5py.File(filename, 'w') + self.do_threshold_to_all_slices() + f['data'] = self.current_mask.matrix[1:, 1:, 1:] + f['spacing'] = self.spacing + f.flush() + f.close() + def create_temp_mask(self): temp_file = tempfile.mktemp() shape = self.matrix.shape diff --git a/invesalius/gui/frame.py b/invesalius/gui/frame.py index d6047c3..fb9924f 100644 --- a/invesalius/gui/frame.py +++ b/invesalius/gui/frame.py @@ -53,6 +53,15 @@ import invesalius.gui.preferences as preferences VIEW_TOOLS = [ID_LAYOUT, ID_TEXT] =\ [wx.NewId() for number in range(2)] +WILDCARD_EXPORT_SLICE = "HDF5 (*.hdf5)|*.hdf5|" \ + "NIfTI 1 (*.nii)|*.nii|" \ + "Compressed NIfTI (*.nii.gz)|*.nii.gz" + +IDX_EXT = { + 0: '.hdf5', + 1: '.nii', + 2: '.nii.gz' +} class MessageWatershed(wx.PopupWindow): @@ -419,6 +428,8 @@ class Frame(wx.Frame): self.SaveProject() elif id == const.ID_PROJECT_SAVE_AS: self.ShowSaveAsProject() + elif id == const.ID_EXPORT_SLICE: + self.ExportProject() elif id == const.ID_PROJECT_CLOSE: self.CloseProject() elif id == const.ID_EXIT: @@ -629,6 +640,28 @@ class Frame(wx.Frame): """ Publisher.sendMessage('Show save dialog', save_as=True) + def ExportProject(self): + """ + Show save dialog to export slice. + """ + p = prj.Project() + + session = ses.Session() + last_directory = session.get('paths', 'last_directory_export_prj', '') + dlg = wx.FileDialog(None, + "Export slice ...", + last_directory, # last used directory + os.path.split(p.name)[-1], # initial filename + WILDCARD_EXPORT_SLICE, + wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + ext = IDX_EXT[dlg.GetFilterIndex()] + if not filename.endswith(ext): + filename += ext + p.export_project(filename) + session['paths']['last_directory_export_prj'] = os.path.split(filename)[0] + def ShowBitmapImporter(self): """ Tiff, BMP, JPEG and PNG @@ -702,6 +735,7 @@ class MenuBar(wx.MenuBar): # not. Eg. save should only be available if a project is open self.enable_items = [const.ID_PROJECT_SAVE, const.ID_PROJECT_SAVE_AS, + const.ID_EXPORT_SLICE, const.ID_PROJECT_CLOSE, const.ID_REORIENT_IMG, const.ID_FLOODFILL_MASK, @@ -771,6 +805,7 @@ class MenuBar(wx.MenuBar): app(const.ID_PROJECT_OPEN, _("Open project...\tCtrl+O")) app(const.ID_PROJECT_SAVE, _("Save project\tCtrl+S")) app(const.ID_PROJECT_SAVE_AS, _("Save project as...\tCtrl+Shift+S")) + app(const.ID_EXPORT_SLICE, _("Export project")) app(const.ID_PROJECT_CLOSE, _("Close project")) file_menu.AppendSeparator() #app(const.ID_PROJECT_INFO, _("Project Information...")) diff --git a/invesalius/project.py b/invesalius/project.py index ad9879c..c50fe0d 100644 --- a/invesalius/project.py +++ b/invesalius/project.py @@ -28,6 +28,7 @@ import sys import tarfile import tempfile +import numpy as np import wx from wx.lib.pubsub import pub as Publisher import vtk @@ -349,6 +350,70 @@ class Project(with_metaclass(Singleton, object)): measure.Load(measurements[index]) self.measurement_dict[int(index)] = measure + def export_project(self, filename, save_masks=True): + if filename.lower().endswith('.hdf5') or filename.lower().endswith('.h5'): + self.export_project_to_hdf5(filename, save_masks) + elif filename.lower().endswith('.nii') or filename.lower().endswith('.nii.gz'): + self.export_project_to_nifti(filename, save_masks) + + def export_project_to_hdf5(self, filename, save_masks=True): + import h5py + import invesalius.data.slice_ as slc + s = slc.Slice() + with h5py.File(filename, 'w') as f: + f['image'] = s.matrix + f['spacing'] = s.spacing + + f["invesalius_version"] = const.INVESALIUS_VERSION + f["date"] = datetime.datetime.now().isoformat() + f["compress"] = self.compress + f["name"] = self.name # patient's name + f["modality"] = self.modality # CT, RMI, ... + f["orientation"] = self.original_orientation + f["window_width"] = self.window + f["window_level"] = self.level + f["scalar_range"] = self.threshold_range + + if save_masks: + for index in self.mask_dict: + mask = self.mask_dict[index] + s.do_threshold_to_all_slices(mask) + key = 'masks/{}'.format(index) + f[key + '/name'] = mask.name + f[key + '/matrix'] = mask.matrix[1:, 1:, 1:] + f[key + '/colour'] = mask.colour[:3] + f[key + '/opacity'] = mask.opacity + f[key + '/threshold_range'] = mask.threshold_range + f[key + '/edition_threshold_range'] = mask.edition_threshold_range + f[key + '/visible'] = mask.is_shown + f[key + '/edited'] = mask.was_edited + + def export_project_to_nifti(self, filename, save_masks=True): + import invesalius.data.slice_ as slc + import nibabel as nib + s = slc.Slice() + img_nifti = nib.Nifti1Image(np.swapaxes(np.fliplr(s.matrix), 0, 2), None) + img_nifti.header.set_zooms(s.spacing) + img_nifti.header.set_dim_info(slice=0) + nib.save(img_nifti, filename) + if save_masks: + for index in self.mask_dict: + mask = self.mask_dict[index] + s.do_threshold_to_all_slices(mask) + mask_nifti = nib.Nifti1Image(np.swapaxes(np.fliplr(mask.matrix), 0, 2), None) + mask_nifti.header.set_zooms(s.spacing) + if filename.lower().endswith('.nii'): + basename = filename[:-4] + ext = filename[-4::] + elif filename.lower().endswith('.nii.gz'): + basename = filename[:-7] + ext = filename[-7::] + else: + ext = '.nii' + basename = filename + nib.save(mask_nifti, "{}_mask_{}_{}{}".format(basename, mask.index, mask.name, ext)) + + def Compress(folder, filename, filelist, compress=False): tmpdir, tmpdir_ = os.path.split(folder) current_dir = os.path.abspath(".") -- libgit2 0.21.2