def test_get_tif(self): '''tif file returns correct reader''' extension='.tif' expected = type(vtk.vtkTIFFReader()) writer = vtk.vtkTIFFWriter() filename = os.path.join(self.test_dir, 'file'+extension) self.generate_image(filename, writer) self.assertEqual(type(get_vtk_reader(filename)), expected)
def test_get_nii_gz(self): '''compressed nifti file returns correct reader''' extension='.nii.gz' expected = type(vtk.vtkNIFTIImageReader()) writer = vtk.vtkNIFTIImageWriter() filename = os.path.join(self.test_dir, 'file'+extension) self.generate_image(filename, writer) self.assertEqual(type(get_vtk_reader(filename)), expected)
def test_get_aim(self): '''AIM file returns correct reader''' extension='.aim' expected = type(vtkbone.vtkboneAIMReader()) writer = vtkbone.vtkboneAIMWriter() filename = os.path.join(self.test_dir, 'file'+extension) self.generate_image(filename, writer) self.assertEqual(type(get_vtk_reader(filename)), expected)
def ImageConverter(input_filename, output_filename, processing_log='', overwrite=False): # Python 2/3 compatible input from six.moves import input # Check if output exists and should overwrite if os.path.isfile(output_filename) and not overwrite: result = input('File \"{}\" already exists. Overwrite? [y/n]: '.format( output_filename)) if result.lower() not in ['y', 'yes']: print('Not overwriting. Exiting...') os.sys.exit() # Read input if not os.path.isfile(input_filename): os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename)) reader = get_vtk_reader(input_filename) if reader is None: os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format( input_filename)) print('Reading input image ' + input_filename) reader.SetFileName(input_filename) reader.Update() # Create writer writer = get_vtk_writer(output_filename) if writer is None: os.sys.exit('[ERROR] Cannot find writer for file \"{}\"'.format( output_filename)) writer.SetInputConnection(reader.GetOutputPort()) writer.SetFileName(output_filename) # Setup processing log final_processing_log = '' if len(processing_log) > 0 and type(reader) == type( vtkbone.vtkboneAIMReader): final_processing_log = reader.GetProcessingLog( ) + os.linesep + processing_log elif type(reader) == type(vtkbone.vtkboneAIMReader): final_processing_log = reader.GetProcessingLog() elif len(processing_log) > 0: final_processing_log = processing_log # Handle edge cases for each output file type handle_filetype_writing_special_cases(writer, processing_log=final_processing_log) print('Saving image ' + output_filename) writer.Update()
def test_image_data(self): '''Correctly works on image data''' filename = os.path.join(self.test_dir, 'test25a.aim') r = get_vtk_reader(filename) r.SetFileName(filename) r.Update() array = vtkImageData_to_numpy(r.GetOutput()) npt.assert_array_almost_equal(array.shape, [25, 25, 25]) test_points = [[(0, 0, 0), 127], [(0, 0, 24), 0], [(20, 21, 23), 0]] for index, value in test_points: self.assertAlmostEqual(array[index], value)
def VisualizeSegmentation(input_filename, segmentation_filename, window, level, nThreads, opacity): # Python 2/3 compatible input from six.moves import input # Read input for filename in [input_filename, segmentation_filename]: if not os.path.isfile(filename): os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(filename)) # Set a minimum thread count nThreads = max(1, nThreads) # Max/min opacity opacity = max(0, opacity) opacity = min(1, opacity) # Read the input image_reader = get_vtk_reader(input_filename) if image_reader is None: os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format( input_filename)) print('Reading input image ' + input_filename) image_reader.SetFileName(input_filename) image_reader.Update() # Read the segmentation seg_reader = get_vtk_reader(segmentation_filename) if seg_reader is None: os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format( segmentation_filename)) print('Reading input image ' + segmentation_filename) seg_reader.SetFileName(segmentation_filename) seg_reader.Update() # Get scalar range for W/L and padding image_scalar_range = image_reader.GetOutput().GetScalarRange() # Determine if we need to autocompute the window/level if window <= 0: window = image_scalar_range[1] - image_scalar_range[0] level = (image_scalar_range[1] + image_scalar_range[0]) / 2 # Get data range seg_scalar_range = [ int(x) for x in seg_reader.GetOutput().GetScalarRange() ] if seg_scalar_range[0] < 0: os.sys.exit( "Segmentation image \"{}\" has values less than zero which cannot currently be handled. Exiting..." .format(segmentation_filename)) nLabels = seg_scalar_range[1] print("Segmented image has {} labels".format(nLabels)) # Setup LUT segLUT = vtk.vtkLookupTable() segLUT.SetRange(0, nLabels) segLUT.SetRampToLinear() segLUT.SetAlphaRange(1, 1) # Make it slightly transparent segLUT.Build() segLUT.SetTableValue(0, 0.0, 0.0, 0.0, 0.0) # Set zero to black, transparent # Setup input Mapper + Property -> Slice inputMapper = vtk.vtkImageResliceMapper() inputMapper.SetInputConnection(image_reader.GetOutputPort()) inputMapper.SliceAtFocalPointOn() inputMapper.SliceFacesCameraOn() inputMapper.BorderOn() inputMapper.SetNumberOfThreads(nThreads) inputMapper.ResampleToScreenPixelsOn() inputMapper.StreamingOn() imageProperty = vtk.vtkImageProperty() imageProperty.SetColorLevel(level) imageProperty.SetColorWindow(window) imageProperty.SetLayerNumber(1) imageProperty.SetInterpolationTypeToNearest() inputSlice = vtk.vtkImageSlice() inputSlice.SetMapper(inputMapper) inputSlice.SetProperty(imageProperty) # Setup seg Mapper + Property -> Slice segImageProperty = vtk.vtkImageProperty() segImageProperty.SetLookupTable(segLUT) segImageProperty.UseLookupTableScalarRangeOn() segImageProperty.SetInterpolationTypeToLinear() segImageProperty.SetOpacity(opacity) segImageProperty.SetLayerNumber(2) segImageProperty.SetInterpolationTypeToNearest() segMapper = vtk.vtkImageResliceMapper() segMapper.SetInputConnection(seg_reader.GetOutputPort()) segMapper.SliceAtFocalPointOn() segMapper.SliceFacesCameraOn() segMapper.BorderOn() segMapper.SetNumberOfThreads(nThreads) segMapper.ResampleToScreenPixelsOn() segMapper.StreamingOn() segSlice = vtk.vtkImageSlice() segSlice.SetProperty(segImageProperty) segSlice.SetMapper(segMapper) # Add everything to a vtkImageStack imageStack = vtk.vtkImageStack() imageStack.AddImage(inputSlice) imageStack.AddImage(segSlice) imageStack.SetActiveLayer(1) # Create Renderer -> RenderWindow -> RenderWindowInteractor -> InteractorStyle renderer = vtk.vtkRenderer() renderer.AddViewProp(imageStack) renderWindow = vtk.vtkRenderWindow() renderWindow.AddRenderer(renderer) interactor = vtk.vtkRenderWindowInteractor() interactorStyle = vtk.vtkInteractorStyleImage() interactorStyle.SetInteractionModeToImageSlicing() interactorStyle.KeyPressActivationOn() interactor.SetInteractorStyle(interactorStyle) interactor.SetRenderWindow(renderWindow) # Add some functionality to switch layers for window/level def layerSwitcher(obj, event): if str(interactor.GetKeyCode()) == 'w': # Print the w/l for the image print("Image W/L: {w}/{l}".format(w=imageProperty.GetColorWindow(), l=imageProperty.GetColorLevel())) elif str(interactor.GetKeyCode()) == 'n': # Set interpolation to nearest neighbour (good for voxel visualization) imageProperty.SetInterpolationTypeToNearest() interactor.Render() elif str(interactor.GetKeyCode()) == 'c': # Set interpolation to cubic (makes a better visualization) imageProperty.SetInterpolationTypeToCubic() interactor.Render() elif str(interactor.GetKeyCode()) == 'r': window = image_scalar_range[1] - image_scalar_range[0] level = (image_scalar_range[1] + image_scalar_range[0]) / 2 imageProperty.SetColorLevel(level) imageProperty.SetColorWindow(window) interactor.Render() # Add ability to switch between active layers interactor.AddObserver('KeyPressEvent', layerSwitcher, -1.0) # Call layerSwitcher as last observer # Initialize and go interactor.Initialize() interactor.Start()
def constructPipeline(self, _filename): self.filename = _filename if self.filename.lower().endswith(".stl"): self.reader = vtk.vtkSTLReader() self.pipelineType = "PolyData" self.pipelineDataType = "PolyData" else: self.reader = get_vtk_reader(self.filename) self.pipelineDataType = "ImageData" if self.reader is None: os.sys.exit("[ERROR] Cannot find reader for file \"{}\"".format(self.filename)) self.reader.SetFileName(self.filename) self.reader.Update() if self.pipelineDataType == "ImageData": self.setImageDataInfoLog(self.reader.GetOutput()) elif self.pipelineDataType == "PolyData": self.setPolyDataInfoLog(self.reader.GetOutput()) else: print("ERROR: Unknown data type.") exit(0) # Capture the log file if it exists if (self.reader.GetClassName() == "vtkboneAIMReader"): self.processing_log = self.reader.GetProcessingLog() self.dim = self.reader.GetOutput().GetDimensions() self.pos = self.reader.GetPosition() self.el_size_mm = self.reader.GetElementSize() self.extent = self.reader.GetOutput().GetExtent() self.origin = self.reader.GetOutput().GetOrigin() self.validForExtrusion = True else: self.processing_log = "No processing log available." # If the input file is a .stl then start a different pipeline # Set up the pipeline if self.pipelineType == "MarchingCubes": # Gaussian smoothing self.gauss.SetStandardDeviation(self.gaussStandardDeviation, self.gaussStandardDeviation, self.gaussStandardDeviation) self.gauss.SetRadiusFactors(self.gaussRadius, self.gaussRadius, self.gaussRadius) self.gauss.SetInputConnection(self.reader.GetOutputPort()) # Padding image_extent = self.reader.GetOutput().GetExtent() self.pad.SetConstant(0) self.pad.SetOutputWholeExtent(image_extent[0]-1,image_extent[1]+1, image_extent[2]-1,image_extent[3]+1, image_extent[4]-1,image_extent[5]+1) self.pad.SetInputConnection(self.gauss.GetOutputPort()) # Image Resize self.resampler.SetResizeMethodToMagnificationFactors() self.resampler.SetMagnificationFactors(self.magnification, self.magnification, self.magnification) self.resampler.BorderOn() self.resampler.SetInputConnection(self.pad.GetOutputPort()) # Marching Cubes self.marchingCubes.SetInputConnection(self.resampler.GetOutputPort()) self.marchingCubes.ComputeGradientsOn() self.marchingCubes.ComputeNormalsOn() self.marchingCubes.ComputeScalarsOff() self.marchingCubes.SetNumberOfContours(1) self.marchingCubes.SetValue(0, self.isosurface) # Transform self.transformPolyData.SetInputConnection(self.marchingCubes.GetOutputPort()) self.transformPolyData.SetTransform(self.rigidBodyTransform) # Set mapper for image data self.mapper.SetInputConnection(self.transformPolyData.GetOutputPort()) # Actor self.actor.SetMapper(self.mapper) self.actor.GetProperty().SetOpacity(self.actorOpacity) self.actor.GetProperty().SetColor(self.actorColor) self.actor.SetVisibility(self.actorVisibility) self.actor.SetPickable(self.actorPickable) elif self.pipelineType == "PolyData": # Transform self.transformPolyData.SetInputConnection(self.reader.GetOutputPort()) self.transformPolyData.SetTransform(self.rigidBodyTransform) # Set mapper for dataset self.mapperDS = vtk.vtkDataSetMapper() self.mapperDS.SetInputConnection(self.transformPolyData.GetOutputPort()) # Actor self.actor.SetMapper(self.mapperDS) self.actor.GetProperty().SetOpacity(self.actorOpacity) self.actor.GetProperty().SetColor(self.actorColor) self.actor.SetVisibility(self.actorVisibility) self.actor.SetPickable(self.actorPickable) else: print("ERROR: No appropriate pipeline!") exit(0)
def aix(infile, log, stat, histo, verbose, meta): # Python 2/3 compatible input from six.moves import input debug = False # Read input if not os.path.isfile(infile): os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename)) reader = get_vtk_reader(infile) if reader is None: os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format( input_filename)) #print('Reading input image ' + infile) reader.SetFileName(infile) reader.Update() image = reader.GetOutput() # Precompute some values guard = '!-------------------------------------------------------------------------------' phys_dim = [ x * y for x, y in zip(image.GetDimensions(), image.GetSpacing()) ] position = [ math.floor(x / y) for x, y in zip(image.GetOrigin(), image.GetSpacing()) ] size = os.path.getsize(infile) names = ['Bytes', 'KBytes', 'MBytes', 'GBytes'] n_image_voxels = image.GetDimensions()[0] * image.GetDimensions( )[1] * image.GetDimensions()[2] voxel_volume = image.GetSpacing()[0] * image.GetSpacing( )[1] * image.GetSpacing()[2] i = 0 while int(size) > 1024 and i < len(names): i += 1 size = size / 2.0**10 if (not meta): # Print header print('') print(guard) print('!>') print( '!> dim {: >6} {: >6} {: >6}'.format( *image.GetDimensions())) print('!> off x x x') print( '!> pos {: >6} {: >6} {: >6}'.format( *position)) print( '!> element size in mm {:.4f} {:.4f} {:.4f}'.format( *image.GetSpacing())) print( '!> phys dim in mm {:.4f} {:.4f} {:.4f}'.format( *phys_dim)) print('!>') print('!> Type of data {}'.format( image.GetScalarTypeAsString())) print('!> Total memory size {:.1f} {: <10}'.format( size, names[i])) print(guard) # Print log if log: if (reader.GetClassName() == "vtkboneAIMReader"): print(reader.GetProcessingLog()) else: print('!- No log available.') # Print meta data information if meta: if (reader.GetClassName() == "vtkboneAIMReader"): log = reader.GetProcessingLog() if (not log): print('!- No meta data available.') exit(1) meta_list = [] p_site = re.compile('^Site[ ]+([0-9]+)') p_patient = re.compile('^Index Patient[ ]+([0-9]+)') p_measurement = re.compile('^Index Measurement[ ]+([0-9]+)') p_name = re.compile('^Patient Name[ ]+ ([\w\-\(\' ]+)') p_strip_trailing_zeros = re.compile('[ \t]+$') for line in iter(log.splitlines()): try: m = p_site.search(line) if (m): name = 'Site' value = m.group(1) if (value == '20'): value = 'RL' if (value == '21'): value = 'RR' if (value == '38'): value = 'TL' if (value == '39'): value = 'TR' meta_list.append(value) if (debug): print('{0:25s}[{1:s}]'.format("Site", value)) m = p_patient.search(line) if (m): name = 'Index Patient' value = m.group(1) meta_list.append(value) if (debug): print('{0:25s}[{1:s}]'.format("Index Patient", value)) m = p_measurement.search(line) if (m): name = 'Index Measurement' value = m.group(1) meta_list.append(value) if (debug): print('{0:25s}[{1:s}]'.format("Index Measurement", value)) m = p_name.search(line) if (m): name = 'Patient Name' name_with_trailing_zeros = m.group(1) value = p_strip_trailing_zeros.sub( '', name_with_trailing_zeros) meta_list.append(value) if (debug): print('{0:25s}[{1:s}]'.format("Patient Name", value)) except AttributeError: print("Error: Cannot find meta data.") exit(1) for item in meta_list: print('\"{0:s}\" '.format(item), end='') print('\n', end='') #print('\"{0}\" \"{1}\" \"{2}\" \"{3}\"'.format(meta_list[0],meta_list[1],meta_list[2],meta_list[3])) # Print Stat if stat: array = vtk_to_numpy(image.GetPointData().GetScalars()).ravel() data = { '!> Max =': array.max(), '!> Min =': array.min(), '!> Mean =': array.mean(), '!> SD =': array.std(), '!> TV =': n_image_voxels * voxel_volume } max_length = 0 for measure, outcome in data.items(): max_length = max(max_length, len(measure)) formatter = '{{:<{}}} {{:>15.4f}} {{}}'.format(max_length) for measure, outcome in data.items(): if measure == '!> TV =': unit = '[mm^3]' else: unit = '[1]' print(formatter.format(measure, outcome, unit)) print(guard) # Print Histogram if histo: array = vtk_to_numpy(image.GetPointData().GetScalars()).ravel() # The range of values and number of bins are hard-coded. If fewer bins # or a different range (i.e. 0 to 127 for char) are wanted then simply # adjust these settings if (image.GetScalarTypeAsString() == "char"): nRange = [-128, 127] nBins = 128 elif (image.GetScalarTypeAsString() == "short"): nRange = [-32768, 32767] nBins = 128 else: print('!- Unknown data type: {}'.format( image.GetScalarTypeAsString())) # https://numpy.org/doc/stable/reference/generated/numpy.histogram.html hist, bin_edges = np.histogram(array, nBins, nRange, None, None, False) nValues = sum(hist) print( '!> {:4s} ({:.3s}) : Showing {:d} histogram bins over range of {:d} to {:d}.' .format('IND', 'QTY', nBins, *nRange)) for bin in range(nBins): index = nRange[0] + int(bin * (nRange[1] - nRange[0]) / (nBins - 1)) count = hist[bin] / nValues # We normalize so total count = 1 nStars = int(count * 100) if (nStars > 60): nStars = 60 # just prevents it from wrapping in the terminal print('!> {:4d} ({:.3f}): {:s}'.format(index, count, nStars * '*')) print(guard) # Print verbose if verbose: half_slice_size = image.GetDimensions()[0] * image.GetDimensions( )[1] * 0.5 array = vtk_to_numpy(image.GetPointData().GetScalars()) array = array.reshape(image.GetDimensions()).transpose(2, 1, 0) i = 1 it = np.nditer(array, flags=['multi_index']) while not it.finished: print("{} {}".format(it.multi_index, it[0])) # Print a half slice at a time if i % half_slice_size == 0: result = input('Continue printing results? (y/n)') if result not in ['y', 'yes']: print('') print('Aborting...') break print('') it.iternext() i += 1
def test_get_nonexistent_file(self): '''Nonexistent file returns None''' expected = None self.assertEqual(get_vtk_reader(''), expected)
def SliceViewer(input_filename, window, level, nThreads): # Python 2/3 compatible input from six.moves import input # Read input if not os.path.isfile(input_filename): os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename)) # Set a minimum thread count nThreads = max(1, nThreads) # Read the input reader = get_vtk_reader(input_filename) if reader is None: os.sys.exit('[ERROR] Cannot find reader for file \"{}\"'.format(input_filename)) print('Reading input image ' + input_filename) reader.SetFileName(input_filename) reader.Update() # Get scalar range for W/L and padding scalarRanges = reader.GetOutput().GetScalarRange() # Determine if we need to autocompute the window/level if window <= 0: window = scalarRanges[1] - scalarRanges[0] level = (scalarRanges[1] + scalarRanges[0])/2 # Setup input Mapper + Property -> Slice inputMapper = vtk.vtkOpenGLImageSliceMapper() inputMapper.SetInputConnection(reader.GetOutputPort()) inputMapper.SliceAtFocalPointOn() inputMapper.SliceFacesCameraOn() inputMapper.BorderOn() inputMapper.SetNumberOfThreads(nThreads) inputMapper.StreamingOn() imageProperty = vtk.vtkImageProperty() imageProperty.SetColorLevel(level) imageProperty.SetColorWindow(window) imageProperty.SetInterpolationTypeToNearest() inputSlice = vtk.vtkImageSlice() inputSlice.SetMapper(inputMapper) inputSlice.SetProperty(imageProperty) # Create Renderer -> RenderWindow -> RenderWindowInteractor -> InteractorStyle renderer = vtk.vtkRenderer() renderer.AddActor(inputSlice) renderWindow = vtk.vtkRenderWindow() renderWindow.AddRenderer(renderer) interactor = vtk.vtkRenderWindowInteractor() interactorStyle = vtk.vtkInteractorStyleImage() interactorStyle.SetInteractionModeToImageSlicing() interactorStyle.KeyPressActivationOn() interactor.SetInteractorStyle(interactorStyle) interactor.SetRenderWindow(renderWindow) # Add some functionality to switch layers for window/level def layerSwitcher(obj,event): if str(interactor.GetKeyCode()) == 'w': print("Image W/L: {w}/{l}".format(w=imageProperty.GetColorWindow(), l=imageProperty.GetColorLevel())) elif str(interactor.GetKeyCode()) == 'n': # Set interpolation to nearest neighbour (good for voxel visualization) imageProperty.SetInterpolationTypeToNearest() interactor.Render() elif str(interactor.GetKeyCode()) == 'c': # Set interpolation to cubic (makes a better visualization) imageProperty.SetInterpolationTypeToCubic() interactor.Render() elif str(interactor.GetKeyCode()) == 'r': window = scalarRanges[1] - scalarRanges[0] level = (scalarRanges[1] + scalarRanges[0])/2 imageProperty.SetColorLevel(level) imageProperty.SetColorWindow(window) interactor.Render() # Add ability to switch between active layers interactor.AddObserver('KeyPressEvent', layerSwitcher, -1.0) # Call layerSwitcher as last observer # Initialize and go interactor.Initialize() interactor.Start()