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