def main(): # Setup description description = '''Convert from one image type to another If a processing log is set, it is appended to the processing log of the input AIM file. If the input is not an AIM file, then the processing log is set as given. Patient positioning in MINC and NIFTI file formats is not explicitely handled. ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blImageConverter", description=description) parser.add_argument('input_filename', help='Input image file') parser.add_argument('output_filename', help='Output image file') parser.add_argument('-l', '--processing_log', default='', help='Processing log to add to AIM images') parser.add_argument('-o', '--overwrite', action='store_true', help='Overwrite output without asking') # Parse and display args = parser.parse_args() print(echo_arguments('ImageConverter', vars(args))) # Run program ImageConverter(**vars(args))
def main(): description = '''Generates an n88model from registered images.''' parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog='blRegBCn88modelgenerator', description=description) parser.add_argument('input_file', help='Input image file (*REG_HOM_LS.AIM).') parser.add_argument('config_file', help='Configuration file (*.conf).') parser.add_argument( '-c', '--correction', action='store_true', help='Apply transformed boundary conditions from 3D registration.') parser.add_argument('-t', '--transform', help='Transformation matrix (*.txt).') parser.add_argument('-o', '--overwrite', action='store_true', help='Overwrite existing output.') parser.add_argument('-b', '--fixed_boundary', default='axial', help='Fixed boundary type (uniaxial/axial)') # Parse and display arguments: args = parser.parse_args() print((echo_arguments('blRegBCn88modelgenerator', vars(args)))) # Run program HERE CreateN88Model(**vars(args))
def main(): # Setup description description = '''Convert a series of 2D images to one 3D image Example usage: blPseudoCT ZTE_DATASET/1700-PUZTE_FA_1_NEX2_1.3mm 1700-PUZTE_FA_1_NEX2_1.3mm_CT blPseudoCT /Volumes/ZTE_Study/1700-PUZTE_FA_1_NEX2_1.3mm/ /Volumes/ZTE_Study/1700-PUZTE_FA_1_NEX2_1.3mm_CT/ blPseudoCT /Volumes/ZTE_Study/1100-PUZTE_FA_1_NEX2_1.3mm/ /Volumes/ZTE_Study/1100-PUZTE_FA_1_NEX2_1.3mm_CT/ blPseudoCT /Volumes/ZTE_Study/700-PUZTE_FA_1_NEX2_1.3mm/ /Volumes/ZTE_Study/700-PUZTE_FA_1_NEX2_1.3mm_CT/ blPseudoCT /Volumes/ZTE_Study/400-PUZTE_FA_1_NEX2_1.3mm/ /Volumes/ZTE_Study/400-PUZTE_FA_1_NEX2_1.3mm_CT/ There are a great deal of details in the DICOM standard and not all are handled by this function. If you experience problematic outputs, please consult a commercial DICOM importer. Stacking of slices will be determined by GDCM if the input is a DICOM series, otherwise an alpha-numeric sort. The output will be a DICOM file. ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blPseudoCT", description=description) parser.add_argument('input_directory', help='Input DICOM series directory') parser.add_argument('output_directory', help='Output DICOM series directory') parser.add_argument( '-e', '--expression', default='*', type=str, help='An expression for matching files (default: %(default)s)') parser.add_argument('-o', '--overwrite', action='store_true', help='Overwrite output without asking') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') # Parse and display args = parser.parse_args() print(echo_arguments('PseudoCT', vars(args))) # Run program PseudoCT(**vars(args))
def main(): # Set constant variables environment_location = os.environ.get( BL_DATA_DIRECTORY_ENVIRONMENT_VARIABLE) output_directory = os.getenv(BL_DATA_DIRECTORY_ENVIRONMENT_VARIABLE, BL_DATA_DIRECTORY_DEFAULT) description = '''Download the bonelab dataset. This function fetches the bonelab dataset and stores it locally on your achine. This repository is good for testing scripts and creating simple programs. The default URL is \"{BL_DATA_DEFAULT_URL}\". This can be changed with the `--url` flag. The output directory is resolved in the following order: - First, it is assigned to ~/.bldata (Currently \"{BL_DATA_DIRECTORY_DEFAULT}\") - Next, if the environment variable {ENV_VAR} is set, it defaults there (Current \"{environment_location}\") - Finally, it can be specified at the command line with the flag `--output_directory`` Note that the order of precedence is `--output_directory` > {ENV_VAR} > ~/.bldata `svn export` is used for downloading the data. This required you to have svn installed on your system. '''.format(BL_DATA_DEFAULT_URL=BL_DATA_DEFAULT_URL, BL_DATA_DIRECTORY_DEFAULT=BL_DATA_DIRECTORY_DEFAULT, ENV_VAR=BL_DATA_DIRECTORY_ENVIRONMENT_VARIABLE, environment_location=environment_location) # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blDownloadData", description=description) parser.add_argument('--url', default=BL_DATA_DEFAULT_URL, help='URL for downloading data') parser.add_argument('--output_directory', default=output_directory, help='Local directory to store data in') parser.add_argument('--no_verify', '-n', action='store_true', help='Prompt user to verify url and output_directory') # Parse and display args = parser.parse_args() print(echo_arguments('DownloadData', vars(args))) # Run program DownloadData(**vars(args))
def main(): # Setup description description='''2D slice visualizer The following keyboard mappings are available: w Print window/Level to terminal n Set interpolator to nearest neighbour c Set interpolator to cubic r Reset window/level x View in x-plane y View in y-plane z View in z-plane q Quit The following mouse mappings are available: left click + vertical scroll Modify window left click + horizontal scroll Modify level right click + vertical scroll Zoom control + left click + vertical scroll Slice level control + right click + vertical scroll Rotate slice shift + left click + vertical scroll Translate slice ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blSliceViewer", description=description ) parser.add_argument('input_filename', help='Input image file') parser.add_argument('--window', default=float(0), type=float, help='The initial window. If window is zero or less, the window is computed from the dynamic range of the image.') parser.add_argument('--level', default=float(0), type=float, help='The initial level. If window is zero or less, the level is computed from the dynamic range of the image.') parser.add_argument('--nThreads', '-n', default=int(1), type=int, help='Number of threads for each image slice visualizer (default: %(default)s)') # Parse and display args = parser.parse_args() print(echo_arguments('SliceViewer', vars(args))) # Run program SliceViewer(**vars(args))
def main(): # Setup description description='''Convert a series of 2D images to one 3D image Example usage: blImageSeries2Image ~/.bldata/dicom dicom.nii There are a great deal of details in the DICOM standard of which not all are handled by this function. If you experience nuanced outputs, please consult a commercial DICOM importer. Stacking of slices will be determined by GDCM if the input is a DICOM series, otherwise an alpha-numeric sort. If you are not reading in a DICOM, it is highly recommended to set a matching expression with `-e` to avoid extra files - like .DS_store, text files, other images - from being input into the stack. If the script fails, this is almost always the problem. `expression` is ignored for DICOM data. The output cannot be an AIM type. If you require an AIM, please write to an intermediate type (say, .nii) and then convert to AIM using `blImageConvert`. ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blImageSeries2Image", description=description ) parser.add_argument('input_directory', help='Input image') parser.add_argument('output_filename', help='Output converted image') parser.add_argument( '-e', '--expression', default='*', type=str, help='An expression for matching files (default: %(default)s)') parser.add_argument('-o', '--overwrite', action='store_true', help='Overwrite output without asking') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') # Parse and display args = parser.parse_args() print(echo_arguments('ImageSeries2Image', vars(args))) # Run program ImageSeries2Image(**vars(args))
def main(): description = '''Tabulates all transformation matrices from 3D registration.''' parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog='blRegN88TabulateTransform', description=description ) parser.add_argument('transform_files', nargs='*', help='Array of transformation matrices (*.txt).') parser.add_argument('-o', '--output_file', default='REPO_transforms.csv', help='Output file name.') # Parse and display arguments: args = parser.parse_args() print((echo_arguments('RegN88TabulateTransform', vars(args)))) # Run program HERE TransformTabulateResults(**vars(args))
def main(): # Setup description description = '''Reads segmented image and writes specified labels. Valid input and output formats include: .nii, .nii.gz Currently only accepts NIFTI file formats as input and output. ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blImageFilter", description=description) parser.add_argument('input_filename', help='Input image file (*.nii, *.nii.gz)') parser.add_argument('output_filename', help='Output image file (*.nii, *.nii.gz)') parser.add_argument( '--range', type=int, nargs=2, default=[0, 10], metavar='0', help='Set range of scalars to keep (default: %(default)s)') parser.add_argument('-o', '--overwrite', action='store_true', help='Overwrite output without asking') # Parse and display args = parser.parse_args() print(echo_arguments('ImageFilter', vars(args))) # Run program ImageFilter(**vars(args))
def main(): description = '''Transforms Faim outputs generated from solving a RegBC N88 model.''' parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog='blRegN88TransformResults', description=description) parser.add_argument( 'data_file', help='File containing data from registered FE analysis.') parser.add_argument( 'transform_file', help='File containing transformations from 3D registration (.csv).') parser.add_argument('-o', '--output_file', default='REPO_transformed_results.csv', help='Transformed output file (.csv).') # Parse and display arguments: args = parser.parse_args() print((echo_arguments('RegN88TransformResults', vars(args)))) # Run program HERE TransformResults(**vars(args))
def main(): # Setup argument parsing parser = argparse.ArgumentParser(prog="aimod", description='''AIM information examine Reads an AIM and allows user to modify image information. The following characters are options during modification. 'q' - Quit 'e' - Exit modification mode (write the AIM) A reimplementation of the seminal SCANCO program `aimod`. All efforts have been made to preserve the style. vtk only supports modification of spacing, origin, and dimensions. ''') parser.add_argument('input_aim', help='Input aim') parser.add_argument('output_aim', help='Output aim') # Parse and display args = parser.parse_args() print(echo_arguments('aimod', vars(args))) # Run program aimod(**vars(args))
def main(): # Setup description description = '''Overlay segmentation on visualization The following keyboard mappings are available: w Print window/Level to terminal n Set interpolator to nearest neighbour c Set interpolator to cubic r Reset window/level x View in x-plane y View in y-plane z View in z-plane q Quit The following mouse mappings are available: left click + vertical scroll Modify window left click + horizontal scroll Modify level right click + vertical scroll Zoom control + left click + vertical scroll Slice level control + right click + vertical scroll Rotate slice shift + left click + vertical scroll Translate slice ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blVisualizeSegmentation", description=description) parser.add_argument('input_filename', help='Input image') parser.add_argument('segmentation_filename', help='Input segmentation image') parser.add_argument('--window', default=float(500), type=float, help='The initial window') parser.add_argument('--level', default=float(0), type=float, help='The initial level') parser.add_argument( '--nThreads', '-n', default=int(1), type=int, help= 'Number of threads for each image slice visualizer (default: %(default)s)' ) parser.add_argument( '--opacity', '-o', default=float(0.25), type=float, help= 'The opacity of the segmentation between zero and one (default: %(default)s)' ) # Parse and display args = parser.parse_args() print(echo_arguments('VisualizeSegmentation', vars(args))) # Run program VisualizeSegmentation(**vars(args))
def main(): # Setup description description='''Muscle segmentation and quantification Example usage: blMuscle ~/.bldata/D0010131.AIM D0010131.nii D0010131_SEG.nii --csv_filename muscle.csv --tiff_filename D0010131_MUSCLE.tif It is assumed that the voxel coordinate z-axis corresponds to the proximal-distal direction in the image. If strange cross sectional areas are being found, check the `converted_filename` image orientation in itksnap. It is assumed the image is tightly cropped to the bone in the z-axes. This would affect the cross sectional area calculation by adding zero values before averaging. Finally, it is assumed that the image voxels are isotropic. Output TIFFs have been window/leveled to have display range (0, `bone_threshold`). To compute the real density and cross sectional area, use the following formulas: density = density_slope * 'muscle density [native]' + density_intercept Cross.A = 'Spacing.X [mm]' * 'Spacing.Y [mm]' * 'A.Cross [vox^2]' Note that for very poor quality muscles, it is possible to get negative density values. Of course this is not reasonable. The source of this error is that the calibration equation is a linearization around bone of a true, non-linear calibration equation. Alternatively, a calibration phantom more appropriate for density and fat could be provided. ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blMuscle", description=description ) parser.add_argument('input_filename', help='Input image') parser.add_argument('converted_filename', help='Output converted image (typically .nii)') parser.add_argument('segmentation_filename', help='Input image (typically .nii)') parser.add_argument('--csv_filename', '-o', default='', type=str, help='Write results to CSV file (empty string causes no write)') parser.add_argument('--tiff_filename', '-i', default='', type=str, help='Write one slice to a TIFF image (empty string causes no write)') parser.add_argument('--bone_threshold', default=800.0, type=float, help='Threshold for selecting bone (default: %(default)s mg HA/cc)') parser.add_argument('--smoothing_iterations', default=10, type=int, help='Number of iterations of Perona-Malik anisotropic filtering (default: %(default)s)') parser.add_argument('--segmentation_iterations', default=2, type=int, help='Number of iterations in confidence connected thresholding (default: %(default)s)') parser.add_argument('--segmentation_multiplier', default=2.0, type=float, help='Multiplier in confidence connected thresholding (default: %(default)s)') parser.add_argument('--initial_neighborhood_radius', default=1.0, type=float, help='Initial neighborhood radius in confidence connected thresholding (default: %(default)s [mm])') parser.add_argument('--closing_radius', default=0.5, type=float, help='Morphological closing radius for cleaning up image (default: %(default)s [mm]) ') parser.add_argument('--histogram_filename', default='', type=str, help='Create a histogram and save to the defined file (typically, a TIFF or PDF)') # Parse and display args = parser.parse_args() print(echo_arguments('Muscle', vars(args))) # Run program Muscle(**vars(args))
def main(): # Setup description description = '''Extract field on elements from N88 model Take values stored in the elements of a N88 model and extract them into an image. This can be used for visualization with uct_3d or for other processing requirements. Possible choices of field names include: {valid_fields} Example usage: blExtractFields test25a_uniaxial.n88model test25a_sed.aim Note that the domain of the finite element model does not match the domain of the image. For voxels that do not have a corresponding element, a value must still be placed there. This solution manually indexes the hexahedron elements and may be slow for very large models. If you are using this script to visualize a field with uct_3d, the output_type must be either char or short. If lower_threshold and upper_threshold are specified and not equal, the field will be truncated between these two values and rescaled to the min/max of the datatype range. If lower_threshold equals upper_threshold, the dynamic range of the image will be mapped to the range of the output type. Valid output types include: {convert_types} '''.format(valid_fields=', '.join(valid_fields()), convert_types=', '.join(EXTRACT_FIELDS_SUPPORTED_TYPES.keys())) # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blExtractFields", description=description) parser.add_argument('input_model', help='Input N88 model') parser.add_argument('output_image', help='Output image filename') parser.add_argument('-f', '--field_name', default='StrainEnergyDensity', type=str, help='Field name to extract (default: %(default)s)') parser.add_argument( '-o', '--outside_value', default=0.0, type=float, help= 'Value to assign voxels where there is no field (default: %(default)s)' ) parser.add_argument( '-l', '--lower_threshold', default=0.0, type=float, help= 'If output type is not float, what is the lower threshold for rescaling? (default: %(default)s)' ) parser.add_argument( '-u', '--upper_threshold', default=0.0, type=float, help= 'If output type is not float, what is the upper threshold for rescaling? (default: %(default)s)' ) parser.add_argument('-t', '--output_type', default='float', type=str, help='Specify the output type (default: %(default)s)') # Parse and display args = parser.parse_args() print(echo_arguments('ExtractFields', vars(args))) # Run program ExtractFields(**vars(args))
def main(): # Setup description description = '''Convert a 3D image to a series of 2D Images Example usage: blImage2ImageSeries ~/.bldata/test25a.nii temp/test25a Output files will be numbered according to last dimension slice number and formatted as follows: '{output_filename}_{number}.{extension}' Output files will be overwritten if they exist. The method will automatically figure out the number of digits required. However, if you want your filenames to have a specific number of zeros greater than that required, please set `number_of_digits`. AIM files are not supported. It is recommended to use blImageConvert if you would like to serialize an AIM. blImageConvert filename.aim filename.nii blImage2ImageSeries filename.nii filename Currently, only single component images are supported. Please submit an issue if support for Vector images is required. Writing of DICOM series is not supported because it requires delicate handling of the DICOM tags. Please see the SimpleITK DICOM documentation to write your own DICOM series writer. Writing is performed using the SimpleITK ImageSeriesWriter. Supported extension include: bmp tif tiff png jpeg jpg Many filetypes used for 2D images (png, bmp, jpg) only support a limited number of data types. For this reason, the image may need to be cast to a supporting type. By default, the algorithm will stretch the dynamic range of the image to fit optimally inside the new type. However, if you do not want the datatype to be stretch, set the flag `--no_rescale` to cause direct conversion. This may cause loss of data, for example when going from a signed to unsigned type. ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blImage2ImageSeries", description=description) parser.add_argument('input_filename', help='Input image') parser.add_argument('output_filename', help='Output converted image prefix') parser.add_argument( '-e', '--extension', default='bmp', type=str, help='Filename extension without a period (default: %(default)s)') parser.add_argument( '-n', '--number_of_digits', default=-1, type=int, help='Number of digits to format with (default: %(default)d)') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('--no_rescale', action='store_true', help='Rescale pixel types on casting') # Parse and display args = parser.parse_args() print(echo_arguments('Image2ImageSeries', vars(args))) # Run program Image2ImageSeries(**vars(args))
def test_empty_dictionary(self): expected = 'title:' + os.linesep self.assertEqual(echo_arguments('title', {}), expected)
def test_simple_dictionary(self): expected = 'title:' + os.linesep + ' a 1' + os.linesep self.assertEqual(echo_arguments('title', {'a': 1}), expected)
def main(): # Setup description description=''' A general utility for creating STL files used for rapid prototyping from AIMs and back again. This is a collection of utilities that together provide significant opportunities for creating complex STL files from input AIMs as well as conversion back to an AIM format. aim2stl : takes a thresholded AIM file and applies isosurface for STL stl2aim : takes an STL model and converts to a thresholded AIM view_stl : view an STL model boolean_stl : join, intersect or difference of two STL models add_stl : add two STL models (an alternative to 'join' boolean_stl) create_sign : make a sign with text create_sphere : make a sphere create_cylinder : make a cylinder create_cube : make a cube Input AIM must be type 'char'. STL model mesh properties can be controlled by setting gaussian smoothing and isosurface. The number of polygons can be reduced (decimation). Visualization can be performed to view STL models. Some useful keys: 'a' - actor mode (mouse, shift, control to manipulate) [REQUIRED] 'c' - camera mode (mouse, shift, control to manipulate) 't' - trackball 'j' - joystick 'w' - wireframe 's' - solid surface 'u' – a user-defined function prints 4x4 transform. 'p' – a user-defined function picks a point. 'd' – a user-defined function deletes a point. 'o' – a user-defined function outputs file of points. 'q' - quits When you quit from the visualization the model will be printed with the new transform if you have applied one. ''' epilog=''' To see the options for each of the utilities, type something like this: $ blRapidPrototype create_cube --help ''' # Setup argument parsing parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, prog="blRapidPrototype", description=description, epilog=epilog ) subparsers = parser.add_subparsers() # parser for aim2stl parser_aim2stl = subparsers.add_parser('aim2stl') parser_aim2stl.add_argument('input_file', action=CheckExt({'aim','AIM'}), help='Input AIM image file name') parser_aim2stl.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_aim2stl.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_aim2stl.add_argument('--gaussian', type=float, default=0.7, metavar='GAUSS', help='Gaussian standard deviation (default: %(default)s)') parser_aim2stl.add_argument('--radius', type=float, default=1.0, metavar='RADIUS', help='Gaussian radius support (default: %(default)s)') parser_aim2stl.add_argument('--marching_cubes', type=float, default=50.0, metavar='MC', help='Marching cubes threshold (default: %(default)s)') parser_aim2stl.add_argument('--decimation', type=float, default=0.0, metavar='DEC', choices=Range(0.0,1.0), help='Decimation is 0 (none) to 1 (max) (default: %(default)s)') parser_aim2stl.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_aim2stl.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_aim2stl.set_defaults(func=aim2stl) # parser for stl2aim parser_stl2aim = subparsers.add_parser('stl2aim') parser_stl2aim.add_argument('input_file', action=CheckExt({'stl','STL'}), help='Input STL image file name') parser_stl2aim.add_argument('output_file', action=CheckExt({'aim','AIM'}), help='Output AIM image file name') parser_stl2aim.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_stl2aim.add_argument('--el_size_mm', type=float, nargs=3, default=[0.0607,0.0607,0.0607], metavar='0', help='Set element size (default: %(default)s)') parser_stl2aim.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_stl2aim.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_stl2aim.set_defaults(func=stl2aim) # parser for view_stl parser_view_stl = subparsers.add_parser('view_stl') parser_view_stl.add_argument('input_file', action=CheckExt({'stl','STL'}), help='Input STL image file name') parser_view_stl.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_view_stl.set_defaults(func=view_stl) # parser for add_stl parser_add_stl = subparsers.add_parser('add_stl') parser_add_stl.add_argument('input_file1', action=CheckExt({'stl','STL'}), help='Input STL image file name') parser_add_stl.add_argument('input_file2', action=CheckExt({'stl','STL'}), help='Input STL image file name (allows transform)') parser_add_stl.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_add_stl.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file to input_file1 (default: %(default)s)') parser_add_stl.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_add_stl.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_add_stl.set_defaults(func=add_stl) # parser for boolean_stl parser_boolean_stl = subparsers.add_parser('boolean_stl') parser_boolean_stl.add_argument('input_file1', action=CheckExt({'stl','STL'}), help='Input STL image file name') parser_boolean_stl.add_argument('input_file2', action=CheckExt({'stl','STL'}), help='Input STL image file name (allows transform)') parser_boolean_stl.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_boolean_stl.add_argument('--operation', default="union", choices=['union', 'intersection', 'difference'], metavar='OP', help='Valid operations are union, intersection or difference (default: %(default)s)') parser_boolean_stl.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file to input_file1 (default: %(default)s)') parser_boolean_stl.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_boolean_stl.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_boolean_stl.set_defaults(func=boolean_stl) # parser for create_sign parser_create_sign = subparsers.add_parser('create_sign') parser_create_sign.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_create_sign.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_create_sign.add_argument('--text', default="Hello!", help='Letters for 3D rendering (default: %(default)s)') parser_create_sign.add_argument('--width', type=float, help='Set width of text') parser_create_sign.add_argument('--height', type=float, help='Set height of text') parser_create_sign.add_argument('--depth', type=float, default=0.5, help='Set depth of text (default: %(default)s mm)') parser_create_sign.add_argument('--nobacking', action='store_true', help='Suppress adding a backing to the text (default: %(default)s)') parser_create_sign.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_create_sign.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_create_sign.set_defaults(func=create_sign) # parser for create_sphere parser_create_sphere = subparsers.add_parser('create_sphere') parser_create_sphere.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_create_sphere.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_create_sphere.add_argument('--radius', type=float, default=0.5, help='Sphere radius (default: %(default)s)') parser_create_sphere.add_argument('--phi', type=int, default=8, metavar='RES', help='Sphere phi_resolution (default: %(default)s)') parser_create_sphere.add_argument('--theta', type=int, default=8, metavar='RES', help='Sphere theta_resolution (default: %(default)s)') parser_create_sphere.add_argument('--phi_start', type=int, default=0, choices=range(0,180), metavar='RES', help='Sphere phi start angle (default: %(default)s)') parser_create_sphere.add_argument('--phi_end', type=int, default=180, choices=range(0,180), metavar='RES', help='Sphere phi end angle (default: %(default)s)') parser_create_sphere.add_argument('--theta_start', type=int, default=0, choices=range(0,360), metavar='RES', help='Sphere theta start angle (default: %(default)s)') parser_create_sphere.add_argument('--theta_end', type=int, default=360, choices=range(0,360), metavar='RES', help='Sphere theta end angle (default: %(default)s)') parser_create_sphere.add_argument('--center', type=float, nargs=3, default=[0,0,0], metavar='0', help='Sphere center (default: %(default)s)') parser_create_sphere.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_create_sphere.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_create_sphere.set_defaults(func=create_sphere) # parser for create_cylinder parser_create_cylinder = subparsers.add_parser('create_cylinder') parser_create_cylinder.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_create_cylinder.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_create_cylinder.add_argument('--radius', type=float, default=0.5, help='Cylinder radius (default: %(default)s)') parser_create_cylinder.add_argument('--height', type=float, default=1.0, help='Cylinder height (default: %(default)s)') parser_create_cylinder.add_argument('--resolution', type=int, default=6, metavar='RES', help='Cylinder resolution (default: %(default)s)') parser_create_cylinder.add_argument('--capping', action='store_false', help='Cylinder capping (default: %(default)s)') parser_create_cylinder.add_argument('--center', type=float, nargs=3, default=[0,0,0], metavar='0', help='Cylinder center (default: %(default)s)') parser_create_cylinder.add_argument('--rotate', type=float, nargs=3, default=[0,0,0], metavar='0', help='Rotation angle about X, Y and Z axes (default: %(default)s)') parser_create_cylinder.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_create_cylinder.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_create_cylinder.set_defaults(func=create_cylinder) # parser for create_cube parser_create_cube = subparsers.add_parser('create_cube') parser_create_cube.add_argument('output_file', action=CheckExt({'stl','STL'}), help='Output STL image file name') parser_create_cube.add_argument('--transform_file', default="None", action=CheckExt({'txt','TXT'}), metavar='FILE', help='Apply a 4x4 transform from a file (default: %(default)s)') parser_create_cube.add_argument('--bounds', type=float, nargs=6, default=[0,1,0,1,0,1], metavar='0', help='Cube bounds in units mm (default: %(default)s)') parser_create_cube.add_argument('--rotate', type=float, nargs=3, default=[0,0,0], metavar='0', help='Rotation angle about X, Y and Z axes (default: %(default)s)') parser_create_cube.add_argument('--visualize', action='store_true', help='Visualize the model (default: %(default)s)') parser_create_cube.add_argument('--overwrite', action='store_true', help='Overwrite output without asking (default: %(default)s)') parser_create_cube.set_defaults(func=create_cube) # Parse and display args = parser.parse_args() print(echo_arguments('RapidPrototype', vars(args))) # Run program args.func(**vars(args))
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)