def main(): SOFTWARE = 'Buie-Burghardt-Scanco Autocontour VTK Implemention' VERSION = 0.2 args = create_parser().parse_args() if args.out_value >= args.in_value: raise ValueError('please make `in-value` larger than `out-value`') aim_fn_list = glob(os.path.join(args.aim_dir, args.aim_pattern)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() for aim_fn in aim_fn_list: print(aim_fn) cort_mask_fn = aim_fn.replace('.AIM', '_CORT_MASK.AIM') trab_mask_fn = aim_fn.replace('.AIM', '_TRAB_MASK.AIM') reader.SetFileName(aim_fn) reader.Update() img = reader.GetOutput() m, b = get_aim_density_equation(reader.GetProcessingLog()) img = convert_aim_to_density(img, m, b) cort_mask, trab_mask = autocontour_buie(img, args) write_mask(reader, cort_mask, cort_mask_fn, 'CORT_MASK', SOFTWARE, VERSION) write_mask(reader, trab_mask, trab_mask_fn, 'TRAB_MASK', SOFTWARE, VERSION)
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 get_vtk_reader(filename): '''Get the appropriate vtkImageReader given the filename This function utilizes the factory method classes in VTK with some added functionality for working with the AIM, nifti, and dicom readers. Args: filename (string): Image to be read in Returns: vtkImageReader: The corresponding vtkImageReader or None if one cannot be found. ''' # Try factory method reader = vtk.vtkImageReader2Factory.CreateImageReader2(filename) # If it doesn't work, try specific cases if reader is None: if filename.lower().endswith('.aim'): reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() elif filename.lower().endswith('.nii'): reader = vtk.vtkNIFTIImageReader() elif filename.lower().endswith('.nii.gz'): reader = vtk.vtkNIFTIImageReader() elif filename.lower().endswith('.dcm'): reader = vtk.vtkDICOMImageReader() return reader
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_spacing(self): '''Can run `aimod` changing spacing''' stdin = os.linesep.join(['1', '2', '3']) + os.linesep * 7 input_file = os.path.join(self.test_dir, 'test25a.aim') output_file = os.path.join(self.test_dir, 'test.aim') os.sys.stdin = StringIO(stdin) aimod(input_file, output_file) self.assertTrue(os.path.isfile(output_file)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(output_file) reader.Update() image = reader.GetOutput() self.assertAlmostEqual(image.GetSpacing()[0], 1.0) self.assertAlmostEqual(image.GetSpacing()[1], 2.0) self.assertAlmostEqual(image.GetSpacing()[2], 3.0)
def test_position(self): '''Can run `aimod` changing position''' stdin = os.linesep * 3 + os.linesep.join(['10.0', '11.2', '13.5' ]) + os.linesep * 4 input_file = os.path.join(self.test_dir, 'test25a.aim') output_file = os.path.join(self.test_dir, 'test.aim') os.sys.stdin = StringIO(stdin) aimod(input_file, output_file) self.assertTrue(os.path.isfile(output_file)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(output_file) reader.Update() image = reader.GetOutput() self.assertAlmostEqual(image.GetOrigin()[0], 10.0, places=1) self.assertAlmostEqual(image.GetOrigin()[1], 11.2, places=1) self.assertAlmostEqual(image.GetOrigin()[2], 13.5, places=1)
def test_dimension(self): '''Can run `aimod` changing dimension''' stdin = os.linesep * 6 + os.linesep.join(['2', '3', '4' ]) + os.linesep * 1 input_file = os.path.join(self.test_dir, 'test25a.aim') output_file = os.path.join(self.test_dir, 'test.aim') os.sys.stdin = StringIO(stdin) aimod(input_file, output_file) self.assertTrue(os.path.isfile(output_file)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(output_file) reader.Update() image = reader.GetOutput() self.assertEqual(image.GetDimensions()[0], 2) self.assertEqual(image.GetDimensions()[1], 3) self.assertEqual(image.GetDimensions()[2], 4)
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)
if args.nThreads < 1: os.sys.exit('Number of threads must be one or greater. Given {n}. Exiting...'.format(n=args.nThreads)) # Check opacity if args.opacity > 1 or args.opacity < 0: os.sys.exit('Opaicty must be between zeor and one. Given {o}. Exiting...'.format(o=args.opacity)) # Read both images inputReader = vtk.vtkImageReader2Factory.CreateImageReader2(args.inputImage) if inputReader is None: if args.inputImage.lower().endswith('.nii'): inputReader = vtk.vtkNIFTIImageReader() elif args.inputImage.lower().endswith('.dcm'): inputReader = vtk.vtkDICOMImageReader() elif vtkboneImported and args.inputImage.lower().endswith('.aim'): inputReader = vtkbone.vtkboneAIMReader() inputReader.DataOnCellsOff() elif vtkbonelabImported and args.inputImage.lower().endswith('.aim'): inputReader = vtkbonelab.vtkbonelabAIMReader() inputReader.DataOnCellsOff() else: os.sys.exit('Unable to find a reader for \"{fileName}\". Exiting...'.format(fileName=args.inputImage)) inputReader.SetFileName(args.inputImage) print('Loading {}...'.format(args.inputImage)) inputReader.Update() segReader = vtk.vtkImageReader2Factory.CreateImageReader2(args.inputSegmentation) if segReader is None: if args.inputSegmentation.lower().endswith('.nii'): segReader = vtk.vtkNIFTIImageReader() elif args.inputSegmentation.lower().endswith('.dcm'):
# Notes: # - This uses the processing log entries 'Mu_Scaling' and 'HU: mu water' to # determine the linear equation between native units and HU # - Outut is a float # - I don't think it is working perfectly yet # # Usage: python native_to_hu.py input.aim output.nii # Imports import argparse import vtk import re import os try: import vtkbone reader = vtkbone.vtkboneAIMReader() except ImportError: try: import vtkn88 reader = vtkn88.vtkn88AIMReader() except ImportError: try: import vtkbonelab reader = vtkbonelab.vtkbonelabAIMReader() except ImportError: os.sys.exit( 'Unable to import vtkbone, vtkn88, or vtkbonelab. Exiting...') # Argument parser parser = argparse.ArgumentParser( description='Subget medical data',
def aix(aim_file, log, stat, verbose, meta): # Python 2/3 compatible input from six.moves import input debug = False # Read input file if not os.path.isfile(aim_file): print("!% Can't open file {} for reading".format(aim_file)) print("File read error: {}".format(aim_file)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(aim_file) reader.Update() image = reader.GetOutput() # Precompute some values guard = '!-------------------------------------------------------------------------------' phys_dim = [ x * y for x, y in zip(image.GetDimensions(), reader.GetElementSize()) ] size = os.path.getsize(aim_file) names = ['Bytes', 'KBytes', 'MBytes', 'GBytes'] n_image_voxels = image.GetDimensions()[0] * image.GetDimensions( )[1] * image.GetDimensions()[2] voxel_volume = reader.GetElementSize()[0] * reader.GetElementSize( )[1] * reader.GetElementSize()[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( *reader.GetPosition())) print( '!> element size in mm {:.4f} {:.4f} {:.4f}'.format( *reader.GetElementSize())) 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: print(reader.GetProcessingLog()) # Print meta data information if meta: log = reader.GetProcessingLog() if (not log): print("None") 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 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 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 aix(aim_file, log, stat, verbose): # Python 2/3 compatible input from six.moves import input # Read input file if not os.path.isfile(aim_file): print("!% Can't open file {} for reading".format(aim_file)) print("File read error: {}".format(aim_file)) reader = vtkbone.vtkboneAIMReader() reader.DataOnCellsOff() reader.SetFileName(aim_file) reader.Update() image = reader.GetOutput() # Precompute some values guard = '!-------------------------------------------------------------------------------' phys_dim = [ x * y for x, y in zip(image.GetDimensions(), reader.GetElementSize()) ] size = os.path.getsize(aim_file) names = ['Bytes', 'KBytes', 'MBytes', 'GBytes'] i = 0 while int(size) > 1024 and i < len(names): i += 1 size = size / 2.0**10 # 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( *reader.GetPosition())) print('!> element size in mm {:.4f} {:.4f} {:.4f}'.format( *reader.GetElementSize())) 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: print(reader.GetProcessingLog()) # 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() } max_length = 0 for measure, outcome in data.items(): max_length = max(max_length, len(measure)) formatter = '{{:<{}}} {{:0.2f}}'.format(max_length) for measure, outcome in data.items(): print(formatter.format(measure, outcome)) print(guard) # Print verbose if verbose: 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 every 100 if i % 100 == 0: result = input('Continue printing results? (y/n)') if result not in ['y', 'yes']: print('') print('Aborting...') break print('') it.iternext() i += 1
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 aim2stl(input_file, output_file, transform_file, gaussian, radius, marching_cubes, decimation, 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() message("Reading AIM file " + input_file) reader = vtkbone.vtkboneAIMReader() reader.SetFileName(input_file) reader.DataOnCellsOff() reader.Update() image = reader.GetOutput() message("Read %d points from AIM file" % image.GetNumberOfPoints()) image_bounds = image.GetBounds() message("Image bounds:", (" %.4f"*6) % image_bounds) image_extent = image.GetExtent() message("Image extent:", (" %d"*6) % image_extent) message("Gaussian smoothing") gauss = vtk.vtkImageGaussianSmooth() gauss.SetStandardDeviation(gaussian) gauss.SetRadiusFactor(radius) gauss.SetInputConnection(reader.GetOutputPort()) gauss.Update() message("Total of %d voxels" % gauss.GetOutput().GetNumberOfCells()) message("Padding the data") pad = vtk.vtkImageConstantPad() pad.SetConstant(0) 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) pad.SetInputConnection(gauss.GetOutputPort()) pad.Update() message("Total of %d padded voxels" % pad.GetOutput().GetNumberOfCells()) message("Extracting isosurface") mcube = vtk.vtkImageMarchingCubes() mcube.ComputeScalarsOff() mcube.ComputeNormalsOff() mcube.SetValue(0,marching_cubes) mcube.SetInputConnection(pad.GetOutputPort()) mcube.Update() message("Generated %d triangles" % mcube.GetOutput().GetNumberOfCells()) if (decimation>0.0): message("Decimating the isosurface. Targeting {:.1f}%".format(decimation*100.0)) deci = vtk.vtkDecimatePro() deci.SetInputConnection(mcube.GetOutputPort()) deci.SetTargetReduction(decimation) # 0 is none, 1 is maximum decimation. deci.Update() message("Decimated to %d triangles" % deci.GetOutput().GetNumberOfCells()) mesh = deci else: message("No decimation of the isosurface") mesh = mcube mesh = applyTransform(transform_file, mesh) if (visualize): mat4x4 = visualize_actors( mesh.GetOutputPort(), None ) else: mat4x4 = vtk.vtkMatrix4x4() write_stl( mesh.GetOutputPort(), output_file, mat4x4 )
# Check threads if args.nThreads < 1: os.sys.exit( 'Number of threads must be one or greater. Given {n}. Exiting...'. format(n=args.nThreads)) # Read in inputs reader1 = vtk.vtkImageReader2Factory.CreateImageReader2(args.inputImage1) if reader1 is None: if args.inputImage1.lower().endswith('.nii'): reader1 = vtk.vtkNIFTIImageReader() elif args.inputImage1.lower().endswith('.dcm'): reader1 = vtk.vtkDICOMImageReader() elif vtkboneImported and args.inputImage1.lower().endswith('.aim'): reader1 = vtkbone.vtkboneAIMReader() reader1.DataOnCellsOff() elif vtkbonelabImported and args.inputImage1.lower().endswith('.aim'): reader1 = vtkbonelab.vtkbonelabAIMReader() reader1.DataOnCellsOff() else: os.sys.exit( 'Unable to find a reader for \"{fileName}\". Exiting...'.format( fileName=args.inputImage1)) reader1.SetFileName(args.inputImage1) print('Loading {}...'.format(args.inputImage1)) reader1.Update() reader2 = vtk.vtkImageReader2Factory.CreateImageReader2(args.inputImage1) if reader2 is None: if args.inputImage2.lower().endswith('.nii'):
def CreateN88Model(input_file, config_file, correction, transform, overwrite, fixed_boundary): # Assign output file name: filename, ext = os.path.splitext(input_file) if (correction): output_file = filename + '.n88model' else: output_file = filename + '_NOROTATE.n88model' # Check if output file already exists, and ask permission to overwrite if not specified: if os.path.isfile(output_file) and not overwrite: result = eval( input('File \"{}\" already exists. Overwrite? [y/n]: '.format( output_file))) if result.lower() not in ['y', 'yes']: print('Not overwriting. Exiting...') os.sys.exit() # Read input image: if not os.path.isfile(input_file): os.sys.exit('[ERROR] File \"{}\" not found!'.format(input_file)) message("Reading AIM file " + input_file + " as input...") reader = vtkbone.vtkboneAIMReader() reader.SetFileName(input_file) reader.DataOnCellsOn() reader.Update() image = reader.GetOutput() if not reader.GetOutput(): message("[ERROR] No image data read!") sys.exit(1) message("Image bounds:", (" %.4f" * 6) % image.GetBounds()) # Read the configuration file: message("Reading configuration file...") exec(compile(open(config_file).read(), config_file, 'exec'), globals()) settings_text = ["Parameters from RegN88Config:"] settings_text.append("Load = %s" % load_input) settings_text.append("Bone material id = %s" % bone_material_id) settings_text.append("Bone Young's modulus = %s" % bone_material_modulus) settings_text.append("Bone Poisson's ratio = %s" % bone_material_poissons_ratio) settings_text.append("Bone Surface material id = %s" % surface_material_id) settings_text.append("Bone Surface Young's modulus = %s" % surface_material_modulus) settings_text.append("Bone Surface Poisson's ratio = %s" % surface_material_poissons_ratio) settings_text.append("Top surface depth = %s" % top_surface_maximum_depth) settings_text.append("Bottom surface depth = %s\n" % bottom_surface_maximum_depth) message(*settings_text) # Read the transformation matrix: message("Reading IPL transformation matrix...") if (transform): t_mat = np.loadtxt(fname=transform, skiprows=2) rotation = t_mat[:3, :3] # Filter the image with a connectivity filter: message("Applying connectivity filter...") confilt = vtkbone.vtkboneImageConnectivityFilter() confilt.SetInputData(image) confilt.Update() image = confilt.GetOutput() # Generate a mesh: message("Generating mesh...") mesher = vtkbone.vtkboneImageToMesh() mesher.SetInputData(image) mesher.Update() mesh = mesher.GetOutput() # Generate material table: message("Generating material table...") E = bone_material_modulus v = bone_material_poissons_ratio material_name = 'Linear XT2' linear_material = vtkbone.vtkboneLinearIsotropicMaterial() linear_material.SetYoungsModulus(E) linear_material.SetPoissonsRatio(v) linear_material.SetName(material_name) material_table = vtkbone.vtkboneMaterialTable() material_table.AddMaterial(surface_material_id, linear_material) material_table.AddMaterial(bone_material_id, linear_material) # Compile the model: message("Compiling model...") modelConfig = vtkbone.vtkboneApplyTestBase() modelConfig.SetInputData(0, mesh) modelConfig.SetInputData(1, material_table) modelConfig.SetTopConstraintSpecificMaterial(surface_material_id) modelConfig.UnevenTopSurfaceOn() modelConfig.UseTopSurfaceMaximumDepthOn() modelConfig.SetTopSurfaceMaximumDepth(top_surface_maximum_depth) modelConfig.SetBottomConstraintSpecificMaterial(surface_material_id) modelConfig.UnevenBottomSurfaceOn() modelConfig.UseBottomSurfaceMaximumDepthOn() modelConfig.SetBottomSurfaceMaximumDepth(bottom_surface_maximum_depth) modelConfig.Update() model = modelConfig.GetOutput() message("Model bounds:", (" %.4f" * 6) % model.GetBounds()) # Apply boundary conditions: message("Setting displacement boundary conditions...") e_init = np.array([0, 0, -load_input]) # Apply fixed boundary conditions (default axial): if fixed_boundary == 'uniaxial': model.ApplyBoundaryCondition('face_z0', vtkbone.vtkboneConstraint.SENSE_Z, 0, 'z_fixed') elif fixed_boundary == 'axial': model.FixNodes('face_z0', 'bottom_fixed') else: message("[ERROR] Invalid fixed boundary conditions!") sys.exit(1) # Apply displacement boundary conditions: if "S1" in filename: message("Setting non-registered boundary conditions.") model.ApplyBoundaryCondition('face_x1', vtkbone.vtkboneConstraint.SENSE_X, e_init[0], 'x_moved') model.ApplyBoundaryCondition('face_y1', vtkbone.vtkboneConstraint.SENSE_Y, e_init[1], 'y_moved') model.ApplyBoundaryCondition('face_z1', vtkbone.vtkboneConstraint.SENSE_Z, e_init[2], 'z_moved') else: message("Setting registered boundary conditions.") if not (correction): message( "[ERROR] Applying registered boundary conditions to the wrong image!" ) sys.exit(1) e_trafo = np.dot(np.linalg.inv(rotation), e_init) model.ApplyBoundaryCondition('face_z1', vtkbone.vtkboneConstraint.SENSE_X, e_trafo[0], 'x_moved') model.ApplyBoundaryCondition('face_z1', vtkbone.vtkboneConstraint.SENSE_Y, e_trafo[1], 'y_moved') model.ApplyBoundaryCondition('face_z1', vtkbone.vtkboneConstraint.SENSE_Z, e_trafo[2], 'z_moved') info = model.GetInformation() pp_node_sets_key = vtkbone.vtkboneSolverParameters.POST_PROCESSING_NODE_SETS( ) pp_node_sets_key.Append(info, 'face_z1') pp_node_sets_key.Append(info, 'face_z0') model.AppendHistory("Created with blRegN88ModelGenerator version 1.1") # Write the n88model file: message("Writing n88model file...") writer = vtkbone.vtkboneN88ModelWriter() writer.SetInputData(model) writer.SetFileName(output_file) writer.Update() message("Done. Have a nice day!")
def Muscle(input_filename, converted_filename, segmentation_filename, bone_threshold, smoothing_iterations, segmentation_iterations, segmentation_multiplier, initial_neighborhood_radius, closing_radius, csv_filename='', tiff_filename='', histogram_filename=''): # Python 2/3 compatible input from six.moves import input # Input must be an AIM if not input_filename.lower().endswith('.aim'): os.sys.exit('[ERROR] Input \"{}\" must be an AIM'.format(input_filename)) # Read input if not os.path.isfile(input_filename): os.sys.exit('[ERROR] Cannot find file \"{}\"'.format(input_filename)) # Internal constants bone_label = 1 muscle_label = 2 # Compute calibration constants print('Computing calibration constants') reader = vtkbone.vtkboneAIMReader() reader.SetFileName(input_filename) reader.DataOnCellsOff() reader.Update() m,b = get_aim_density_equation(reader.GetProcessingLog()) del reader print(' m: {}'.format(m)) print(' b: {}'.format(b)) print('') # Converting image print('Converting {} to {}'.format(input_filename, converted_filename)) ImageConverter(input_filename, converted_filename, overwrite=True) print('') print('Reading in converted image') image = sitk.ReadImage(converted_filename) # Segment bone print('Segmenting bone') seg_bone = segment_bone(image, (bone_threshold - b)/m) seg_bone = (seg_bone>0)*bone_label print('') # Find centroid print('Finding centroid of the two largest bones') stat_filter = sitk.LabelShapeStatisticsImageFilter() stat_filter.Execute(sitk.RelabelComponent(sitk.ConnectedComponent(seg_bone))) centroids = [ stat_filter.GetCentroid(1), stat_filter.GetCentroid(2) ] seed = [0 for i in range(len(centroids[0]))] for i in range(len(seed)): seed[i] = 0.5*(centroids[0][i] + centroids[1][i]) seed_index = image.TransformPhysicalPointToIndex(seed) print(' Centroid1: {}'.format(centroids[0])) print(' Centroid2: {}'.format(centroids[1])) print(' Seed (physical): {}'.format(seed)) print(' Seed (index): {}'.format(seed_index)) print('') # Smooth image print('Performing anisotropic smoothing') timeStep = image.GetSpacing()[0] / 2.0**4 smooth_image = sitk.GradientAnisotropicDiffusion( sitk.Cast(image, sitk.sitkFloat32), timeStep=timeStep, numberOfIterations=smoothing_iterations ) print('') # Segment muscle print('Segmenting muscle') radius = int(max(1, initial_neighborhood_radius/image.GetSpacing()[0])) print(' initialNeighborhoodRadius [vox]: {}'.format(radius)) seg_muscle = sitk.ConfidenceConnected( smooth_image, seedList=[seed_index], numberOfIterations=segmentation_iterations, multiplier=segmentation_multiplier, initialNeighborhoodRadius=radius, replaceValue=1 ) # Take largest component seg_muscle = (sitk.RelabelComponent(sitk.ConnectedComponent(seg_muscle>0))==1)*muscle_label print('') # One, solid peice of background print('Cleaning up segmentation') vector_radius = [int(max(1, closing_radius//s)) for s in image.GetSpacing()] print(' Closing radius [mm]: {}'.format(closing_radius)) print(' Vector radius [voxels]: {}'.format(vector_radius)) seg = (seg_bone+seg_muscle)>0 seg = sitk.BinaryDilate(seg, vector_radius) background = sitk.RelabelComponent(sitk.ConnectedComponent(seg<1))==1 seg_muscle = sitk.BinaryErode(background<1, vector_radius)*muscle_label print('') # Join segmentation seg_muscle = sitk.Mask(seg_muscle, 1-(seg_bone>0)) seg = seg_bone + seg_muscle seg = sitk.Cast(seg, sitk.sitkInt8) # Write segmentation print('Writing segmentation to ' + segmentation_filename) sitk.WriteImage(seg, segmentation_filename) print('') print('Performing quantification') # Quantify Density intensity_filter = sitk.LabelIntensityStatisticsImageFilter() intensity_filter.Execute(seg, image) muscle_density = intensity_filter.GetMean(muscle_label) # Quantify cross sectional area # Note that since stat_filter = sitk.LabelShapeStatisticsImageFilter() stat_filter.Execute(seg) ave_cross_area = float(stat_filter.GetNumberOfPixels(muscle_label)) / seg.GetSize()[2] print(' density: {}'.format(muscle_density)) print(' cross section: {}'.format(ave_cross_area)) print('') # Write results entry = OrderedDict() entry['Filename'] = input_filename entry['Spacing.X [mm]'] = image.GetSpacing()[0] entry['Spacing.Y [mm]'] = image.GetSpacing()[1] entry['Spacing.Z [mm]'] = image.GetSpacing()[2] entry['density_slope'] = m entry['density_intercept'] = b entry['muscle density [native]'] = muscle_density entry['A.Cross [vox^2]'] = ave_cross_area print(echo_arguments('Muscle Outcomes:', entry)) # Write CSV if len(csv_filename)>0: print(' Writing to csv file ' + csv_filename) write_csv(entry, csv_filename) # Write TIFF if len(tiff_filename)>0: overlay = sitk.LabelOverlay( sitk.Cast(sitk.IntensityWindowing( sitk.Cast(image, sitk.sitkFloat32), windowMinimum = 0, windowMaximum = (bone_threshold - b)/m, outputMinimum = 0.0, outputMaximum = 255.0 ), sitk.sitkUInt8), seg, opacity=0.3 ) size = list(overlay.GetSize()) index = [0, 0, int(size[2]//2)] size[2]=0 #Step 5: Extract that specific slice using the Extract Image Filter tiff_image = sitk.Extract( overlay, size=size, index=index ) print(' Save single slice to ' + tiff_filename) sitk.WriteImage(tiff_image, tiff_filename) # Create histogram if len(histogram_filename)>0: import matplotlib.pyplot as plt print('Saving histogram to ' + histogram_filename) data = m*sitk.GetArrayFromImage(image)+b mask = sitk.GetArrayFromImage(seg) data = data.ravel() mask = mask.ravel() plt.figure(figsize=(8, 6)) plt.hist(data[mask==muscle_label], bins=1000, density=True) plt.xlabel('Density [mg HA/ccm]') plt.ylabel('Normalized Count') plt.pause(0.1) plt.savefig(histogram_filename)