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
app.py
... ... @@ -336,6 +336,13 @@ def parse_comand_line():
336 336 parser.add_option("-a", "--export-to-all",
337 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 346 options, args = parser.parse_args()
340 347 return options, args
341 348  
... ... @@ -429,6 +436,17 @@ def check_for_export(options, suffix='', remove_surfaces=False):
429 436 finally:
430 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 451 def export(path_, threshold_range, remove_surface=False):
434 452 import invesalius.constants as const
... ...
invesalius/constants.py
... ... @@ -506,10 +506,11 @@ VTK_WARNING = 0
506 506 #----------------------------------------------------------
507 507  
508 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 514 ID_EXIT = wx.ID_EXIT
514 515 ID_ABOUT = wx.ID_ABOUT
515 516  
... ...
invesalius/data/slice_.py
... ... @@ -161,6 +161,9 @@ class Slice(with_metaclass(utils.Singleton, object)):
161 161 Publisher.subscribe(self.__show_current_mask, 'Show current mask')
162 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 167 Publisher.subscribe(self.__set_current_mask_threshold_limits,
165 168 'Update threshold limits')
166 169  
... ... @@ -426,6 +429,23 @@ class Slice(with_metaclass(utils.Singleton, object)):
426 429 session = ses.Session()
427 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 449 def create_temp_mask(self):
430 450 temp_file = tempfile.mktemp()
431 451 shape = self.matrix.shape
... ...
invesalius/gui/frame.py
... ... @@ -53,6 +53,15 @@ import invesalius.gui.preferences as preferences
53 53 VIEW_TOOLS = [ID_LAYOUT, ID_TEXT] =\
54 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 67 class MessageWatershed(wx.PopupWindow):
... ... @@ -419,6 +428,8 @@ class Frame(wx.Frame):
419 428 self.SaveProject()
420 429 elif id == const.ID_PROJECT_SAVE_AS:
421 430 self.ShowSaveAsProject()
  431 + elif id == const.ID_EXPORT_SLICE:
  432 + self.ExportProject()
422 433 elif id == const.ID_PROJECT_CLOSE:
423 434 self.CloseProject()
424 435 elif id == const.ID_EXIT:
... ... @@ -629,6 +640,28 @@ class Frame(wx.Frame):
629 640 """
630 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 665 def ShowBitmapImporter(self):
633 666 """
634 667 Tiff, BMP, JPEG and PNG
... ... @@ -702,6 +735,7 @@ class MenuBar(wx.MenuBar):
702 735 # not. Eg. save should only be available if a project is open
703 736 self.enable_items = [const.ID_PROJECT_SAVE,
704 737 const.ID_PROJECT_SAVE_AS,
  738 + const.ID_EXPORT_SLICE,
705 739 const.ID_PROJECT_CLOSE,
706 740 const.ID_REORIENT_IMG,
707 741 const.ID_FLOODFILL_MASK,
... ... @@ -771,6 +805,7 @@ class MenuBar(wx.MenuBar):
771 805 app(const.ID_PROJECT_OPEN, _("Open project...\tCtrl+O"))
772 806 app(const.ID_PROJECT_SAVE, _("Save project\tCtrl+S"))
773 807 app(const.ID_PROJECT_SAVE_AS, _("Save project as...\tCtrl+Shift+S"))
  808 + app(const.ID_EXPORT_SLICE, _("Export project"))
774 809 app(const.ID_PROJECT_CLOSE, _("Close project"))
775 810 file_menu.AppendSeparator()
776 811 #app(const.ID_PROJECT_INFO, _("Project Information..."))
... ...
invesalius/project.py
... ... @@ -28,6 +28,7 @@ import sys
28 28 import tarfile
29 29 import tempfile
30 30  
  31 +import numpy as np
31 32 import wx
32 33 from wx.lib.pubsub import pub as Publisher
33 34 import vtk
... ... @@ -349,6 +350,70 @@ class Project(with_metaclass(Singleton, object)):
349 350 measure.Load(measurements[index])
350 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 417 def Compress(folder, filename, filelist, compress=False):
353 418 tmpdir, tmpdir_ = os.path.split(folder)
354 419 current_dir = os.path.abspath(".")
... ...