def test_add_models_to_foreground_renderer(vtk_overlay_with_gradient_image): liver = [ sm.VTKSurfaceModel('tests/data/models/Liver/liver.vtk', (1.0, 1.0, 1.0)) ] tumors = [ sm.VTKSurfaceModel('tests/data/models/Liver/liver_tumours.vtk', (1.0, 1.0, 1.0)) ] image, widget, _, app = vtk_overlay_with_gradient_image #If no layer is specified, default is 0 widget.add_vtk_models(liver) foreground_actors = widget.foreground_renderer.GetActors() assert foreground_actors.GetNumberOfItems() == 1 # Explicitly specify use of foreground renderer widget.add_vtk_models(tumors, 1) foreground_actors = widget.foreground_renderer.GetActors() assert foreground_actors.GetNumberOfItems() == 2 # Check overlay renderer is empty overlay_renderer_actors = widget.generic_overlay_renderer.GetActors() assert overlay_renderer_actors.GetNumberOfItems() == 0
def __init__(self, background_file, model_file, camera_file): super(ManualRegistrationMainWidget, self).__init__() if not background_file: raise ValueError("Background image must be specified") if not model_file: raise ValueError("VTK model must be specified") if not camera_file: raise ValueError("Camera matrix must be specified") self.setContentsMargins(0, 0, 0, 0) self.viewer = ow.VTKOverlayWindow() self.viewer_size_policy = \ QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.viewer.setSizePolicy(self.viewer_size_policy) self.interactor_style = vtk.vtkInteractorStyleTrackballActor() self.viewer.SetInteractorStyle(self.interactor_style) self.vertical_layout = QtWidgets.QVBoxLayout(self) self.vertical_layout.setContentsMargins(0, 0, 0, 0) self.vertical_layout.addWidget(self.viewer) background = cv2.imread(background_file) model = sm.VTKSurfaceModel(model_file, [1.0, 1.0, 1.0], opacity=0.5) camera_matrix = np.loadtxt(camera_file) self.viewer.set_video_image(background) self.viewer.set_camera_matrix(camera_matrix) self.viewer.add_vtk_models([model])
def test_preop_from_vtkdata(): """ Voxelise from already loaded vtkdata, rather than from file name.""" input_file = 'tests/data/voxelisation/liver_downsample.stl' model = vtk_surface_model.VTKSurfaceModel(input_file, [0.0, 1.0, 0.0]) size = 0.3 grid_elements = 64 grid = voxelise.voxelise(input_mesh=model.get_vtk_source_data(), scale_input=0.001, center=True, size=size, grid_elements=grid_elements) # Check dimensions correct cell_dims = [0, 0, 0] grid.GetCellDims(cell_dims) assert cell_dims == [63, 63, 63] # Check array name is correct numpy_data = voxelise.extract_array_from_grid(grid, 'preoperativeSurface') print("Numpy data", numpy_data) # Cells 'inside' the liver have negative values, so this should be # consistent cells_in_liver = numpy_data < 0 assert np.count_nonzero(cells_in_liver) == 14628
def run_demo(input_file, output_file, transform_file): """ Takes a poly data input, transforms it by a 4x4 matrix, writes output. :param input_file: Input poly data as .vtk, .vtp, .stl or .ply file. :param output_file: Output poly data as .vtk file. :param transform_file: 4x4 matrix in plain text (e.g. numpy.savetxt) format. :return: """ if not output_file.endswith('.vtk'): raise ValueError('Currently, only .vtk outputs are supported.') model = sm.VTKSurfaceModel(input_file, colour=[1.0, 1.0, 1.0], visibility=True) matrix = np.loadtxt(transform_file) transform = mu.create_vtk_matrix_from_numpy(matrix) model.set_model_transform(transform) # this line can go, when scikit-surgeryvtk updates to 0.22.0 model.transform_filter.Update() writer = vtk.vtkPolyDataWriter() writer.SetFileName(output_file) writer.SetInputData(model.transform_filter.GetOutput()) writer.Write()
def test_add_model_to_background_renderer_raises_error( vtk_overlay_with_gradient_image): surface = [ sm.VTKSurfaceModel('tests/data/models/Liver/liver.vtk', (1.0, 1.0, 1.0)) ] image, widget, _, app = vtk_overlay_with_gradient_image with pytest.raises(ValueError): widget.add_vtk_models(surface, layer=0)
def test_surface_model_overlay(vtk_overlay_with_gradient_image): image, widget, _, app = vtk_overlay_with_gradient_image surface = [ sm.VTKSurfaceModel('tests/data/models/Liver/liver.vtk', (1.0, 1.0, 1.0)) ] widget.add_vtk_models(surface) widget.resize(512, 256) widget.show() widget.Render()
def __load_surface(self, config): if 'file' in config.keys(): file_name = config['file'] else: raise KeyError("No 'file' section defined in config") if 'opacity' in config.keys(): opacity = config['opacity'] else: raise KeyError("No 'opacity' section defined in config") if 'visibility' in config.keys(): visibility = config['visibility'] else: raise KeyError("No 'visibility' section defined in config") if 'colour' in config.keys(): colour = config['colour'] else: raise KeyError("No 'colour' section defined in config") if 'pickable' in config.keys(): pickable = config['pickable'] else: raise KeyError("No 'pickable' section defined in config") colour_as_float = [colour[0] / 255.0, colour[1] / 255.0, colour[2] / 255.0 ] tmp_name = file_name if self.directory_prefix is not None: tmp_name = os.path.join(self.directory_prefix, tmp_name) model = sm.VTKSurfaceModel(tmp_name, colour_as_float, visibility, opacity, pickable) if 'texture' in config.keys(): texture_file = config['texture'] if self.directory_prefix is not None: texture_file = os.path.join(self.directory_prefix, texture_file) model.set_texture(texture_file) if 'no shading' in config.keys(): no_shading = config['no shading'] model.set_no_shading(no_shading) return model
def load_file_of_points(file_name): """ Loads an MITK .mps file, or .txt file (rows of x y z). :param file_name: string containing path to a valid file :return: Nx3 ndarray """ extension = os.path.splitext(file_name)[1] if extension == '.mps': _, points = lmps.load_mps(file_name) elif extension == '.vtk': model = sm.VTKSurfaceModel(file_name, (1.0, 1.0, 1.0)) points = model.get_points_as_numpy() else: points = np.loadtxt(file_name) return points
def test_add_models_to_overlay_renderer(vtk_overlay_with_gradient_image): liver = [ sm.VTKSurfaceModel('tests/data/models/Liver/liver.vtk', (1.0, 1.0, 1.0)) ] tumors = [ sm.VTKSurfaceModel('tests/data/models/Liver/liver_tumours.vtk', (1.0, 1.0, 1.0)) ] image, widget, _, app = vtk_overlay_with_gradient_image widget.add_vtk_models(liver, 2) overlay_actors = widget.generic_overlay_renderer.GetActors() assert overlay_actors.GetNumberOfItems() == 1 widget.add_vtk_models(tumors, 2) overlay_actors = widget.generic_overlay_renderer.GetActors() assert overlay_actors.GetNumberOfItems() == 2 # Check foreground is empty foreground_actors = widget.foreground_renderer.GetActors() assert foreground_actors.GetNumberOfItems() == 0
def set_target_liver(self): #pylint:disable=attribute-defined-outside-init, unexpected-keyword-arg """ Set position of target liver. """ liver_model = 'tests/data/overlay/liver.vtk' self.target_model = sm.VTKSurfaceModel(liver_model, [0.5, 0.5, 0.5], pickable=False) self.target_actor = self.target_model.actor self.target_x = -300 + 600 * random.random() self.target_y = -200 + 400 * random.random() self.target_actor.SetOrigin(0, 0, 0) scale = 0.5 + random.random() * 1.0 self.target_actor.SetScale(scale, scale, scale) self.target_actor.RotateX(180*random.random()) self.target_actor.RotateY(90*random.random()) self.target_actor.SetPosition(self.target_x, self.target_y, 0)
def test_camera_projection(setup_vtk_overlay_window): vtk_overlay, factory, vtk_std_err, app = setup_vtk_overlay_window # See data: # chessboard_14_10_3_no_ID.txt - 3D chessboard coordinates # left-1095.png - image taken of chessboard # left-1095.png.points.txt - detected 2D image points # calib.intrinsic.txt - top 3x3 matrix are intrinsic parameters # left-1095.extrinsic.txt - model to camera matrix, a.k.a camera extrinsics, a.k.a pose # Load 3D points model_points_file = 'tests/data/calibration/chessboard_14_10_3_no_ID.txt' model_points = np.loadtxt(model_points_file) number_model_points = model_points.shape[0] model_polydata = [sm.VTKSurfaceModel('tests/data/calibration/chessboard_14_10_3.vtk', (1.0, 1.0, 0.0))] # Load images left_image = cv2.imread('tests/data/calibration/left-1095-undistorted.png') left_mask = cv2.imread('tests/data/calibration/left-1095-undistorted-mask.png') left_mask = cv2.cvtColor(left_mask, cv2.COLOR_RGB2GRAY) # Load 2D points image_points_file ='tests/data/calibration/left-1095-undistorted.png.points.txt' image_points = np.loadtxt(image_points_file) number_image_points = image_points.shape[0] # Load intrinsics for projection matrix. intrinsics_file = 'tests/data/calibration/calib.left.intrinsic.txt' intrinsics = np.loadtxt(intrinsics_file) # Load extrinsics for camera pose (position, orientation). extrinsics_file = 'tests/data/calibration/left-1095.extrinsic.txt' extrinsics = np.loadtxt(extrinsics_file) # OpenCV maps from chessboard to camera. # Assume chessboard == world, so the input matrix is world_to_camera. # We need camera_to_world to position the camera in world coordinates. # So, invert it. camera_to_world = np.linalg.inv(extrinsics) assert number_model_points == 140 assert number_image_points == 140 assert len(image_points) == 140 assert len(model_points) == 140 screen = app.primaryScreen() width = left_image.shape[1] height = left_image.shape[0] while width >= screen.geometry().width() or height >= screen.geometry().height(): width //= 2 height //= 2 vtk_overlay.add_vtk_models(model_polydata) vtk_overlay.set_video_image(left_image) vtk_overlay.set_camera_pose(camera_to_world) vtk_overlay.resize(width, height) vtk_overlay.show() vtk_renderer = vtk_overlay.get_foreground_renderer() vtk_camera = vtk_overlay.get_foreground_camera() vtk_renderwindow_size = vtk_overlay.GetRenderWindow().GetSize() # Wierdly, vtkRenderWindow, sometimes seems to use the wrong resolution, # like its trying to render at high resolution, maybe for anti-aliasing or averaging? scale_x = left_image.shape[1] / vtk_renderwindow_size[0] scale_y = left_image.shape[0] / vtk_renderwindow_size[1] # Print out some debug. On some displays, the widget size and the size of the vtkRenderWindow don't match. six.print_('Left image = (' + str(left_image.shape[1]) + ', ' + str(left_image.shape[0]) + ')') six.print_('Chosen size = (' + str(width) + ', ' + str(height) + ')') six.print_('Render window = ' + str(vtk_overlay.GetRenderWindow().GetSize())) six.print_('Widget = (' + str(vtk_overlay.width()) + ', ' + str(vtk_overlay.height()) + ')') six.print_('Viewport = ' + str(vtk_renderer.GetViewport())) six.print_('Scale = ' + str(scale_x) + ', ' + str(scale_y)) # Set intrisic parameters, which internally sets vtkCamera vars. vtk_overlay.set_camera_matrix(intrinsics) # Compute the rms error, using a vtkCoordinate loop, which is slow. rms = pu.compute_rms_error(model_points, image_points, vtk_renderer, scale_x, scale_y, left_image.shape[0] ) six.print_('rms using VTK =' + str(rms)) # Now check the rms error, using an OpenCV projection, which should be faster. projected_points = pu.project_points(model_points, camera_to_world, intrinsics ) counter = 0 rms_opencv = 0 for counter in range(0, number_model_points): i_c = image_points[counter] dx = projected_points[counter][0][0] - float(i_c[0]) dy = projected_points[counter][0][1] - float(i_c[1]) rms_opencv += (dx * dx + dy * dy) counter += 1 rms_opencv /= float(counter) rms_opencv = np.sqrt(rms_opencv) six.print_('rms using OpenCV =' + str(rms_opencv)) assert rms < 1.2 assert rms_opencv < 0.7 model_polydata_points = model_polydata[0].get_points_as_numpy() model_polydata_normals = model_polydata[0].get_normals_as_numpy() six.print_('model_points=' + str(model_polydata_points)) projected_facing_points = pu.project_facing_points(model_polydata_points, model_polydata_normals, camera_to_world, intrinsics ) assert projected_facing_points.shape[0] == 4 assert projected_facing_points.shape[2] == 2 # Can't think how to do this more efficiently yet. masked = [] for point_index in range(projected_facing_points.shape[0]): x = projected_facing_points[point_index][0][0] y = projected_facing_points[point_index][0][1] val = left_mask[int(y), int(x)] six.print_('p=' + str(x) + ', ' + str(y)) if int(x) >= 0 \ and int(x) < left_mask.shape[1] \ and int(y) >= 0 \ and int(y) < left_mask.shape[0] \ and val > 0: masked.append((x, y)) assert len(masked) == 2
def __init__(self, video_source, input_volume, input_surface): super().__init__(video_source) # Start by loading some data. if os.path.isdir(input_volume): reader = vtk.vtkDICOMImageReader() reader.SetDirectoryName(input_volume) elif input_volume.endswith(('.nii', '.nii.gz')): reader = vtk.vtkNIFTIImageReader() reader.SetFileName(input_volume) else: raise TypeError("Invalid input volume specified") reader.Update() self.model = sm.VTKSurfaceModel(input_surface, [0.5, 0.5, 0.5], opacity=0.5) self.vtk_overlay_window.add_vtk_models([self.model]) # Calculate the center of the volume self.x_min, self.x_max, self.y_min, self.y_max, self.z_min, self.z_max \ = reader.GetExecutive().GetWholeExtent( reader.GetOutputInformation(0)) self.num_x = self.x_max - self.x_min self.num_y = self.y_max - self.y_min self.num_z = self.z_max - self.z_min self.x_spacing, self.y_spacing, self.z_spacing = \ reader.GetOutput().GetSpacing() self.x_0, self.y_0, self.z_0 = reader.GetOutput().GetOrigin() self.center =\ [self.x_spacing * (self.x_min + 0.5 * (self.x_min + self.x_max)), self.x_spacing * (self.y_min + 0.5 * (self.y_min + self.y_max)), self.z_spacing * (self.z_min + 0.5 * (self.z_min + self.z_max))] self.reslice_center = \ [-0.5 * self.x_spacing * (self.num_x - 1), -0.5 * self.y_spacing * (self.num_y - 1), -0.5 * self.z_spacing * (self.num_z - 1)] # Setup reslice driver self.reslice = vtk.vtkImageReslice() self.reslice.SetInputConnection(reader.GetOutputPort()) # Specific values to make the skull example look nicer. self.reslice.SetOutputExtent(100, 400, 150, 400, 25, 175) self.reslice.SetOutputOrigin(self.reslice_center[0], self.reslice_center[1], 0) self.reslice.SetInterpolationModeToLinear() self.reslice_x_angle = 0 self.reslice_y_angle = 0 self.reslice_z_angle = 0 # Create a greyscale lookup table table = vtk.vtkLookupTable() table.SetRange(-1000, 1000) # image intensity range table.SetValueRange(0.1, 0.9) # from black to white table.SetSaturationRange(0.0, 0.0) # no color saturation table.SetRampToLinear() table.Build() # Map the image through the lookup table color = vtk.vtkImageMapToColors() color.SetLookupTable(table) color.SetInputConnection(self.reslice.GetOutputPort()) # Display the image self.reslice_actor = vtk.vtkImageActor() self.reslice_actor.GetMapper().SetInputConnection(color.GetOutputPort()) self.vtk_overlay_window.add_vtk_actor(self.reslice_actor) self.update_reslice()
def get_models(self, directory_name): """ Loads models from the given directory. :param directory_name: string, readable directory name. :raises: TypeError, ValueError, RuntimeError """ LOGGER.info("Loading models from %s", directory_name) # Reset files = [] self.models = [] # This may well throw FileNotFoundError which is fine. # If its not valid I want the Exception raised. files = os.listdir(directory_name) files.sort() # Loop through each file, trying to load it. counter = 0 for filename in files: full_path = os.path.join(directory_name, filename) try: model = sm.VTKSurfaceModel(full_path, (1.0, 1.0, 1.0)) model_name = os.path.splitext(model.get_name())[0] model.set_name(model_name) # New behaviour, if we provide defaults in a file, use them. if self.configuration_data: if model_name in self.configuration_data.keys(): model_defaults = self.configuration_data[model_name] if 'opacity' in model_defaults.keys(): opacity = model_defaults['opacity'] model.set_opacity(opacity) if 'visibility' in model_defaults.keys(): visibility = model_defaults['visibility'] model.set_visibility(visibility) if 'colour' in model_defaults.keys(): colour = model_defaults['colour'] colour_as_float = [ colour[0] / 255.0, colour[1] / 255.0, colour[2] / 255.0 ] model.set_colour(colour_as_float) if 'pickable' in model_defaults.keys(): pickable = model_defaults['pickable'] model.set_pickable(pickable) if 'texture' in model_defaults.keys(): texture_file = model_defaults['texture'] texture_file_path = os.path.join( directory_name, texture_file) model.set_texture(texture_file_path) if 'no shading' in model_defaults.keys(): no_shading = model_defaults['no shading'] model.set_no_shading(no_shading) else: # Original behaviour (see previous version in git) # Either load colour from file, or we just pick a # colour based on an index. if filename in self.colours: model_colour = self.colours[filename] else: LOGGER.info("Filename %s not found in colours.txt", filename) model_colour = self.colours[str(counter)] model.set_colour(model_colour) # Finally, add to list, increment counter. self.models.append(model) LOGGER.info("Loaded model from %s", full_path) counter += 1 except ValueError: # Presume wrong type of file. LOGGER.info("Didn't load vtk_surface_model: %s", full_path) if not self.models: LOGGER.info("No valid model files in given directory") LOGGER.info("Loaded models from %s", directory_name)
def setup_liver_overlay(self): """ Positio overlay liver""" liver_model = 'tests/data/overlay/liver.vtk' self.model = sm.VTKSurfaceModel(liver_model, [1.0, 0.0, 0.0]) self.vtk_overlay_window.add_vtk_models([self.model])
def setup_circle_overlay(self): """Position overlay sphere """ sphere_model = 'tests/data/overlay/sphere.vtk' self.model = sm.VTKSurfaceModel(sphere_model, [0.5, 0.5, 0.5]) self.vtk_overlay_window.add_vtk_models([self.model])