def export_project_to_nifti(self, filename, save_masks=True): import invesalius.data.slice_ as slc import nibabel as nib s = slc.Slice() img_nifti = nib.Nifti1Image(np.swapaxes(np.fliplr(s.matrix), 0, 2), None) img_nifti.header.set_zooms(s.spacing) img_nifti.header.set_dim_info(slice=0) nib.save(img_nifti, filename) if save_masks: for index in self.mask_dict: mask = self.mask_dict[index] s.do_threshold_to_all_slices(mask) mask_nifti = nib.Nifti1Image(np.swapaxes(np.fliplr(mask.matrix), 0, 2), None) mask_nifti.header.set_zooms(s.spacing) if filename.lower().endswith('.nii'): basename = filename[:-4] ext = filename[-4::] elif filename.lower().endswith('.nii.gz'): basename = filename[:-7] ext = filename[-7::] else: ext = '.nii' basename = filename nib.save(mask_nifti, "{}_mask_{}_{}{}".format(basename, mask.index, mask.name, ext))
def OpenOtherFiles(self, group): # Retreaving matrix from image data self.matrix, scalar_range, self.filename = image_utils.img2memmap( group) hdr = group.header hdr.set_data_dtype('int16') dims = hdr.get_zooms() dimsf = tuple([float(s) for s in dims]) wl = float((scalar_range[0] + scalar_range[1]) * 0.5) ww = float((scalar_range[1] - scalar_range[0])) self.Slice = sl.Slice() self.Slice.matrix = self.matrix self.Slice.matrix_filename = self.filename self.Slice.spacing = dimsf self.Slice.window_level = wl self.Slice.window_width = ww scalar_range = int(scalar_range[0]), int(scalar_range[1]) Publisher.sendMessage('Update threshold limits list', scalar_range) return self.matrix, self.filename
def OnPopup(self, evt): id = evt.GetId() item = self.ID_TO_TOOL_ITEM[evt.GetId()] key = item.GetLabel() if (key in const.WINDOW_LEVEL.keys()): window, level = const.WINDOW_LEVEL[key] Publisher.sendMessage('Bright and contrast adjustment image', (window, level)) Publisher.sendMessage('Update window level value',\ (window, level)) Publisher.sendMessage('Update window and level text',\ "WL: %d WW: %d"%(level, window)) Publisher.sendMessage('Update slice viewer') #Necessary update the slice plane in the volume case exists Publisher.sendMessage('Render volume viewer') elif (key in const.SLICE_COLOR_TABLE.keys()): values = const.SLICE_COLOR_TABLE[key] Publisher.sendMessage('Change colour table from background image', values) Publisher.sendMessage('Update slice viewer') if sys.platform.startswith('linux'): for i in self.pseudo_color_items: it = self.pseudo_color_items[i] if it.IsChecked(): it.Toggle() item.Toggle() self.HideClutDialog() self._gen_event = True elif key in self.plist_presets: values = presets.get_wwwl_preset_colours(self.plist_presets[key]) Publisher.sendMessage( 'Change colour table from background image from plist', values) Publisher.sendMessage('Update slice viewer') if sys.platform.startswith('linux'): for i in self.pseudo_color_items: it = self.pseudo_color_items[i] if it.IsChecked(): it.Toggle() item.Toggle() self.HideClutDialog() self._gen_event = True elif (key in const.IMAGE_TILING.keys()): values = const.IMAGE_TILING[key] Publisher.sendMessage('Set slice viewer layout', values) Publisher.sendMessage('Update slice viewer') elif key in PROJECTIONS_ID: pid = PROJECTIONS_ID[key] Publisher.sendMessage('Set projection type', pid) Publisher.sendMessage('Reload actual slice') elif key == _('Custom'): if self.cdialog is None: slc = sl.Slice() histogram = slc.histogram init = slc.matrix.min() end = slc.matrix.max() nodes = slc.nodes self.cdialog = ClutImagedataDialog(histogram, init, end, nodes) self.cdialog.Show() else: self.cdialog.Show(self._gen_event) if sys.platform.startswith('linux'): for i in self.pseudo_color_items: it = self.pseudo_color_items[i] if it.IsChecked(): it.Toggle() item.Toggle() item = self.ID_TO_TOOL_ITEM[evt.GetId()] item.Check(True) self._gen_event = False evt.Skip()
def OpenDicomGroup(self, dicom_group, interval, file_range, gui=True): # Retrieve general DICOM headers dicom = dicom_group.GetDicomSample() # Create imagedata interval += 1 filelist = dicom_group.GetFilenameList()[::interval] if not filelist: utils.debug("Not used the IPPSorter") filelist = [ i.image.file for i in dicom_group.GetHandSortedList()[::interval] ] if file_range is not None and file_range[ 0] is not None and file_range[1] > file_range[0]: filelist = filelist[file_range[0]:file_range[1] + 1] zspacing = dicom_group.zspacing * interval size = dicom.image.size bits = dicom.image.bits_allocad sop_class_uid = dicom.acquisition.sop_class_uid xyspacing = dicom.image.spacing orientation = dicom.image.orientation_label wl = float(dicom.image.level) ww = float(dicom.image.window) if sop_class_uid == '1.2.840.10008.5.1.4.1.1.7': #Secondary Capture Image Storage use_dcmspacing = 1 else: use_dcmspacing = 0 imagedata = None if dicom.image.number_of_frames == 1: sx, sy = size n_slices = len(filelist) resolution_percentage = utils.calculate_resizing_tofitmemory( int(sx), int(sy), n_slices, bits / 8) if resolution_percentage < 1.0 and gui: re_dialog = dialog.ResizeImageDialog() re_dialog.SetValue(int(resolution_percentage * 100)) re_dialog_value = re_dialog.ShowModal() re_dialog.Close() if re_dialog_value == wx.ID_OK: percentage = re_dialog.GetValue() resolution_percentage = percentage / 100.0 else: return xyspacing = xyspacing[0] / resolution_percentage, xyspacing[ 1] / resolution_percentage self.matrix, scalar_range, self.filename = image_utils.dcm2memmap( filelist, size, orientation, resolution_percentage) if orientation == 'AXIAL': spacing = xyspacing[0], xyspacing[1], zspacing elif orientation == 'CORONAL': spacing = xyspacing[0], zspacing, xyspacing[1] elif orientation == 'SAGITTAL': spacing = zspacing, xyspacing[1], xyspacing[0] else: print(">>>>>> filelist", filelist) self.matrix, scalar_range, spacing, self.filename = image_utils.dcmmf2memmap( filelist[0], orientation) print(">>>>>> spacing", spacing) self.Slice = sl.Slice() self.Slice.matrix = self.matrix self.Slice.matrix_filename = self.filename self.Slice.spacing = spacing # 1(a): Fix gantry tilt, if any tilt_value = dicom.acquisition.tilt if (tilt_value) and (gui): # Tell user gantry tilt and fix, according to answer message = _("Fix gantry tilt applying the degrees below") value = -1 * tilt_value tilt_value = dialog.ShowNumberDialog(message, value) image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) elif (tilt_value) and not (gui): tilt_value = -1 * tilt_value image_utils.FixGantryTilt(self.matrix, self.Slice.spacing, tilt_value) self.Slice.window_level = wl self.Slice.window_width = ww scalar_range = int(self.matrix.min()), int(self.matrix.max()) Publisher.sendMessage('Update threshold limits list', threshold_range=scalar_range) return self.matrix, self.filename, dicom
def create_project_from_matrix(self, name, matrix, orientation="AXIAL", spacing=(1.0, 1.0, 1.0), modality="CT", window_width=None, window_level=None, new_instance=False): """ Creates a new project from a Numpy 3D array. name: Name of the project. matrix: A Numpy 3D array. It only works with int16 arrays. spacing: The spacing between the center of the voxels in X, Y and Z direction. modality: Imaging modality. """ if window_width is None: window_width = (matrix.max() - matrix.min()) if window_level is None: window_level = (matrix.max() + matrix.min()) // 2 window_width = int(window_width) window_level = int(window_level) name_to_const = { "AXIAL": const.AXIAL, "CORONAL": const.CORONAL, "SAGITTAL": const.SAGITAL } if new_instance: self.start_new_inv_instance(matrix, name, spacing, modality, name_to_const[orientation], window_width, window_level) else: # Verifying if there is a project open s = ses.Session() if s.IsOpen(): Publisher.sendMessage('Close Project') Publisher.sendMessage('Disconnect tracker') # Check if user really closed the project, if not, stop project creation if s.IsOpen(): return mmap_matrix = image_utils.array2memmap(matrix) self.Slice = sl.Slice() self.Slice.matrix = mmap_matrix self.Slice.matrix_filename = mmap_matrix.filename self.Slice.spacing = spacing self.Slice.window_width = window_width self.Slice.window_level = window_level proj = prj.Project() proj.name = name proj.modality = modality proj.SetAcquisitionModality(modality) proj.matrix_shape = matrix.shape proj.matrix_dtype = matrix.dtype.name proj.matrix_filename = self.Slice.matrix_filename proj.window = window_width proj.level = window_level proj.original_orientation =\ name_to_const[orientation] proj.threshold_range = int(matrix.min()), int(matrix.max()) proj.spacing = self.Slice.spacing Publisher.sendMessage('Update threshold limits list', threshold_range=proj.threshold_range) ###### session = ses.Session() filename = proj.name + ".inv3" filename = filename.replace("/", "") dirpath = session.CreateProject(filename) self.LoadProject() Publisher.sendMessage("Enable state project", state=True)
def LoadProject(self): proj = prj.Project() const.THRESHOLD_OUTVALUE = proj.threshold_range[0] const.THRESHOLD_INVALUE = proj.threshold_range[1] const.THRESHOLD_RANGE = proj.threshold_modes[_("Bone")] const.WINDOW_LEVEL[_('Default')] = (proj.window, proj.level) const.WINDOW_LEVEL[_('Manual')] = (proj.window, proj.level) self.Slice = sl.Slice() self.Slice.spacing = proj.spacing Publisher.sendMessage('Load slice to viewer', mask_dict=proj.mask_dict) Publisher.sendMessage('Load slice plane') Publisher.sendMessage('Bright and contrast adjustment image', window=proj.window, level=proj.level) Publisher.sendMessage('Update window level value', window=proj.window, level=proj.level) Publisher.sendMessage('Set project name', proj_name=proj.name) Publisher.sendMessage('Load surface dict', surface_dict=proj.surface_dict) Publisher.sendMessage('Hide surface items', surface_dict=proj.surface_dict) self.LoadImagedataInfo() # TODO: where do we insert this <<<? Publisher.sendMessage('Show content panel') Publisher.sendMessage('Update AUI') if len(proj.mask_dict): mask_index = len(proj.mask_dict) - 1 for m in proj.mask_dict.values(): Publisher.sendMessage('Add mask', mask=m) if m.is_shown: self.Slice.current_mask = proj.mask_dict[mask_index] Publisher.sendMessage('Show mask', index=m.index, value=True) Publisher.sendMessage('Change mask selected', index=m.index) else: mask_name = const.MASK_NAME_PATTERN % (1, ) if proj.modality != "UNKNOWN": thresh = const.THRESHOLD_RANGE else: thresh = proj.threshold_range colour = const.MASK_COLOUR[0] Publisher.sendMessage('Create new mask', mask_name=mask_name, thresh=thresh, colour=colour) Publisher.sendMessage('Load measurement dict', measurement_dict=proj.measurement_dict, spacing=self.Slice.spacing) Publisher.sendMessage(('Set scroll position', 'AXIAL'), index=proj.matrix_shape[0] / 2) Publisher.sendMessage(('Set scroll position', 'SAGITAL'), index=proj.matrix_shape[1] / 2) Publisher.sendMessage(('Set scroll position', 'CORONAL'), index=proj.matrix_shape[2] / 2) Publisher.sendMessage('End busy cursor')
def StartNavigation(self, tracker): tracker_fiducials, tracker_fiducials_raw = tracker.GetTrackerFiducials( ) # initialize jobs list jobs_list = [] if self.event.is_set(): self.event.clear() vis_components = [ self.serial_port_in_use, self.view_tracts, self.peel_loaded ] vis_queues = [ self.coord_queue, self.serial_port_queue, self.tracts_queue, self.icp_queue, self.robot_target_queue ] Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) self.all_fiducials = np.vstack( [self.image_fiducials, tracker_fiducials]) # fiducials matrix m_change = tr.affine_matrix_from_points(self.all_fiducials[3:, :].T, self.all_fiducials[:3, :].T, shear=False, scale=False) self.m_change = m_change errors = False if self.track_obj: # if object tracking is selected if self.obj_reg is None: # check if object registration was performed wx.MessageBox( _("Perform coil registration before navigation."), _("InVesalius 3")) errors = True else: # if object registration was correctly performed continue with navigation # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix obj_fiducials, obj_orients, obj_ref_mode, obj_name = self.obj_reg coreg_data = [m_change, obj_ref_mode] if self.ref_mode_id: coord_raw, markers_flag = tracker.TrackerCoordinates.GetCoordinates( ) else: coord_raw = np.array([None]) obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) coreg_data.extend(obj_data) queues = [ self.coord_queue, self.coord_tracts_queue, self.icp_queue, self.object_at_target_queue ] jobs_list.append( dcr.CoordinateCorregistrate(self.ref_mode_id, tracker, coreg_data, self.view_tracts, queues, self.event, self.sleep_nav, tracker.tracker_id, self.target)) else: coreg_data = (m_change, 0) queues = [ self.coord_queue, self.coord_tracts_queue, self.icp_queue ] jobs_list.append( dcr.CoordinateCorregistrateNoObject(self.ref_mode_id, tracker, coreg_data, self.view_tracts, queues, self.event, self.sleep_nav)) if not errors: #TODO: Test the serial port thread if self.serial_port_in_use: self.serial_port_connection = spc.SerialPortConnection( com_port=self.com_port, baud_rate=self.baud_rate, serial_port_queue=self.serial_port_queue, event=self.event, sleep_nav=self.sleep_nav, ) self.serial_port_connection.Connect() jobs_list.append(self.serial_port_connection) if self.view_tracts: # initialize Trekker parameters # TODO: This affine and affine_vtk are created 4 times. To improve, create a new affine object inside # Slice() that contains the transformation with the img_shift. Rename it to avoid confusion to the # affine, for instance it can be: affine_world_to_invesalius_vtk slic = sl.Slice() prj_data = prj.Project() matrix_shape = tuple(prj_data.matrix_shape) spacing = tuple(prj_data.spacing) img_shift = spacing[1] * (matrix_shape[1] - 1) affine = slic.affine.copy() affine[1, -1] -= img_shift affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) Publisher.sendMessage("Update marker offset state", create=True) self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius,\ self.n_threads, self.act_data, affine_vtk, img_shift # print("Appending the tract computation thread!") queues = [self.coord_tracts_queue, self.tracts_queue] if self.enable_act: jobs_list.append( dti.ComputeTractsACTThread(self.trk_inp, queues, self.event, self.sleep_nav)) else: jobs_list.append( dti.ComputeTractsThread(self.trk_inp, queues, self.event, self.sleep_nav)) jobs_list.append( UpdateNavigationScene( vis_queues=vis_queues, vis_components=vis_components, event=self.event, sle=self.sleep_nav, neuronavigation_api=self.neuronavigation_api, )) for jobs in jobs_list: # jobs.daemon = True jobs.start() # del jobs if self.pedal_connection is not None: self.pedal_connection.add_callback( name='navigation', callback=self.PedalStateChanged)
def apply_segment_threshold(self): threshold = self.sld_threshold.GetValue() / 100.0 if self.ps is not None: self.ps.apply_segment_threshold(threshold) slc.Slice().discard_all_buffers() Publisher.sendMessage("Reload actual slice")
def CreateSurfaceFromPolydata(self, polydata, overwrite=False, index=None, name=None, colour=None, transparency=None, volume=None, area=None, scalar=False): normals = vtk.vtkPolyDataNormals() normals.SetInputData(polydata) normals.SetFeatureAngle(80) normals.AutoOrientNormalsOn() normals.Update() mapper = vtk.vtkPolyDataMapper() mapper.SetInputData(normals.GetOutput()) if scalar: mapper.ScalarVisibilityOn() else: mapper.ScalarVisibilityOff() # mapper.ImmediateModeRenderingOn() # improve performance actor = vtk.vtkActor() actor.SetMapper(mapper) actor.GetProperty().SetBackfaceCulling(1) if self.convert2inv: # convert between invesalius and world space with shift in the Y coordinate affine = sl.Slice().affine # TODO: Check that this is needed with the new way of using affine # now the affine should be at least the identity(4) and never None if affine is not None: matrix_shape = sl.Slice().matrix.shape spacing = sl.Slice().spacing img_shift = spacing[1] * (matrix_shape[1] - 1) affine = sl.Slice().affine.copy() affine[1, -1] -= img_shift affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) actor.SetUserMatrix(affine_vtk) if overwrite: if index is None: index = self.last_surface_index surface = Surface(index=index) else: surface = Surface() if not colour: surface.colour = random.choice(const.SURFACE_COLOUR) else: surface.colour = colour surface.polydata = polydata if transparency: surface.transparency = transparency if name: surface.name = name # Append surface into Project.surface_dict proj = prj.Project() if overwrite: proj.ChangeSurface(surface) else: index = proj.AddSurface(surface) surface.index = index self.last_surface_index = index # Set actor colour and transparency actor.GetProperty().SetColor(surface.colour) actor.GetProperty().SetOpacity(1-surface.transparency) if overwrite and self.actors_dict.keys(): try: old_actor = self.actors_dict[index] Publisher.sendMessage('Remove surface actor from viewer', actor=old_actor) except KeyError: pass self.actors_dict[surface.index] = actor session = ses.Session() session.ChangeProject() # The following lines have to be here, otherwise all volumes disappear if not volume or not area: triangle_filter = vtk.vtkTriangleFilter() triangle_filter.SetInputData(polydata) triangle_filter.Update() measured_polydata = vtk.vtkMassProperties() measured_polydata.SetInputConnection(triangle_filter.GetOutputPort()) measured_polydata.Update() volume = measured_polydata.GetVolume() area = measured_polydata.GetSurfaceArea() surface.volume = volume surface.area = area print(">>>>", surface.volume) else: surface.volume = volume surface.area = area self.last_surface_index = surface.index Publisher.sendMessage('Load surface actor into viewer', actor=actor) Publisher.sendMessage('Update surface info in GUI', surface=surface) return surface.index
def segment(self, image, prob_threshold, backend, device_id, use_gpu, progress_callback=None, after_segment=None): print("backend", backend) if backend.lower() == 'plaidml': os.environ["KERAS_BACKEND"] = "plaidml.keras.backend" os.environ["PLAIDML_DEVICE_IDS"] = device_id elif backend.lower() == 'theano': os.environ["KERAS_BACKEND"] = "theano" if use_gpu: os.environ["THEANO_FLAGS"] = "device=cuda0" print("Use GPU theano", os.environ["THEANO_FLAGS"]) else: os.environ["THEANO_FLAGS"] = "device=cpu" else: raise TypeError("Wrong backend") import keras import invesalius.data.slice_ as slc image = imagedata_utils.image_normalize(image, 0.0, 1.0) # Loading model folder = pathlib.Path(__file__).parent.resolve() with open(folder.joinpath("model.json"), "r") as json_file: model = keras.models.model_from_json(json_file.read()) model.load_weights(str(folder.joinpath("model.h5"))) model.compile("Adam", "binary_crossentropy") # segmenting by patches msk = np.zeros_like(image, dtype="float32") sums = np.zeros_like(image) for completion, sub_image, patch in gen_patches(image, SIZE, OVERLAP): if self.stop: self.stop = False return if progress_callback is not None: progress_callback(completion) print("completion", completion) (iz, ez), (iy, ey), (ix, ex) = patch sub_mask = predict_patch(sub_image, patch, model, SIZE) msk[iz:ez, iy:ey, ix:ex] += sub_mask sums[iz:ez, iy:ey, ix:ex] += 1 propability_array = msk / sums mask = slc.Slice().create_new_mask() mask.was_edited = True mask.matrix[:] = 1 mask.matrix[1:, 1:, 1:] = (propability_array >= prob_threshold) * 255 self.mask = mask self.propability_array = propability_array self.segmented = True if after_segment is not None: after_segment()