Commit 95d0842a0bcaa4281123114c97e10cc56fa4e198
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
Showing
5 changed files
with
143 additions
and
4 deletions
Show diff stats
app.py
@@ -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(".") |