def handle_filetype_writing_special_cases(writer, **kwargs): '''Handle intermediate steps for writing filetype Calls the associated function for each writer type to try and handle the output. These may add filters before the writer such as casting. It is recommended to set the writer input data or input connection before calling this function. If no special cases are known for a writer type, nothing is done. In general, it is recommended to call this on your vtkImageWriter class just before calling update to avoid common data typing errors. Note that this function may insert new filters between the filter connected to writer and the writer itself. Args: writer (vtk.vtkImageWriter): The file writer kwargs (dict): Dictionary of args passed to subsequent functions Returns: None ''' step_map = { type(vtkbone.vtkboneAIMWriter()): handle_aim_writing_special_cases, type(vtk.vtkTIFFWriter()): handle_tiff_writing_special_cases, type(vtk.vtkPNGWriter()): handle_png_writing_special_cases, type(vtk.vtkBMPWriter()): handle_bmp_writing_special_cases, type(vtk.vtkJPEGWriter()): handle_jpeg_writing_special_cases } if type(writer) in step_map: return step_map[type(writer)](writer, **kwargs) return None
def test_write_aim_set_log(self): '''Can write aim file with processing log''' extension = '.aim' filename = os.path.join(self.test_dir, 'file' + extension) scalar_type = vtk.VTK_SHORT processing_log = 'This is a fake processing log' source = self.generate_image(scalar_type) writer = vtkbone.vtkboneAIMWriter() writer.SetInputConnection(source.GetOutputPort()) writer.SetFileName(filename) handle_filetype_writing_special_cases(writer, processing_log=processing_log) writer.Update() self.assertTrue(os.path.isfile(filename)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(filename) reader.Update() self.assertEqual(reader.GetOutput().GetScalarType(), vtk.VTK_SHORT) # A dummy log is created in this instance. Only check that our log was appended self.assertEqual(reader.GetProcessingLog().split(os.linesep)[-1], processing_log)
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 test_write_aim_unsigned_long(self): '''Can write aim file with type unsigned long''' extension = '.aim' filename = os.path.join(self.test_dir, 'file' + extension) scalar_type = vtk.VTK_UNSIGNED_LONG source = self.generate_image(scalar_type) writer = vtkbone.vtkboneAIMWriter() writer.SetInputConnection(source.GetOutputPort()) writer.SetFileName(filename) handle_filetype_writing_special_cases(writer) writer.Update() self.assertTrue(os.path.isfile(filename)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(filename) reader.Update() self.assertEqual(reader.GetOutput().GetScalarType(), vtk.VTK_FLOAT)
def aimod(input_aim, output_aim): # Python 2/3 compatible input from six.moves import input # Read file print('Reading file: {}.....'.format(input_aim)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(input_aim) reader.Update() image = reader.GetOutput() spacing = np.array(image.GetSpacing()) origin = np.array(image.GetOrigin()) dim = np.array(image.GetDimensions()) def set(x, index, value): x[index] = value mapping = [[ 'el_size_mm.x', lambda: spacing[0], lambda x: set(spacing, 0, x), float ], [ 'el_size_mm.y', lambda: spacing[1], lambda x: set(spacing, 1, x), float ], [ 'el_size_mm.z', lambda: spacing[2], lambda x: set(spacing, 2, x), float ], ['pos.x', lambda: origin[0], lambda x: set(origin, 0, x), float], [ 'pos.y', lambda: origin[1], lambda x: set(origin, 1, x), float ], ['pos.z', lambda: origin[2], lambda x: set(origin, 2, x), float], ['dim.x', lambda: dim[0], lambda x: set(dim, 0, x), int], ['dim.y', lambda: dim[1], lambda x: set(dim, 1, x), int], ['dim.z', lambda: dim[2], lambda x: set(dim, 2, x), int]] max_length = 0 for line in mapping: max_length = max(max_length, len(line[0])) formatter_float = '{{: <{}}} [{{:0.3f}}] ? '.format(max_length) formatter_int = '{{: <{}}} [{{}}] ? '.format(max_length) formatter_switch = lambda x: formatter_int if x == int else formatter_float for i in range(len(mapping)): # Grab this input line = mapping[i] formatter = formatter_switch(line[3]) # Prompt user result = input(formatter.format(line[0], line[1]())) # Enter == continue if result == '': continue # q, quit, no save # e, exit, save if len(result) > 0: if result[0] == 'q': print('Quit: not saved.') os.sys.exit() if result[0] == 'e': print('Exit.') break # Otherwise, set value line[2](line[3](result)) # Set results image.SetSpacing(spacing) image.SetOrigin(origin) image.SetDimensions(dim) # Write writer = vtkbone.vtkboneAIMWriter() writer.SetInputData(image) writer.SetFileName(output_aim) writer.SetProcessingLog(reader.GetProcessingLog()) writer.Update()
def fileConverter(inputImage, outputImage, AIMProcessingLog): print('******************************************************') print(f'CONVERTING: {inputImage} to {outputImage}') # Extract directory, filename, basename, and extensions from the output image outDirectory, outFilename = os.path.split(outputImage) outBasename, outExtension = os.path.splitext(outFilename) # Check the output file format if outExtension.lower() == '.mha': outputImageFileName = os.path.join(outDirectory, outBasename + '.mha') elif outExtension.lower() == '.aim': if not AIMProcessingLog or ('.txt' not in AIMProcessingLog.lower()): print() print( 'Error: A valid processing log (header) is needed to write out an AIM file.' ) sys.exit(1) outputImageFileName = os.path.join(outDirectory, outBasename + '.aim') else: print() print('Error: output file extension must be MHA or AIM') sys.exit(1) # Check if the input is a file or directory if os.path.isfile(inputImage): # NOT DIRECTORY: # Extract directory, filename, basename, and extensions from the input image inDirectory, inFilename = os.path.split(inputImage) inBasename, inExtension = os.path.splitext(inFilename) # Setup the correct reader based on the input image extension if '.aim' in inExtension.lower(): # If the input AIM contains a version number, remove it and rename the file if ';' in inExtension.lower(): inputImageNew = inputImage.rsplit(';', 1)[0] os.rename(inputImage, inputImageNew) inputImage = inputImageNew # Get the processing log file extension procLogDir, procLogFilename = os.path.split( AIMProcessingLog.lower()) procLogBasename, procLogExtension = os.path.splitext( procLogFilename) # Check to make sure the processing log file extension is valid # If the provided file name is not valid, create a new file based on the requested output file name if not AIMProcessingLog or (procLogExtension != '.txt'): AIMProcessingLog = os.path.join(outDirectory, outBasename + '.txt') print( 'Warning: No AIM processing log file name provided or invalid file extension provided. Using default file name: ' + AIMProcessingLog) else: print('WRITING PROCESSING LOG: ' + AIMProcessingLog) # Read in the AIM imageReader = vtkbone.vtkboneAIMReader() imageReader.SetFileName(inputImage) imageReader.DataOnCellsOff() imageReader.Update() inputHeader = imageReader.GetProcessingLog() # Write out the processing log as a txt file f = open(AIMProcessingLog, 'w') f.write(inputHeader) # Determine scalar type to use # VTK_CHAR <-> D1char # VTK_SHORT <-> D1short # If it is of type BIT, CHAR, SIGNED CHAR, or UNSIGNED CHAR it is possible # to store in a CHAR. inputScalarType = imageReader.GetOutput().GetScalarType() if (inputScalarType == vtk.VTK_BIT or inputScalarType == vtk.VTK_CHAR or inputScalarType == vtk.VTK_SIGNED_CHAR or inputScalarType == vtk.VTK_UNSIGNED_CHAR): # Make sure the image will fit in the range # It is possible that the chars are defined in such a way that either # signed or unsigned chars don't fit inside the char. We can be safe # buy checking if the image range will fit inside the VTK_CHAR scalarRange = imageReader.GetOutput().GetScalarRange() if scalarRange[0] >= vtk.VTK_SHORT_MIN and scalarRange[ 1] <= vtk.VTK_SHORT_MAX: outputScalarType = vtk.VTK_CHAR else: outputScalarType = vtk.VTK_SHORT else: outputScalarType = vtk.VTK_SHORT # Cast caster = vtk.vtkImageCast() caster.SetOutputScalarType(outputScalarType) caster.SetInputConnection(imageReader.GetOutputPort()) caster.ReleaseDataFlagOff() caster.Update() # Get VTK and SITK images vtk_image = caster.GetOutput() sitk_image = vtk2sitk(vtk_image) else: sitk_image = sitk.ReadImage(inputImage) # Check if the input is a directory elif os.path.isdir(inputImage): # DIRECTORY: print() print('Error: Please provide a valid file, not a directory!') sys.exit(1) # Setup the correct writer based on the output image extension if outExtension.lower() == '.mha': print('WRITING IMAGE: ' + str(outputImage)) sitk.WriteImage(sitk_image, str(outputImageFileName)) elif outExtension.lower() == '.aim': print('WRITING IMAGE: ' + str(outputImage)) # Handle the special case of MHA to AIM to avoid crashing due to SITK to VTK conversion # Need to cast grayscale images to VTK_SHORT type and binary images to VTK_CHAR type to display properly on the OpenVMS system # Scan the AIM log file to determine if the image is segmented or grayscale segFile = searchAIMLog(AIMProcessingLog) if segFile and inExtension.lower() == '.mha': img = vtk.vtkMetaImageReader() img.SetFileName(inputImage) img.Update() caster = vtk.vtkImageCast() caster.SetInputData(img.GetOutput()) caster.SetOutputScalarType(vtk.VTK_CHAR) caster.ReleaseDataFlagOff() caster.Update() vtk_image = caster.GetOutput() elif not segFile and inExtension.lower() == '.mha': img = vtk.vtkMetaImageReader() img.SetFileName(inputImage) img.Update() caster = vtk.vtkImageCast() caster.SetInputData(img.GetOutput()) caster.SetOutputScalarType(vtk.VTK_SHORT) caster.ReleaseDataFlagOff() caster.Update() vtk_image = caster.GetOutput() # Open the processing log for reading f = open(AIMProcessingLog, 'r') header = f.read() writer = vtkbone.vtkboneAIMWriter() writer.SetInputData(vtk_image) writer.SetFileName(str(outputImageFileName)) # Do not create a new processing log as this will add extra values that # will cause problems when processing the new AIM in IPL writer.NewProcessingLogOff() writer.SetProcessingLog(header) writer.Update() print('DONE') print('******************************************************') print()
def extrudeFromPoints(self): pts = self.pickerstyle.getPoints("in1_pipeline") if (pts.GetNumberOfPoints() < 3): qtw.QMessageBox.warning( self, "Warning", "At least 3 points must be defined on image 1 to create extrusion." ) return if (not self.in1_pipe.getIsValidForExtrusion()): qtw.QMessageBox.warning( self, "Warning", "Extrusion may not work properly when input file is not of type AIM." ) # Spline spline = vtk.vtkParametricSpline() spline.SetPoints(pts) spline.ClosedOn() parametricFunction = vtk.vtkParametricFunctionSource() parametricFunction.SetParametricFunction(spline) parametricFunction.Update() # Extrude extrusionFactor = 100.0 # mm above and below surface # A large number will cause the extrusion to fill the extent of the input image positiveExtruder = vtk.vtkLinearExtrusionFilter() positiveExtruder.SetInputConnection(parametricFunction.GetOutputPort()) positiveExtruder.SetExtrusionTypeToNormalExtrusion() positiveExtruder.SetVector(0, 0, 1) positiveExtruder.CappingOn() positiveExtruder.SetScaleFactor(extrusionFactor) posTriFilter = vtk.vtkTriangleFilter() posTriFilter.SetInputConnection(positiveExtruder.GetOutputPort()) negativeExtruder = vtk.vtkLinearExtrusionFilter() negativeExtruder.SetInputConnection(parametricFunction.GetOutputPort()) negativeExtruder.SetExtrusionTypeToNormalExtrusion() negativeExtruder.SetVector(0, 0, -1) negativeExtruder.CappingOn() negativeExtruder.SetScaleFactor(extrusionFactor) negTriFilter = vtk.vtkTriangleFilter() negTriFilter.SetInputConnection(negativeExtruder.GetOutputPort()) # Combine data combiner = vtk.vtkAppendPolyData() combiner.AddInputConnection(posTriFilter.GetOutputPort()) combiner.AddInputConnection(negTriFilter.GetOutputPort()) cleaner = vtk.vtkCleanPolyData() cleaner.SetInputConnection(combiner.GetOutputPort()) cleaner.Update() el_size_mm = self.in1_pipe.getElementSize() dim = self.in1_pipe.getDimensions() extent = self.in1_pipe.getExtent() origin = self.in1_pipe.getOrigin() foregroundValue = 127 backgroundValue = 0 # Stencil whiteImage = vtk.vtkImageData() whiteImage.SetSpacing(el_size_mm) whiteImage.SetDimensions(dim) whiteImage.SetExtent(extent) whiteImage.SetOrigin(origin) whiteImage.AllocateScalars(vtk.VTK_CHAR, 1) whiteImage.GetPointData().GetScalars().Fill(foregroundValue) # Use our extruded polydata to stencil the solid image poly2sten = vtk.vtkPolyDataToImageStencil() poly2sten.SetTolerance(0) #poly2sten.SetInputConnection(clipper.GetOutputPort()) poly2sten.SetInputConnection(cleaner.GetOutputPort()) poly2sten.SetOutputOrigin(origin) poly2sten.SetOutputSpacing(el_size_mm) poly2sten.SetOutputWholeExtent(whiteImage.GetExtent()) stencil = vtk.vtkImageStencil() stencil.SetInputData(whiteImage) stencil.SetStencilConnection(poly2sten.GetOutputPort()) #stencil.ReverseStencilOff() stencil.SetBackgroundValue(backgroundValue) stencil.Update() # Write image filename, _ = qtw.QFileDialog.getSaveFileName( self, "Select the file to save to…", qtc.QDir.homePath(), "AIM File (*.aim)") if (filename): writer = vtkbone.vtkboneAIMWriter() writer.SetInputConnection(stencil.GetOutputPort()) writer.SetFileName(filename) writer.SetProcessingLog( '!-------------------------------------------------------------------------------\n' + 'Written by blQtViewer.') writer.Update() self.statusBar().showMessage("File " + filename + " written.", 4000)
def stl2aim(input_file, output_file, transform_file, el_size_mm, visualize, overwrite, func): if os.path.isfile(output_file) and not overwrite: result = input('File \"{}\" already exists. Overwrite? [y/n]: '.format(output_file)) if result.lower() not in ['y', 'yes']: print('Not overwriting. Exiting...') os.sys.exit() model = vtk.vtkSTLReader() model.SetFileName(input_file) model.Update() model = applyTransform(transform_file, model) if (visualize): mat4x4 = visualize_actors( model.GetOutputPort(), None ) else: mat4x4 = vtk.vtkMatrix4x4() transform = vtk.vtkTransform() transform.SetMatrix(mat4x4) transformFilter = vtk.vtkTransformFilter() transformFilter.SetInputConnection( model.GetOutputPort() ) transformFilter.SetTransform( transform ) transformFilter.Update() bounds = transformFilter.GetOutput().GetBounds() dim = [1,1,1] for i in range(3): dim[i] = (math.ceil((bounds[i*2+1]-bounds[i*2]) / el_size_mm[i])) origin = [1,1,1] origin[0] = bounds[0] + el_size_mm[0] / 2 origin[1] = bounds[2] + el_size_mm[1] / 2 origin[2] = bounds[4] + el_size_mm[2] / 2 whiteImage = vtk.vtkImageData() whiteImage.SetSpacing(el_size_mm) whiteImage.SetDimensions(dim) whiteImage.SetExtent(0, dim[0] - 1, 0, dim[1] - 1, 0, dim[2] - 1) whiteImage.SetOrigin(origin) whiteImage.AllocateScalars(vtk.VTK_CHAR,1) for i in range(whiteImage.GetNumberOfPoints()): whiteImage.GetPointData().GetScalars().SetTuple1(i, 127) pol2stenc = vtk.vtkPolyDataToImageStencil() pol2stenc.SetInputData( transformFilter.GetOutput() ) pol2stenc.SetOutputOrigin( origin ) pol2stenc.SetOutputSpacing( el_size_mm ) pol2stenc.SetOutputWholeExtent( whiteImage.GetExtent() ) pol2stenc.Update() imgstenc = vtk.vtkImageStencil() imgstenc.SetInputData(whiteImage) imgstenc.SetStencilConnection( pol2stenc.GetOutputPort() ) imgstenc.ReverseStencilOff() imgstenc.SetBackgroundValue(0) imgstenc.Update() writer = vtkbone.vtkboneAIMWriter() writer.SetInputData( imgstenc.GetOutput() ) writer.SetFileName(output_file) writer.SetProcessingLog('!-------------------------------------------------------------------------------\n'+'Written by blRapidPrototype.') writer.Update() message("Writing file " + output_file)
def test_get_aim(self): '''AIM filetype returns None''' expected = type(vtkbone.vtkboneAIMWriter()) self.assertEqual(type(get_vtk_writer('test25a.AiM')), expected)