Commit 95d0842a0bcaa4281123114c97e10cc56fa4e198

Authored by Thiago Franco de Moraes
Committed by GitHub
1 parent e0a64d3e
Exists in master

Export slices mask (#166)

* Export hdf5

* Exporting mask to hdf5

* Exporting all project to hdf5

* better hierachy of tags

* Only exporting project

* Option to export to hdf5 via command line

* suffix

* Added an option to not export masks

* exporting to nifti

* Exporting to nifti

* Doing swap axes to export nii

* Better extension handling

* Better extension handling

* fliping lr when exporting to nii
@@ -336,6 +336,13 @@ def parse_comand_line(): @@ -336,6 +336,13 @@ def parse_comand_line():
336 parser.add_option("-a", "--export-to-all", 336 parser.add_option("-a", "--export-to-all",
337 help="Export to STL for all mask presets.") 337 help="Export to STL for all mask presets.")
338 338
  339 + parser.add_option("--export-project",
  340 + help="Export slices and mask to HDF5 or Nifti file.")
  341 +
  342 + parser.add_option("--no-masks", action="store_false",
  343 + dest="save_masks", default=True,
  344 + help="Make InVesalius not export mask when exporting project.")
  345 +
339 options, args = parser.parse_args() 346 options, args = parser.parse_args()
340 return options, args 347 return options, args
341 348
@@ -429,6 +436,17 @@ def check_for_export(options, suffix='', remove_surfaces=False): @@ -429,6 +436,17 @@ def check_for_export(options, suffix='', remove_surfaces=False):
429 finally: 436 finally:
430 exit(0) 437 exit(0)
431 438
  439 + if options.export_project:
  440 + from invesalius.project import Project
  441 + prj = Project()
  442 + export_filename = options.export_project
  443 + if suffix:
  444 + export_filename, ext = os.path.splitext(export_filename)
  445 + export_filename = u'{}-{}{}'.format(export_filename, suffix, ext)
  446 +
  447 + prj.export_project(export_filename, save_masks=options.save_masks)
  448 + print("Saved {}".format(export_filename))
  449 +
432 450
433 def export(path_, threshold_range, remove_surface=False): 451 def export(path_, threshold_range, remove_surface=False):
434 import invesalius.constants as const 452 import invesalius.constants as const
invesalius/constants.py
@@ -506,10 +506,11 @@ VTK_WARNING = 0 @@ -506,10 +506,11 @@ VTK_WARNING = 0
506 #---------------------------------------------------------- 506 #----------------------------------------------------------
507 507
508 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE, 508 [ID_DICOM_IMPORT, ID_PROJECT_OPEN, ID_PROJECT_SAVE_AS, ID_PROJECT_SAVE,
509 -ID_PROJECT_CLOSE, ID_PROJECT_INFO, ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET,  
510 -ID_PRINT_SCREENSHOT, ID_IMPORT_OTHERS_FILES, ID_PREFERENCES, ID_DICOM_NETWORK,  
511 -ID_TIFF_JPG_PNG, ID_VIEW_INTERPOLATED, ID_MODE_NAVIGATION, ID_ANALYZE_IMPORT,  
512 -ID_NIFTI_IMPORT, ID_PARREC_IMPORT, ID_MODE_DBS] = [wx.NewId() for number in range(19)] 509 + ID_PROJECT_CLOSE, ID_EXPORT_SLICE, ID_EXPORT_MASK, ID_PROJECT_INFO,
  510 + ID_SAVE_SCREENSHOT, ID_DICOM_LOAD_NET, ID_PRINT_SCREENSHOT,
  511 + ID_IMPORT_OTHERS_FILES, ID_PREFERENCES, ID_DICOM_NETWORK, ID_TIFF_JPG_PNG,
  512 + ID_VIEW_INTERPOLATED, ID_MODE_NAVIGATION, ID_ANALYZE_IMPORT, ID_NIFTI_IMPORT,
  513 + ID_PARREC_IMPORT, ID_MODE_DBS] = [wx.NewId() for number in range(21)]
513 ID_EXIT = wx.ID_EXIT 514 ID_EXIT = wx.ID_EXIT
514 ID_ABOUT = wx.ID_ABOUT 515 ID_ABOUT = wx.ID_ABOUT
515 516
invesalius/data/slice_.py
@@ -161,6 +161,9 @@ class Slice(with_metaclass(utils.Singleton, object)): @@ -161,6 +161,9 @@ class Slice(with_metaclass(utils.Singleton, object)):
161 Publisher.subscribe(self.__show_current_mask, 'Show current mask') 161 Publisher.subscribe(self.__show_current_mask, 'Show current mask')
162 Publisher.subscribe(self.__clean_current_mask, 'Clean current mask') 162 Publisher.subscribe(self.__clean_current_mask, 'Clean current mask')
163 163
  164 + Publisher.subscribe(self.__export_slice, 'Export slice')
  165 + Publisher.subscribe(self.__export_actual_mask, 'Export actual mask')
  166 +
164 Publisher.subscribe(self.__set_current_mask_threshold_limits, 167 Publisher.subscribe(self.__set_current_mask_threshold_limits,
165 'Update threshold limits') 168 'Update threshold limits')
166 169
@@ -426,6 +429,23 @@ class Slice(with_metaclass(utils.Singleton, object)): @@ -426,6 +429,23 @@ class Slice(with_metaclass(utils.Singleton, object)):
426 session = ses.Session() 429 session = ses.Session()
427 session.ChangeProject() 430 session.ChangeProject()
428 431
  432 + def __export_slice(self, filename):
  433 + import h5py
  434 + f = h5py.File(filename, 'w')
  435 + f['data'] = self.matrix
  436 + f['spacing'] = self.spacing
  437 + f.flush()
  438 + f.close()
  439 +
  440 + def __export_actual_mask(self, filename):
  441 + import h5py
  442 + f = h5py.File(filename, 'w')
  443 + self.do_threshold_to_all_slices()
  444 + f['data'] = self.current_mask.matrix[1:, 1:, 1:]
  445 + f['spacing'] = self.spacing
  446 + f.flush()
  447 + f.close()
  448 +
429 def create_temp_mask(self): 449 def create_temp_mask(self):
430 temp_file = tempfile.mktemp() 450 temp_file = tempfile.mktemp()
431 shape = self.matrix.shape 451 shape = self.matrix.shape
invesalius/gui/frame.py
@@ -53,6 +53,15 @@ import invesalius.gui.preferences as preferences @@ -53,6 +53,15 @@ import invesalius.gui.preferences as preferences
53 VIEW_TOOLS = [ID_LAYOUT, ID_TEXT] =\ 53 VIEW_TOOLS = [ID_LAYOUT, ID_TEXT] =\
54 [wx.NewId() for number in range(2)] 54 [wx.NewId() for number in range(2)]
55 55
  56 +WILDCARD_EXPORT_SLICE = "HDF5 (*.hdf5)|*.hdf5|" \
  57 + "NIfTI 1 (*.nii)|*.nii|" \
  58 + "Compressed NIfTI (*.nii.gz)|*.nii.gz"
  59 +
  60 +IDX_EXT = {
  61 + 0: '.hdf5',
  62 + 1: '.nii',
  63 + 2: '.nii.gz'
  64 +}
56 65
57 66
58 class MessageWatershed(wx.PopupWindow): 67 class MessageWatershed(wx.PopupWindow):
@@ -419,6 +428,8 @@ class Frame(wx.Frame): @@ -419,6 +428,8 @@ class Frame(wx.Frame):
419 self.SaveProject() 428 self.SaveProject()
420 elif id == const.ID_PROJECT_SAVE_AS: 429 elif id == const.ID_PROJECT_SAVE_AS:
421 self.ShowSaveAsProject() 430 self.ShowSaveAsProject()
  431 + elif id == const.ID_EXPORT_SLICE:
  432 + self.ExportProject()
422 elif id == const.ID_PROJECT_CLOSE: 433 elif id == const.ID_PROJECT_CLOSE:
423 self.CloseProject() 434 self.CloseProject()
424 elif id == const.ID_EXIT: 435 elif id == const.ID_EXIT:
@@ -629,6 +640,28 @@ class Frame(wx.Frame): @@ -629,6 +640,28 @@ class Frame(wx.Frame):
629 """ 640 """
630 Publisher.sendMessage('Show save dialog', save_as=True) 641 Publisher.sendMessage('Show save dialog', save_as=True)
631 642
  643 + def ExportProject(self):
  644 + """
  645 + Show save dialog to export slice.
  646 + """
  647 + p = prj.Project()
  648 +
  649 + session = ses.Session()
  650 + last_directory = session.get('paths', 'last_directory_export_prj', '')
  651 + dlg = wx.FileDialog(None,
  652 + "Export slice ...",
  653 + last_directory, # last used directory
  654 + os.path.split(p.name)[-1], # initial filename
  655 + WILDCARD_EXPORT_SLICE,
  656 + wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
  657 + if dlg.ShowModal() == wx.ID_OK:
  658 + filename = dlg.GetPath()
  659 + ext = IDX_EXT[dlg.GetFilterIndex()]
  660 + if not filename.endswith(ext):
  661 + filename += ext
  662 + p.export_project(filename)
  663 + session['paths']['last_directory_export_prj'] = os.path.split(filename)[0]
  664 +
632 def ShowBitmapImporter(self): 665 def ShowBitmapImporter(self):
633 """ 666 """
634 Tiff, BMP, JPEG and PNG 667 Tiff, BMP, JPEG and PNG
@@ -702,6 +735,7 @@ class MenuBar(wx.MenuBar): @@ -702,6 +735,7 @@ class MenuBar(wx.MenuBar):
702 # not. Eg. save should only be available if a project is open 735 # not. Eg. save should only be available if a project is open
703 self.enable_items = [const.ID_PROJECT_SAVE, 736 self.enable_items = [const.ID_PROJECT_SAVE,
704 const.ID_PROJECT_SAVE_AS, 737 const.ID_PROJECT_SAVE_AS,
  738 + const.ID_EXPORT_SLICE,
705 const.ID_PROJECT_CLOSE, 739 const.ID_PROJECT_CLOSE,
706 const.ID_REORIENT_IMG, 740 const.ID_REORIENT_IMG,
707 const.ID_FLOODFILL_MASK, 741 const.ID_FLOODFILL_MASK,
@@ -771,6 +805,7 @@ class MenuBar(wx.MenuBar): @@ -771,6 +805,7 @@ class MenuBar(wx.MenuBar):
771 app(const.ID_PROJECT_OPEN, _("Open project...\tCtrl+O")) 805 app(const.ID_PROJECT_OPEN, _("Open project...\tCtrl+O"))
772 app(const.ID_PROJECT_SAVE, _("Save project\tCtrl+S")) 806 app(const.ID_PROJECT_SAVE, _("Save project\tCtrl+S"))
773 app(const.ID_PROJECT_SAVE_AS, _("Save project as...\tCtrl+Shift+S")) 807 app(const.ID_PROJECT_SAVE_AS, _("Save project as...\tCtrl+Shift+S"))
  808 + app(const.ID_EXPORT_SLICE, _("Export project"))
774 app(const.ID_PROJECT_CLOSE, _("Close project")) 809 app(const.ID_PROJECT_CLOSE, _("Close project"))
775 file_menu.AppendSeparator() 810 file_menu.AppendSeparator()
776 #app(const.ID_PROJECT_INFO, _("Project Information...")) 811 #app(const.ID_PROJECT_INFO, _("Project Information..."))
invesalius/project.py
@@ -28,6 +28,7 @@ import sys @@ -28,6 +28,7 @@ import sys
28 import tarfile 28 import tarfile
29 import tempfile 29 import tempfile
30 30
  31 +import numpy as np
31 import wx 32 import wx
32 from wx.lib.pubsub import pub as Publisher 33 from wx.lib.pubsub import pub as Publisher
33 import vtk 34 import vtk
@@ -349,6 +350,70 @@ class Project(with_metaclass(Singleton, object)): @@ -349,6 +350,70 @@ class Project(with_metaclass(Singleton, object)):
349 measure.Load(measurements[index]) 350 measure.Load(measurements[index])
350 self.measurement_dict[int(index)] = measure 351 self.measurement_dict[int(index)] = measure
351 352
  353 + def export_project(self, filename, save_masks=True):
  354 + if filename.lower().endswith('.hdf5') or filename.lower().endswith('.h5'):
  355 + self.export_project_to_hdf5(filename, save_masks)
  356 + elif filename.lower().endswith('.nii') or filename.lower().endswith('.nii.gz'):
  357 + self.export_project_to_nifti(filename, save_masks)
  358 +
  359 + def export_project_to_hdf5(self, filename, save_masks=True):
  360 + import h5py
  361 + import invesalius.data.slice_ as slc
  362 + s = slc.Slice()
  363 + with h5py.File(filename, 'w') as f:
  364 + f['image'] = s.matrix
  365 + f['spacing'] = s.spacing
  366 +
  367 + f["invesalius_version"] = const.INVESALIUS_VERSION
  368 + f["date"] = datetime.datetime.now().isoformat()
  369 + f["compress"] = self.compress
  370 + f["name"] = self.name # patient's name
  371 + f["modality"] = self.modality # CT, RMI, ...
  372 + f["orientation"] = self.original_orientation
  373 + f["window_width"] = self.window
  374 + f["window_level"] = self.level
  375 + f["scalar_range"] = self.threshold_range
  376 +
  377 + if save_masks:
  378 + for index in self.mask_dict:
  379 + mask = self.mask_dict[index]
  380 + s.do_threshold_to_all_slices(mask)
  381 + key = 'masks/{}'.format(index)
  382 + f[key + '/name'] = mask.name
  383 + f[key + '/matrix'] = mask.matrix[1:, 1:, 1:]
  384 + f[key + '/colour'] = mask.colour[:3]
  385 + f[key + '/opacity'] = mask.opacity
  386 + f[key + '/threshold_range'] = mask.threshold_range
  387 + f[key + '/edition_threshold_range'] = mask.edition_threshold_range
  388 + f[key + '/visible'] = mask.is_shown
  389 + f[key + '/edited'] = mask.was_edited
  390 +
  391 + def export_project_to_nifti(self, filename, save_masks=True):
  392 + import invesalius.data.slice_ as slc
  393 + import nibabel as nib
  394 + s = slc.Slice()
  395 + img_nifti = nib.Nifti1Image(np.swapaxes(np.fliplr(s.matrix), 0, 2), None)
  396 + img_nifti.header.set_zooms(s.spacing)
  397 + img_nifti.header.set_dim_info(slice=0)
  398 + nib.save(img_nifti, filename)
  399 + if save_masks:
  400 + for index in self.mask_dict:
  401 + mask = self.mask_dict[index]
  402 + s.do_threshold_to_all_slices(mask)
  403 + mask_nifti = nib.Nifti1Image(np.swapaxes(np.fliplr(mask.matrix), 0, 2), None)
  404 + mask_nifti.header.set_zooms(s.spacing)
  405 + if filename.lower().endswith('.nii'):
  406 + basename = filename[:-4]
  407 + ext = filename[-4::]
  408 + elif filename.lower().endswith('.nii.gz'):
  409 + basename = filename[:-7]
  410 + ext = filename[-7::]
  411 + else:
  412 + ext = '.nii'
  413 + basename = filename
  414 + nib.save(mask_nifti, "{}_mask_{}_{}{}".format(basename, mask.index, mask.name, ext))
  415 +
  416 +
352 def Compress(folder, filename, filelist, compress=False): 417 def Compress(folder, filename, filelist, compress=False):
353 tmpdir, tmpdir_ = os.path.split(folder) 418 tmpdir, tmpdir_ = os.path.split(folder)
354 current_dir = os.path.abspath(".") 419 current_dir = os.path.abspath(".")