def test_convert_flirt_to_sitk_transform(self): for dim in [3]: path_to_flirt_transform = os.path.join( DIR_TEST, "%dD_flirt_Target_Source.txt" % dim) path_to_fixed = os.path.join(DIR_DATA, "%dD_Brain_Target.nii.gz" % dim) path_to_moving = os.path.join(DIR_DATA, "%dD_Brain_Source.nii.gz" % dim) path_to_res = os.path.join( DIR_TMP, "%dD_flirt2sitk_target_Source_.txt" % dim) path_to_reference_transform = os.path.join( DIR_TEST, "%dD_sitk_Target_Source.txt" % dim) flirt2sitk.convert_flirt_to_sitk_transform(path_to_flirt_transform, path_to_fixed, path_to_moving, path_to_res) transform_sitk = sitkh.read_transform_sitk(path_to_res) transform_ref_sitk = sitkh.read_transform_sitk( path_to_reference_transform) nda_reference = np.array(transform_ref_sitk.GetParameters()) nda = np.array(transform_sitk.GetParameters()) # Conversion to FLIRT only provides 4 decimal places self.assertAlmostEqual(np.sum(np.abs(nda - nda_reference)), 0, places=2)
def test_estimate_motion(self): filename = "SRR_reference.nii.gz" output = os.path.join(self.dir_output, filename) dir_reference = os.path.join(self.dir_data, "estimate_motion") dir_reference_mc = os.path.join(dir_reference, "motion_correction") path_to_reference = os.path.join(dir_reference, filename) path_to_reference_mask = ph.append_to_filename( os.path.join(dir_reference, filename), self.suffix_mask) two_step_cycles = 1 iter_max = 5 exe = os.path.abspath(rsfmri_estimate_motion.__file__) cmd_args = ["python %s" % exe] cmd_args.append("--filename %s" % self.filename) cmd_args.append("--filename-mask %s" % ph.append_to_filename( self.filename, self.suffix_mask)) cmd_args.append("--dir-output %s" % self.dir_output) cmd_args.append("--two-step-cycles %s" % two_step_cycles) cmd_args.append("--iter-max %d" % iter_max) cmd = (" ").join(cmd_args) self.assertEqual(ph.execute_command(cmd), 0) # Check SRR volume res_sitk = sitkh.read_nifti_image_sitk(output) ref_sitk = sitkh.read_nifti_image_sitk(path_to_reference) diff_sitk = res_sitk - ref_sitk error = np.linalg.norm(sitk.GetArrayFromImage(diff_sitk)) self.assertAlmostEqual(error, 0, places=self.precision) # Check SRR mask volume res_sitk = sitkh.read_nifti_image_sitk( ph.append_to_filename(output, self.suffix_mask)) ref_sitk = sitkh.read_nifti_image_sitk(path_to_reference_mask) diff_sitk = res_sitk - ref_sitk error = np.linalg.norm(sitk.GetArrayFromImage(diff_sitk)) self.assertAlmostEqual(error, 0, places=self.precision) # Check transforms pattern = REGEX_FILENAMES + "[.]tfm" p = re.compile(pattern) dir_res_mc = os.path.join(self.dir_output, "motion_correction") trafos_res = sorted( [os.path.join(dir_res_mc, t) for t in os.listdir(dir_res_mc) if p.match(t)]) trafos_ref = sorted( [os.path.join(dir_reference_mc, t) for t in os.listdir(dir_reference_mc) if p.match(t)]) self.assertEqual(len(trafos_res), len(trafos_ref)) for i in range(len(trafos_ref)): nda_res = sitkh.read_transform_sitk(trafos_res[i]).GetParameters() nda_ref = sitkh.read_transform_sitk(trafos_ref[i]).GetParameters() nda_diff = np.linalg.norm(np.array(nda_res) - nda_ref) self.assertAlmostEqual(nda_diff, 0, places=self.precision)
def read_transform(path_to_file, inverse=0, nii_as_nib=0, as_itk=0): if not ph.file_exists(path_to_file): raise IOError("Transform file '%s' not found" % path_to_file) extension = ph.strip_filename_extension(path_to_file)[1] if extension not in ALLOWED_TRANSFORMS and \ extension not in ALLOWED_TRANSFORMS_DISPLACEMENTS: raise IOError("Transform file extension must be of type " "%s (transformation) or %s (displacements)" % (", ".join(ALLOWED_TRANSFORMS), ", ".join(ALLOWED_TRANSFORMS_DISPLACEMENTS))) if extension in ALLOWED_TRANSFORMS: if as_itk: tranform_sitk = sitk.read_transform_itk(path_to_file, inverse=inverse) else: transform_sitk = sitkh.read_transform_sitk(path_to_file, inverse=inverse) else: # Used for sitk_to_nreg conversion only if nii_as_nib: displacement_sitk = nib.load(path_to_file) return displacement_sitk else: displacement_sitk = sitk.ReadImage(path_to_file, sitk.sitkVectorFloat64) transform_sitk = sitk.DisplacementFieldTransform( sitk.Image(displacement_sitk)) if inverse: # May throw RuntimeError transform_sitk = transform_sitk.GetInverse() return transform_sitk
def _run_registrations(self, transformations): path_to_fixed = os.path.join(DIR_TMP, "fixed.nii.gz") path_to_moving = os.path.join(DIR_TMP, "moving.nii.gz") path_to_fixed_mask = os.path.join(DIR_TMP, "fixed_mask.nii.gz") path_to_moving_mask = os.path.join(DIR_TMP, "moving_mask.nii.gz") path_to_tmp_output = os.path.join(DIR_TMP, "foo.nii.gz") path_to_transform_regaladin = os.path.join(DIR_TMP, "transform_regaladin.txt") path_to_transform_sitk = os.path.join(DIR_TMP, "transform_sitk.txt") sitkh.write_nifti_image_sitk(self._fixed.sitk, path_to_fixed) sitkh.write_nifti_image_sitk(self._moving.sitk, path_to_moving) sitkh.write_nifti_image_sitk(self._fixed.sitk_mask, path_to_fixed_mask) # sitkh.write_nifti_image_sitk( # self._moving.sitk_mask, path_to_moving_mask) for i in range(len(transformations)): sitk.WriteTransform(transformations[i], path_to_transform_sitk) # Convert SimpleITK to RegAladin transform cmd = "simplereg_transform -sitk2nreg %s %s" % ( path_to_transform_sitk, path_to_transform_regaladin) ph.execute_command(cmd, verbose=False) # Run NiftyReg cmd_args = ["reg_aladin"] cmd_args.append("-ref %s" % path_to_fixed) cmd_args.append("-flo %s" % path_to_moving) cmd_args.append("-res %s" % path_to_tmp_output) cmd_args.append("-inaff %s" % path_to_transform_regaladin) cmd_args.append("-aff %s" % path_to_transform_regaladin) cmd_args.append("-rigOnly") cmd_args.append("-ln 2") cmd_args.append("-voff") cmd_args.append("-rmask %s" % path_to_fixed_mask) # To avoid error "0 correspondences between blocks were found" that can # occur for some cases. Also, disable moving mask, as this would be ignored # anyway cmd_args.append("-noSym") ph.print_info( "Run Registration (RegAladin) based on PCA-init %d ... " % (i + 1)) ph.execute_command(" ".join(cmd_args), verbose=False) # Convert RegAladin to SimpleITK transform cmd = "simplereg_transform -nreg2sitk %s %s" % ( path_to_transform_regaladin, path_to_transform_sitk) ph.execute_command(cmd, verbose=False) transformations[i] = sitkh.read_transform_sitk( path_to_transform_sitk) return transformations
def main(): time_start = ph.start_timing() np.set_printoptions(precision=3) input_parser = InputArgparser( description="Register an obtained reconstruction (moving) " "to a template image/space (fixed) using rigid registration. " "The resulting registration can optionally be applied to previously " "obtained motion correction slice transforms so that a volumetric " "reconstruction is possible in the (standard anatomical) space " "defined by the fixed.", ) input_parser.add_fixed(required=True) input_parser.add_moving(required=True) input_parser.add_output(help="Path to registration transform (.txt)", required=True) input_parser.add_fixed_mask(required=False) input_parser.add_moving_mask(required=False) input_parser.add_option( option_string="--initial-transform", type=str, help="Path to initial transform. " "If not provided, registration will be initialized based on " "rigid alignment of eigenbasis of the fixed/moving image masks " "using principal component analysis", default=None) input_parser.add_v2v_method( option_string="--method", help="Registration method used for the registration.", default="RegAladin", ) input_parser.add_argument( "--refine-pca", "-refine-pca", action='store_true', help="If given, PCA-based initializations will be refined using " "RegAladin registrations.") input_parser.add_dir_input_mc() input_parser.add_verbose(default=0) input_parser.add_log_config(default=1) args = input_parser.parse_args() input_parser.print_arguments(args) if args.log_config: input_parser.log_config(os.path.abspath(__file__)) if not args.output.endswith(".txt"): raise IOError("output transformation path must end in '.txt'") dir_output = os.path.dirname(args.output) ph.create_directory(dir_output) # --------------------------------Read Data-------------------------------- ph.print_title("Read Data") fixed = st.Stack.from_filename(file_path=args.fixed, file_path_mask=args.fixed_mask, extract_slices=False) moving = st.Stack.from_filename(file_path=args.moving, file_path_mask=args.moving_mask, extract_slices=False) path_to_tmp_output = os.path.join( DIR_TMP, ph.append_to_filename(os.path.basename(args.moving), "_warped")) # ---------------------------- Initialization ---------------------------- if args.initial_transform is None: ph.print_title("Estimate initial transform using PCA") if args.moving_mask is None or args.fixed_mask is None: ph.print_warning("Fixed and moving masks are strongly recommended") transform_initializer = tinit.TransformInitializer( fixed=fixed, moving=moving, similarity_measure="NMI", refine_pca_initializations=args.refine_pca, ) transform_initializer.run() transform_init_sitk = transform_initializer.get_transform_sitk() else: transform_init_sitk = sitkh.read_transform_sitk(args.initial_transform) sitk.WriteTransform(transform_init_sitk, args.output) # -------------------Register Reconstruction to Template------------------- ph.print_title("Registration") if args.method == "RegAladin": path_to_transform_regaladin = os.path.join(DIR_TMP, "transform_regaladin.txt") # Convert SimpleITK to RegAladin transform cmd = "simplereg_transform -sitk2nreg %s %s" % ( args.output, path_to_transform_regaladin) ph.execute_command(cmd, verbose=False) # Run NiftyReg cmd_args = ["reg_aladin"] cmd_args.append("-ref '%s'" % args.fixed) cmd_args.append("-flo '%s'" % args.moving) cmd_args.append("-res '%s'" % path_to_tmp_output) cmd_args.append("-inaff '%s'" % path_to_transform_regaladin) cmd_args.append("-aff '%s'" % path_to_transform_regaladin) cmd_args.append("-rigOnly") cmd_args.append("-ln 2") # seems to perform better for spina bifida cmd_args.append("-voff") if args.fixed_mask is not None: cmd_args.append("-rmask '%s'" % args.fixed_mask) # To avoid error "0 correspondences between blocks were found" that can # occur for some cases. Also, disable moving mask, as this would be ignored # anyway cmd_args.append("-noSym") # if args.moving_mask is not None: # cmd_args.append("-fmask '%s'" % args.moving_mask) ph.print_info("Run Registration (RegAladin) ... ", newline=False) ph.execute_command(" ".join(cmd_args), verbose=False) print("done") # Convert RegAladin to SimpleITK transform cmd = "simplereg_transform -nreg2sitk '%s' '%s'" % ( path_to_transform_regaladin, args.output) ph.execute_command(cmd, verbose=False) else: path_to_transform_flirt = os.path.join(DIR_TMP, "transform_flirt.txt") # Convert SimpleITK into FLIRT transform cmd = "simplereg_transform -sitk2flirt '%s' '%s' '%s' '%s'" % ( args.output, args.fixed, args.moving, path_to_transform_flirt) ph.execute_command(cmd, verbose=False) # Define search angle ranges for FLIRT in all three dimensions search_angles = [ "-searchr%s -%d %d" % (x, 180, 180) for x in ["x", "y", "z"] ] cmd_args = ["flirt"] cmd_args.append("-in '%s'" % args.moving) cmd_args.append("-ref '%s'" % args.fixed) if args.initial_transform is not None: cmd_args.append("-init '%s'" % path_to_transform_flirt) cmd_args.append("-omat '%s'" % path_to_transform_flirt) cmd_args.append("-out '%s'" % path_to_tmp_output) cmd_args.append("-dof 6") cmd_args.append((" ").join(search_angles)) if args.moving_mask is not None: cmd_args.append("-inweight '%s'" % args.moving_mask) if args.fixed_mask is not None: cmd_args.append("-refweight '%s'" % args.fixed_mask) ph.print_info("Run Registration (FLIRT) ... ", newline=False) ph.execute_command(" ".join(cmd_args), verbose=False) print("done") # Convert FLIRT to SimpleITK transform cmd = "simplereg_transform -flirt2sitk '%s' '%s' '%s' '%s'" % ( path_to_transform_flirt, args.fixed, args.moving, args.output) ph.execute_command(cmd, verbose=False) if args.dir_input_mc is not None: ph.print_title("Update Motion-Correction Transformations") transform_sitk = sitkh.read_transform_sitk(args.output, inverse=1) if args.dir_input_mc.endswith("/"): subdir_mc = args.dir_input_mc.split("/")[-2] else: subdir_mc = args.dir_input_mc.split("/")[-1] dir_output_mc = os.path.join(dir_output, subdir_mc) ph.create_directory(dir_output_mc, delete_files=True) pattern = REGEX_FILENAMES + "[.]tfm" p = re.compile(pattern) trafos = [t for t in os.listdir(args.dir_input_mc) if p.match(t)] for t in trafos: path_to_input_transform = os.path.join(args.dir_input_mc, t) path_to_output_transform = os.path.join(dir_output_mc, t) t_sitk = sitkh.read_transform_sitk(path_to_input_transform) t_sitk = sitkh.get_composite_sitk_affine_transform( transform_sitk, t_sitk) sitk.WriteTransform(t_sitk, path_to_output_transform) ph.print_info("%d transformations written to '%s'" % (len(trafos), dir_output_mc)) if args.verbose: ph.show_niftis([args.fixed, path_to_tmp_output]) elapsed_time_total = ph.stop_timing(time_start) # Summary ph.print_title("Summary") print("Computational Time: %s" % (elapsed_time_total)) return 0
def run(self): if not ph.directory_exists(self._dir_motion_correction): raise exceptions.DirectoryNotExistent(self._dir_motion_correction) abs_path_to_directory = os.path.abspath(self._dir_motion_correction) for i in range(len(self._stacks)): stack_name = self._stacks[i].get_filename() # update stack position path_to_stack_transform = os.path.join(abs_path_to_directory, "%s.tfm" % stack_name) if ph.file_exists(path_to_stack_transform): transform_stack_sitk = sitkh.read_transform_sitk( path_to_stack_transform) transform_stack_sitk_inv = sitkh.read_transform_sitk( path_to_stack_transform, inverse=True) self._stacks[i].update_motion_correction(transform_stack_sitk) ph.print_info("Stack '%s': Stack position updated" % stack_name) else: transform_stack_sitk_inv = sitk.Euler3DTransform() # update slice positions pattern_trafo_slices = stack_name + self._prefix_slice + \ "([0-9]+)[.]tfm" p = re.compile(pattern_trafo_slices) dic_slice_transforms = { int(p.match(f).group(1)): os.path.join(abs_path_to_directory, p.match(f).group(0)) for f in os.listdir(abs_path_to_directory) if p.match(f) } slices = self._stacks[i].get_slices() for i_slice in range(self._stacks[i].get_number_of_slices()): if i_slice in dic_slice_transforms.keys(): transform_slice_sitk = sitkh.read_transform_sitk( dic_slice_transforms[i_slice]) transform_slice_sitk = \ sitkh.get_composite_sitk_affine_transform( transform_slice_sitk, transform_stack_sitk_inv) slices[i_slice].update_motion_correction( transform_slice_sitk) # # ------------------------- HACK ------------------------- # # 18 Jan 2019 # # HACK to use results of a previous version where image # # slices were still exported # # (Bug was that after stack intensity correction, the # # previous v2v-reg was not passed on to the final # # registration transform): # import niftymic.base.slice as sl # path_to_slice = re.sub( # ".tfm", ".nii.gz", dic_slice_transforms[i_slice]) # path_to_slice_mask = re.sub( # ".tfm", "_mask.nii.gz", dic_slice_transforms[i_slice]) # slice_sitk = sitk.ReadImage(path_to_slice) # slice_sitk_mask = sitk.ReadImage(path_to_slice_mask) # hack = sl.Slice.from_sitk_image( # # slice_sitk=slice_sitk, # slice_sitk=slice_sitk_mask, # mask for Mask-SRR! # slice_sitk_mask=slice_sitk_mask, # slice_number=slices[i_slice].get_slice_number(), # slice_thickness=slices[i_slice].get_slice_thickness(), # ) # self._stacks[i]._slices[i_slice] = hack # # -------------------------------------------------------- else: self._stacks[i].delete_slice(slices[i_slice]) # print update information ph.print_info("Stack '%s': Slice positions updated " "(%d/%d slices deleted)" % ( stack_name, len(self._stacks[i].get_deleted_slice_numbers()), self._stacks[i].sitk.GetSize()[-1], )) # delete entire stack if all slices were rejected if self._stacks[i].get_number_of_slices() == 0: ph.print_info("Stack '%s' removed as all slices were deleted" % stack_name) self._stacks[i] = None # only return maintained stacks self._stacks = [s for s in self._stacks if s is not None] if len(self._stacks) == 0: raise RuntimeError( "All stacks removed. " "Did you check that the correct motion-correction directory " "was provided?")
def run(self, older_than_v3=False): if not ph.directory_exists(self._dir_motion_correction): raise exceptions.DirectoryNotExistent( self._dir_motion_correction) abs_path_to_directory = os.path.abspath( self._dir_motion_correction) path_to_rejected_slices = os.path.join( abs_path_to_directory, "rejected_slices.json") if ph.file_exists(path_to_rejected_slices): self._rejected_slices = ph.read_dictionary_from_json( path_to_rejected_slices) bool_check = True else: self._rejected_slices = None bool_check = False for i in range(len(self._stacks)): stack_name = self._stacks[i].get_filename() if not older_than_v3: # update stack position path_to_stack_transform = os.path.join( abs_path_to_directory, "%s.tfm" % stack_name) if ph.file_exists(path_to_stack_transform): transform_stack_sitk = sitkh.read_transform_sitk( path_to_stack_transform) transform_stack_sitk_inv = sitkh.read_transform_sitk( path_to_stack_transform, inverse=True) self._stacks[i].update_motion_correction( transform_stack_sitk) ph.print_info( "Stack '%s': Stack position updated" % stack_name) else: transform_stack_sitk_inv = sitk.Euler3DTransform() if self._volume_motion_only: continue # update slice positions pattern_trafo_slices = stack_name + self._prefix_slice + \ "([0-9]+)[.]tfm" p = re.compile(pattern_trafo_slices) dic_slice_transforms = { int(p.match(f).group(1)): os.path.join( abs_path_to_directory, p.match(f).group(0)) for f in os.listdir(abs_path_to_directory) if p.match(f) } slices = self._stacks[i].get_slices() for i_slice in range(self._stacks[i].get_number_of_slices()): if i_slice in dic_slice_transforms.keys(): transform_slice_sitk = sitkh.read_transform_sitk( dic_slice_transforms[i_slice]) transform_slice_sitk = \ sitkh.get_composite_sitk_affine_transform( transform_slice_sitk, transform_stack_sitk_inv) slices[i_slice].update_motion_correction( transform_slice_sitk) else: self._stacks[i].delete_slice(slices[i_slice]) # ----------------------------- HACK ----------------------------- # 18 Jan 2019 # HACK to use results of a previous version where image slices were # still exported. # (There was a bug after stack intensity correction, which resulted # in v2v-reg transforms not being part of in the final registration # transforms; Thus, slice transformations (tfm's) were flawed and # could not be used): else: # Recover suffix for mask pattern = stack_name + self._prefix_slice + \ "[0-9]+[_]([a-zA-Z]+)[.]nii.gz" pm = re.compile(pattern) matches = list(set([pm.match(f).group(1) for f in os.listdir( abs_path_to_directory) if pm.match(f)])) if len(matches) > 1: raise RuntimeError("Suffix mask cannot be determined") suffix_mask = "_%s" % matches[0] # Recover stack path_to_stack = os.path.join( abs_path_to_directory, "%s.nii.gz" % stack_name) path_to_stack_mask = os.path.join( abs_path_to_directory, "%s%s.nii.gz" % ( stack_name, suffix_mask)) stack = st.Stack.from_filename( path_to_stack, path_to_stack_mask) # Recover slices pattern_trafo_slices = stack_name + self._prefix_slice + \ "([0-9]+)[.]tfm" p = re.compile(pattern_trafo_slices) dic_slice_transforms = { int(p.match(f).group(1)): os.path.join( abs_path_to_directory, p.match(f).group(0)) for f in os.listdir(abs_path_to_directory) if p.match(f) } slices = self._stacks[i].get_slices() for i_slice in range(self._stacks[i].get_number_of_slices()): if i_slice in dic_slice_transforms.keys(): path_to_slice = re.sub( ".tfm", ".nii.gz", dic_slice_transforms[i_slice]) path_to_slice_mask = re.sub( ".tfm", "%s.nii.gz" % suffix_mask, dic_slice_transforms[i_slice]) slice_sitk = sitk.ReadImage(path_to_slice) slice_sitk_mask = sitk.ReadImage(path_to_slice_mask) hack = sl.Slice.from_sitk_image( slice_sitk=slice_sitk, # slice_sitk=slice_sitk_mask, # mask for Mask-SRR! slice_sitk_mask=slice_sitk_mask, slice_number=slices[i_slice].get_slice_number(), slice_thickness=slices[ i_slice].get_slice_thickness(), ) self._stacks[i]._slices[i_slice] = hack else: self._stacks[i].delete_slice(slices[i_slice]) self._stacks[i].sitk = stack.sitk self._stacks[i].sitk_mask = stack.sitk_mask self._stacks[i].itk = stack.itk self._stacks[i].itk_mask = stack.itk_mask # ----------------------------------------------------------------- # print update information ph.print_info( "Stack '%s': Slice positions updated " "(%d/%d slices deleted)" % ( stack_name, len(self._stacks[i].get_deleted_slice_numbers()), self._stacks[i].sitk.GetSize()[-1], ) ) # delete entire stack if all slices were rejected if self._stacks[i].get_number_of_slices() == 0: ph.print_info( "Stack '%s' removed as all slices were deleted" % stack_name) self._stacks[i] = None # only return maintained stacks self._stacks = [s for s in self._stacks if s is not None] if len(self._stacks) == 0: raise RuntimeError( "All stacks removed. " "Did you check that the correct motion-correction directory " "was provided?")
def test_reconstruct_volume(self): filename = "SRR_stacks3_TK1_lsmr_alpha0p02_itermax5.nii.gz" output = os.path.join(self.dir_output, filename) dir_reference = os.path.join(self.dir_data, "reconstruct_volume") dir_reference_mc = os.path.join(dir_reference, "motion_correction") path_to_reference = os.path.join(dir_reference, filename) path_to_reference_mask = ph.append_to_filename( os.path.join(dir_reference, filename), self.suffix_mask) two_step_cycles = 1 iter_max = 5 threshold = 0.75 alpha = 0.02 alpha_first = 0.2 sigma = 1 intensity_correction = 1 isotropic_resolution = 1.02 v2v_method = "RegAladin" cmd_args = [] cmd_args.append("--filenames %s" % " ".join(self.filenames)) cmd_args.append("--output %s" % output) cmd_args.append("--suffix-mask %s" % self.suffix_mask) cmd_args.append("--two-step-cycles %s" % two_step_cycles) cmd_args.append("--iter-max %d" % iter_max) cmd_args.append("--threshold-first %f" % threshold) cmd_args.append("--sigma %f" % sigma) cmd_args.append("--threshold %f" % threshold) cmd_args.append("--intensity-correction %d" % intensity_correction) cmd_args.append("--isotropic-resolution %s" % isotropic_resolution) cmd_args.append("--alpha %f" % alpha) cmd_args.append("--alpha-first %f" % alpha_first) cmd_args.append("--v2v-method %s" % v2v_method) # cmd_args.append("--verbose 1") cmd = "niftymic_reconstruct_volume %s" % (" ").join(cmd_args) self.assertEqual(ph.execute_command(cmd), 0) # Check SRR volume res_sitk = sitkh.read_nifti_image_sitk(output) ref_sitk = sitkh.read_nifti_image_sitk(path_to_reference) diff_sitk = res_sitk - ref_sitk error = np.linalg.norm(sitk.GetArrayFromImage(diff_sitk)) self.assertAlmostEqual(error, 0, places=self.precision) # Check SRR mask volume res_sitk = sitkh.read_nifti_image_sitk( ph.append_to_filename(output, "_mask")) ref_sitk = sitkh.read_nifti_image_sitk(path_to_reference_mask) diff_sitk = res_sitk - ref_sitk error = np.linalg.norm(sitk.GetArrayFromImage(diff_sitk)) self.assertAlmostEqual(error, 0, places=self.precision) # Check transforms pattern = REGEX_FILENAMES + "[.]tfm" p = re.compile(pattern) dir_res_mc = os.path.join(self.dir_output, "motion_correction") trafos_res = sorted([ os.path.join(dir_res_mc, t) for t in os.listdir(dir_res_mc) if p.match(t) ]) trafos_ref = sorted([ os.path.join(dir_reference_mc, t) for t in os.listdir(dir_reference_mc) if p.match(t) ]) self.assertEqual(len(trafos_res), len(trafos_ref)) for i in range(len(trafos_ref)): nda_res = sitkh.read_transform_sitk(trafos_res[i]).GetParameters() nda_ref = sitkh.read_transform_sitk(trafos_ref[i]).GetParameters() nda_diff = np.linalg.norm(np.array(nda_res) - nda_ref) self.assertAlmostEqual(nda_diff, 0, places=self.precision)
def test_register_image(self): filename = "registration_transform_sitk.txt" gestational_age = 28 path_to_recon = os.path.join( self.dir_data, "register_image", "SRR_stacks3_TK1_lsmr_alpha0p02_itermax5.nii.gz") dir_input_mc = os.path.join(self.dir_data, "register_image", "motion_correction") path_to_transform_res = os.path.join(self.dir_output, filename) path_to_transform_ref = os.path.join(self.dir_data, "register_image", filename) dir_ref_mc = os.path.join(self.dir_data, "register_image", "motion_correction_ref") path_to_rejected_slices_ref = os.path.join(dir_ref_mc, "rejected_slices.json") template = os.path.join(DIR_TEMPLATES, "STA%d.nii.gz" % gestational_age) template_mask = os.path.join(DIR_TEMPLATES, "STA%d_mask.nii.gz" % gestational_age) cmd_args = ["niftymic_register_image"] cmd_args.append("--fixed %s" % template) cmd_args.append("--moving %s" % path_to_recon) cmd_args.append("--fixed-mask %s" % template_mask) cmd_args.append("--moving-mask %s" % ph.append_to_filename(path_to_recon, self.suffix_mask)) cmd_args.append("--dir-input-mc %s" % dir_input_mc) cmd_args.append("--output %s" % path_to_transform_res) cmd_args.append("--init-pca") # cmd_args.append("--verbose 1") self.assertEqual(ph.execute_command(" ".join(cmd_args)), 0) # Check registration transform res_sitk = sitkh.read_transform_sitk(path_to_transform_res) ref_sitk = sitkh.read_transform_sitk(path_to_transform_ref) res_nda = res_sitk.GetParameters() ref_nda = ref_sitk.GetParameters() diff_nda = np.array(res_nda) - ref_nda self.assertAlmostEqual(np.linalg.norm(diff_nda), 0, places=self.precision) # Check individual slice transforms pattern = REGEX_FILENAMES + "[.]tfm" p = re.compile(pattern) dir_res_mc = os.path.join(self.dir_output, "motion_correction") trafos_res = sorted([ os.path.join(dir_res_mc, t) for t in os.listdir(dir_res_mc) if p.match(t) ]) trafos_ref = sorted([ os.path.join(dir_ref_mc, t) for t in os.listdir(dir_res_mc) if p.match(t) ]) self.assertEqual(len(trafos_res), len(trafos_ref)) for i in range(len(trafos_ref)): nda_res = sitkh.read_transform_sitk(trafos_res[i]).GetParameters() nda_ref = sitkh.read_transform_sitk(trafos_ref[i]).GetParameters() nda_diff = np.linalg.norm(np.array(nda_res) - nda_ref) self.assertAlmostEqual(nda_diff, 0, places=self.precision) # Check rejected_slices.json path_to_rejected_slices_res = os.path.join(dir_res_mc, "rejected_slices.json") self.assertEqual(ph.file_exists(path_to_rejected_slices_res), True) rejected_slices_res = ph.read_dictionary_from_json( path_to_rejected_slices_res) rejected_slices_ref = ph.read_dictionary_from_json( path_to_rejected_slices_ref) self.assertEqual(rejected_slices_res == rejected_slices_ref, True)
def main(): time_start = ph.start_timing() np.set_printoptions(precision=3) input_parser = InputArgparser( description="Register an obtained reconstruction (moving) " "to a template image/space (fixed) using rigid registration. " "The resulting registration can optionally be applied to previously " "obtained motion correction slice transforms so that a volumetric " "reconstruction is possible in the (standard anatomical) space " "defined by the fixed.", ) input_parser.add_fixed(required=True) input_parser.add_moving(required=True) input_parser.add_output(help="Path to registration transform (.txt)", required=True) input_parser.add_fixed_mask() input_parser.add_moving_mask() input_parser.add_dir_input_mc() input_parser.add_search_angle(default=180) input_parser.add_option(option_string="--initial-transform", type=str, help="Path to initial transform.", default=None) input_parser.add_option( option_string="--test-ap-flip", type=int, help="Turn on/off functionality to run an additional registration " "after an AP-flip. Seems to be more robust to find a better " "registration outcome in general.", default=1) input_parser.add_option( option_string="--use-flirt", type=int, help="Turn on/off functionality to use FLIRT for the registration.", default=1) input_parser.add_option( option_string="--use-regaladin", type=int, help="Turn on/off functionality to use RegAladin for the " "registration.", default=1) input_parser.add_verbose(default=0) input_parser.add_log_config(default=1) args = input_parser.parse_args() input_parser.print_arguments(args) debug = 0 if args.log_config: input_parser.log_config(os.path.abspath(__file__)) if not args.use_regaladin and not args.use_flirt: raise IOError("Either RegAladin or FLIRT must be activated.") if not args.output.endswith(".txt"): raise IOError("output transformation path must end in '.txt'") dir_output = os.path.dirname(args.output) # --------------------------------Read Data-------------------------------- ph.print_title("Read Data") fixed = st.Stack.from_filename(file_path=args.fixed, file_path_mask=args.fixed_mask, extract_slices=False) moving = st.Stack.from_filename(file_path=args.moving, file_path_mask=args.moving_mask, extract_slices=False) if args.initial_transform is not None: transform_sitk = sitkh.read_transform_sitk(args.initial_transform) else: transform_sitk = sitk.AffineTransform(fixed.sitk.GetDimension()) sitk.WriteTransform(transform_sitk, args.output) path_to_tmp_output = os.path.join( DIR_TMP, ph.append_to_filename(os.path.basename(args.moving), "_warped")) # -------------------Register Reconstruction to Template------------------- ph.print_title("Register Reconstruction to Template") if args.use_flirt: path_to_transform_flirt = os.path.join(DIR_TMP, "transform_flirt.txt") # Convert SimpleITK into FLIRT transform cmd = "simplereg_transform -sitk2flirt %s %s %s %s" % ( args.output, args.fixed, args.moving, path_to_transform_flirt) ph.execute_command(cmd, verbose=False) # Define search angle ranges for FLIRT in all three dimensions search_angles = [ "-searchr%s -%d %d" % (x, args.search_angle, args.search_angle) for x in ["x", "y", "z"] ] # flt = nipype.interfaces.fsl.FLIRT() # flt.inputs.in_file = args.moving # flt.inputs.reference = args.fixed # if args.initial_transform is not None: # flt.inputs.in_matrix_file = path_to_transform_flirt # flt.inputs.out_matrix_file = path_to_transform_flirt # # flt.inputs.output_type = "NIFTI_GZ" # flt.inputs.out_file = path_to_tmp_output # flt.inputs.args = "-dof 6" # flt.inputs.args += " %s" % " ".join(search_angles) # if args.moving_mask is not None: # flt.inputs.in_weight = args.moving_mask # if args.fixed_mask is not None: # flt.inputs.ref_weight = args.fixed_mask # ph.print_info("Run Registration (FLIRT) ... ", newline=False) # flt.run() # print("done") cmd_args = ["flirt"] cmd_args.append("-in %s" % args.moving) cmd_args.append("-ref %s" % args.fixed) if args.initial_transform is not None: cmd_args.append("-init %s" % path_to_transform_flirt) cmd_args.append("-omat %s" % path_to_transform_flirt) cmd_args.append("-out %s" % path_to_tmp_output) cmd_args.append("-dof 6") cmd_args.append((" ").join(search_angles)) if args.moving_mask is not None: cmd_args.append("-inweight %s" % args.moving_mask) if args.fixed_mask is not None: cmd_args.append("-refweight %s" % args.fixed_mask) ph.print_info("Run Registration (FLIRT) ... ", newline=False) ph.execute_command(" ".join(cmd_args), verbose=False) print("done") # Convert FLIRT to SimpleITK transform cmd = "simplereg_transform -flirt2sitk %s %s %s %s" % ( path_to_transform_flirt, args.fixed, args.moving, args.output) ph.execute_command(cmd, verbose=False) if debug: ph.show_niftis([args.fixed, path_to_tmp_output]) # Additionally, use RegAladin for more accurate alignment # Rationale: FLIRT has better capture range, but RegAladin seems to # find better alignment once it is within its capture range. if args.use_regaladin: path_to_transform_regaladin = os.path.join(DIR_TMP, "transform_regaladin.txt") # Convert SimpleITK to RegAladin transform cmd = "simplereg_transform -sitk2nreg %s %s" % ( args.output, path_to_transform_regaladin) ph.execute_command(cmd, verbose=False) # nreg = nipype.interfaces.niftyreg.RegAladin() # nreg.inputs.ref_file = args.fixed # nreg.inputs.flo_file = args.moving # nreg.inputs.res_file = path_to_tmp_output # nreg.inputs.in_aff_file = path_to_transform_regaladin # nreg.inputs.aff_file = path_to_transform_regaladin # nreg.inputs.args = "-rigOnly -voff" # if args.moving_mask is not None: # nreg.inputs.fmask_file = args.moving_mask # if args.fixed_mask is not None: # nreg.inputs.rmask_file = args.fixed_mask # ph.print_info("Run Registration (RegAladin) ... ", newline=False) # nreg.run() # print("done") cmd_args = ["reg_aladin"] cmd_args.append("-ref %s" % args.fixed) cmd_args.append("-flo %s" % args.moving) cmd_args.append("-res %s" % path_to_tmp_output) if args.initial_transform is not None or args.use_flirt == 1: cmd_args.append("-inaff %s" % path_to_transform_regaladin) cmd_args.append("-aff %s" % path_to_transform_regaladin) # cmd_args.append("-cog") # cmd_args.append("-ln 2") cmd_args.append("-rigOnly") cmd_args.append("-voff") if args.moving_mask is not None: cmd_args.append("-fmask %s" % args.moving_mask) if args.fixed_mask is not None: cmd_args.append("-rmask %s" % args.fixed_mask) ph.print_info("Run Registration (RegAladin) ... ", newline=False) ph.execute_command(" ".join(cmd_args), verbose=False) print("done") # Convert RegAladin to SimpleITK transform cmd = "simplereg_transform -nreg2sitk %s %s" % ( path_to_transform_regaladin, args.output) ph.execute_command(cmd, verbose=False) if debug: ph.show_niftis([args.fixed, path_to_tmp_output]) if args.test_ap_flip: path_to_transform_flip = os.path.join(DIR_TMP, "transform_flip.txt") path_to_tmp_output_flip = os.path.join(DIR_TMP, "output_flip.nii.gz") # Get AP-flip transform transform_ap_flip_sitk = get_ap_flip_transform(args.fixed) path_to_transform_flip_regaladin = os.path.join( DIR_TMP, "transform_flip_regaladin.txt") sitk.WriteTransform(transform_ap_flip_sitk, path_to_transform_flip) # Compose current transform with AP flip transform cmd = "simplereg_transform -c %s %s %s" % ( args.output, path_to_transform_flip, path_to_transform_flip) ph.execute_command(cmd, verbose=False) # Convert SimpleITK to RegAladin transform cmd = "simplereg_transform -sitk2nreg %s %s" % ( path_to_transform_flip, path_to_transform_flip_regaladin) ph.execute_command(cmd, verbose=False) # nreg = nipype.interfaces.niftyreg.RegAladin() # nreg.inputs.ref_file = args.fixed # nreg.inputs.flo_file = args.moving # nreg.inputs.res_file = path_to_tmp_output_flip # nreg.inputs.in_aff_file = path_to_transform_flip_regaladin # nreg.inputs.aff_file = path_to_transform_flip_regaladin # nreg.inputs.args = "-rigOnly -voff" # if args.moving_mask is not None: # nreg.inputs.fmask_file = args.moving_mask # if args.fixed_mask is not None: # nreg.inputs.rmask_file = args.fixed_mask # ph.print_info("Run Registration AP-flipped (RegAladin) ... ", # newline=False) # nreg.run() # print("done") cmd_args = ["reg_aladin"] cmd_args.append("-ref %s" % args.fixed) cmd_args.append("-flo %s" % args.moving) cmd_args.append("-res %s" % path_to_tmp_output_flip) cmd_args.append("-inaff %s" % path_to_transform_flip_regaladin) cmd_args.append("-aff %s" % path_to_transform_flip_regaladin) cmd_args.append("-rigOnly") # cmd_args.append("-ln 2") cmd_args.append("-voff") if args.moving_mask is not None: cmd_args.append("-fmask %s" % args.moving_mask) if args.fixed_mask is not None: cmd_args.append("-rmask %s" % args.fixed_mask) ph.print_info("Run Registration AP-flipped (RegAladin) ... ", newline=False) ph.execute_command(" ".join(cmd_args), verbose=False) print("done") if debug: ph.show_niftis( [args.fixed, path_to_tmp_output, path_to_tmp_output_flip]) warped_moving = st.Stack.from_filename(path_to_tmp_output, extract_slices=False) warped_moving_flip = st.Stack.from_filename(path_to_tmp_output_flip, extract_slices=False) fixed = st.Stack.from_filename(args.fixed, args.fixed_mask) stacks = [warped_moving, warped_moving_flip] image_similarity_evaluator = ise.ImageSimilarityEvaluator( stacks=stacks, reference=fixed) image_similarity_evaluator.compute_similarities() similarities = image_similarity_evaluator.get_similarities() if similarities["NMI"][1] > similarities["NMI"][0]: ph.print_info("AP-flipped outcome better") # Convert RegAladin to SimpleITK transform cmd = "simplereg_transform -nreg2sitk %s %s" % ( path_to_transform_flip_regaladin, args.output) ph.execute_command(cmd, verbose=False) # Copy better outcome cmd = "cp -p %s %s" % (path_to_tmp_output_flip, path_to_tmp_output) ph.execute_command(cmd, verbose=False) else: ph.print_info("AP-flip does not improve outcome") if args.dir_input_mc is not None: transform_sitk = sitkh.read_transform_sitk(args.output, inverse=1) if args.dir_input_mc.endswith("/"): subdir_mc = args.dir_input_mc.split("/")[-2] else: subdir_mc = args.dir_input_mc.split("/")[-1] dir_output_mc = os.path.join(dir_output, subdir_mc) ph.create_directory(dir_output_mc, delete_files=True) pattern = REGEX_FILENAMES + "[.]tfm" p = re.compile(pattern) trafos = [t for t in os.listdir(args.dir_input_mc) if p.match(t)] for t in trafos: path_to_input_transform = os.path.join(args.dir_input_mc, t) path_to_output_transform = os.path.join(dir_output_mc, t) t_sitk = sitkh.read_transform_sitk(path_to_input_transform) t_sitk = sitkh.get_composite_sitk_affine_transform( transform_sitk, t_sitk) sitk.WriteTransform(t_sitk, path_to_output_transform) if args.verbose: ph.show_niftis([args.fixed, path_to_tmp_output]) elapsed_time_total = ph.stop_timing(time_start) # Summary ph.print_title("Summary") print("Computational Time: %s" % (elapsed_time_total)) return 0