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