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(".") |