diff --git a/README.md b/README.md index 6509e7f..cd553cc 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,40 @@ mv fixed_image.nii.gz moving_image.nii.gz incoming apptainer exec docker://fnndsc/pl-images-register:latest images_register [--args] incoming outgoing ``` +If there is only one moving image, it will be registered onto the fixed image. +If there are multiple moving images, each one will be registered to the fixed image separately. + +For multiple moving images, all of them should be placed in a folder, and the folder should be passed to the +parser under moving_images_folder. + +Example 1: + +Inputs: +```shell + input_dir/fixed_image.nii.gz + input_dir/moving_image.nii.gz +``` +Outputs: +```shell + output_dir/moving_image_registered.nii.gz + output_dir/moving_image_transform.mat +``` +Example 2: + +Inputs: +```shell + input_dir/fixed_image.nii.gz + input_dir/moving_images_folder/moving_image1.nii.gz, moving_image2.nii.gz, moving_image3.nii.gz, etc. +``` +Outputs: +```shell + output_dir/moving_images_folder/moving_image1_registered.nii.gz, moving_image2_registered.nii.gz, + moving_image3.nii.gz, etc. + output_dir/moving_images_folder/moving_image1_transform.mat, moving_image2_transform.mat, + moving_image3_transform.mat, etc. +``` +Please not that all images (fixed, moving, registered) must be in nii.gz format. + ## Development The registration algorithms are found in images_register.py module. diff --git a/images_register.py b/images_register.py index 54f17a3..347e39f 100644 --- a/images_register.py +++ b/images_register.py @@ -8,6 +8,7 @@ # --------------------------------------------- ENVIRONMENT SETUP ----------------------------------------------------- # Project imports: from registration_tools import rigid_registration +from os_tools import sub_niftis # System imports: from os.path import join @@ -36,24 +37,59 @@ 'the transformation matrix. The fixed, moving, and registered moving images ' 'are all in NIfTI format.', formatter_class=ArgumentDefaultsHelpFormatter) + # parser.add_argument('-V', '--version', action='version', # version=f'%(prog)s {__version__}') +""" +If there is only one moving image, it will be registered onto the fixed image. +If there are multiple moving images, each one will be registered to the fixed image separately. + +For multiple moving images, all of them should be placed in a folder, and the folder should be passed to the +parser under moving_images_folder. + +Example 1: + +Inputs: + input_dir/fixed_image.nii.gz + input_dir/moving_image.nii.gz + +Outputs: + output_dir/moving_image_registered.nii.gz + output_dir/moving_image_transform.mat + +Example 2: + +Inputs: + input_dir/fixed_image.nii.gz + input_dir/moving_images_folder/moving_image1.nii.gz, moving_image2.nii.gz, moving_image3.nii.gz, etc. + +Outputs: + output_dir/moving_images_folder/moving_image1_registered.nii.gz, moving_image2_registered.nii.gz, + moving_image3.nii.gz, etc. + output_dir/moving_images_folder/moving_image1_transform.mat, moving_image2_transform.mat, + moving_image3_transform.mat, etc. + +Please not that all images (fixed, moving, registered) must be in nii.gz format. +""" + parser.add_argument('--fixed_image', type=str, default='fixed_image.nii.gz', help='relative path to the fixed image in relation to input folder') parser.add_argument('--moving_image', type=str, default='moving_image.nii.gz', - help='relative path to the moving image in relation to input folder') -parser.add_argument('--registered_image', type=str, default='registered_image.nii.gz', - help='relative path to the registered image in relation to output folder') -parser.add_argument('--transform_matrix', type=str, default='transform.mat', - help='relative path to the transformation matrix in relation to output folder') + help='relative path to the moving image in relation to input folder.' + 'The moving image must be in .nii.gz format.') +parser.add_argument('--moving_images_folder', type=str, default='None', + help='relative path to the folder containing multiple moving images.' + 'Every image in this folder will be registered to the fixed image.' + 'All moving images must be in .nii.gz format.') + +# ------------------------------------------- ChRIS PLUGIN WRAPPER ---------------------------------------------------- # The main function of this *ChRIS* plugin is denoted by this ``@chris_plugin`` "decorator." # Some metadata about the plugin is specified here. There is more metadata specified in setup.py. # # documentation: https://fnndsc.github.io/chris_plugin/chris_plugin.html#chris_plugin -# ------------------------------------------- ChRIS PLUGIN WRAPPER ---------------------------------------------------- @chris_plugin( parser=parser, title='Images registration', @@ -77,11 +113,23 @@ def main(options: Namespace, inputdir: Path, outputdir: Path): print(DISPLAY_TITLE) fixed_image_path = join(inputdir, options.fixed_image) - moving_image_path = join(inputdir, options.moving_image) - registered_image_path = join(outputdir, options.registered_image) - transform_matrix_path = join(outputdir, options.transform_matrix) - rigid_registration(fixed_image_path, moving_image_path, registered_image_path, transform_matrix_path) + if options.moving_images_folder == 'None': + moving_image_path = join(inputdir, options.moving_image) + registered_image_path = join(outputdir, options.moving_image.replace('.nii.gz', '_registered.nii.gz')) + transform_matrix_path = join(outputdir, options.moving_image.replace('.nii.gz', '_transform.mat')) + + rigid_registration(fixed_image_path, moving_image_path, registered_image_path, transform_matrix_path) + + else: + moving_images_list = sub_niftis(join(inputdir, options.moving_images_folder), complete_path=False) + for i, moving_image in enumerate(moving_images_list): + moving_image_path = join(inputdir, options.moving_images_folder, moving_image) + registered_image_path = join(outputdir, options.moving_images_folder, + options.moving_image.replace('.nii.gz', '_registered.nii.gz')) + transform_matrix_path = join(outputdir, options.moving_images_folder, + options.moving_image.replace('.nii.gz', '_transform.mat')) + rigid_registration(fixed_image_path, moving_image_path, registered_image_path, transform_matrix_path) # ------------------------------------------------ EXECUTE MAIN ------------------------------------------------------- diff --git a/os_tools.py b/os_tools.py new file mode 100644 index 0000000..44216d0 --- /dev/null +++ b/os_tools.py @@ -0,0 +1,65 @@ +""" +Developed by Arman Avesta, MD, PhD +FNNDSC | Boston Children's Hospital | Harvard Medical School + +This file contains functions to help with file & folder organization, parallel processing, and other operating system +functionalities. +""" + +# ----------------------------------------------- ENVIRONMENT SETUP --------------------------------------------------- +# Project imports: + +# System imports: +import os +from os.path import join, dirname +from typing import List + +# ---------------------------------------------- HELPER FUNCTIONS ----------------------------------------------------- + +def subdirs(root: str, complete_path: bool = False, prefix: str = None, suffix: str = None, sort: bool = True) \ + -> List[str]: + if complete_path: + func = os.path.join + else: + func = lambda x, y: y + res = [func(root, subdir) for subdir in os.listdir(root) if os.path.isdir(join(root, subdir)) + and (prefix is None or subdir.startswith(prefix)) + and (suffix is None or subdir.endswith(suffix))] + if sort: + res.sort() + return res + + +def subfiles(root: str, complete_path: bool = True, prefix: str = None, suffix: str = None, sort: bool = True) \ + -> List[str]: + if complete_path: + func = os.path.join + else: + func = lambda x, y: y + res = [func(root, subdir) for subdir in os.listdir(root) if os.path.isfile(join(root, subdir)) + and (prefix is None or subdir.startswith(prefix)) + and (suffix is None or subdir.endswith(suffix))] + if sort: + res.sort() + return res + + +def sub_niftis(root: str, complete_path: bool = True, sort: bool = True) -> List[str]: + return subfiles(root, complete_path=complete_path, sort=sort, suffix='.nii.gz') + + + +def split_path(path: str) -> List[str]: + """ + splits at each separator. This is different from os.path.split which only splits at last separator + """ + return path.split(os.sep) + + +# -------------------------------------------------- CODE TESTING ----------------------------------------------------- + +if __name__ == '__main__': + + path = '/Users/arman/projects/pl-images-register/data/nifti' + niftis_list = sub_niftis(path, complete_path=False) + print(niftis_list) \ No newline at end of file