def update_motion_correction(self, affine_transform_sitk): # Update rigid motion estimate current_rigid_motion_estimate = sitkh.get_composite_sitk_affine_transform( affine_transform_sitk, self._history_motion_corrections[-1]) self._history_motion_corrections.append(current_rigid_motion_estimate) # New affine transform of slice after rigid motion correction affine_transform = sitkh.get_composite_sitk_affine_transform( affine_transform_sitk, self._affine_transform_sitk) # Update affine transform of slice, i.e. change image origin and # direction in physical space self._update_affine_transform(affine_transform)
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 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
def get_inplane_corrupted_stack(stack, angle_z, center_2D, translation_2D, scale=1, intensity_scale=1, intensity_bias=0, debug=0, random=False): # Convert to 3D: translation_3D = np.zeros(3) translation_3D[0:-1] = translation_2D center_3D = np.zeros(3) center_3D[0:-1] = center_2D # Transform to align physical coordinate system with stack-coordinate # system affine_centering_sitk = sitk.AffineTransform(3) affine_centering_sitk.SetMatrix(stack.sitk.GetDirection()) affine_centering_sitk.SetTranslation(stack.sitk.GetOrigin()) # Corrupt first stack towards positive direction if random: angle_z_1 = -angle_z * np.random.rand(1)[0] else: angle_z_1 = -angle_z in_plane_motion_sitk = sitk.Euler3DTransform() in_plane_motion_sitk.SetRotation(0, 0, angle_z_1) in_plane_motion_sitk.SetCenter(center_3D) in_plane_motion_sitk.SetTranslation(translation_3D) motion_sitk = sitkh.get_composite_sitk_affine_transform( in_plane_motion_sitk, sitk.AffineTransform(affine_centering_sitk.GetInverse())) motion_sitk = sitkh.get_composite_sitk_affine_transform( affine_centering_sitk, motion_sitk) stack_corrupted_resampled_sitk = sitk.Resample(stack.sitk, motion_sitk, sitk.sitkLinear) stack_corrupted_resampled_sitk_mask = sitk.Resample( stack.sitk_mask, motion_sitk, sitk.sitkLinear) # Corrupt first stack towards negative direction if random: angle_z_2 = -angle_z * np.random.rand(1)[0] else: angle_z_2 = -angle_z in_plane_motion_2_sitk = sitk.Euler3DTransform() in_plane_motion_2_sitk.SetRotation(0, 0, angle_z_2) in_plane_motion_2_sitk.SetCenter(center_3D) in_plane_motion_2_sitk.SetTranslation(-translation_3D) motion_2_sitk = sitkh.get_composite_sitk_affine_transform( in_plane_motion_2_sitk, sitk.AffineTransform(affine_centering_sitk.GetInverse())) motion_2_sitk = sitkh.get_composite_sitk_affine_transform( affine_centering_sitk, motion_2_sitk) stack_corrupted_2_resampled_sitk = sitk.Resample(stack.sitk, motion_2_sitk, sitk.sitkLinear) stack_corrupted_2_resampled_sitk_mask = sitk.Resample( stack.sitk_mask, motion_2_sitk, sitk.sitkLinear) # Create stack based on those two corrupted stacks nda = sitk.GetArrayFromImage(stack_corrupted_resampled_sitk) nda_mask = sitk.GetArrayFromImage(stack_corrupted_resampled_sitk_mask) nda_neg = sitk.GetArrayFromImage(stack_corrupted_2_resampled_sitk) nda_neg_mask = sitk.GetArrayFromImage( stack_corrupted_2_resampled_sitk_mask) for i in range(0, stack.sitk.GetDepth(), 2): nda[i, :, :] = nda_neg[i, :, :] nda_mask[i, :, :] = nda_neg_mask[i, :, :] stack_corrupted_sitk = sitk.GetImageFromArray( (nda - intensity_bias) / intensity_scale) stack_corrupted_sitk_mask = sitk.GetImageFromArray(nda_mask) stack_corrupted_sitk.CopyInformation(stack.sitk) stack_corrupted_sitk_mask.CopyInformation(stack.sitk_mask) # Debug: Show corrupted stacks (before scaling) if debug: sitkh.show_sitk_image([ stack.sitk, stack_corrupted_resampled_sitk, stack_corrupted_2_resampled_sitk, stack_corrupted_sitk ], title=[ "original", "corrupted_1", "corrupted_2", "corrupted_final_from_1_and_2" ]) # Update in-plane scaling spacing = np.array(stack.sitk.GetSpacing()) spacing[0:-1] /= scale stack_corrupted_sitk.SetSpacing(spacing) stack_corrupted_sitk_mask.SetSpacing(spacing) # Create Stack object stack_corrupted = st.Stack.from_sitk_image(stack_corrupted_sitk, "stack_corrupted", stack_corrupted_sitk_mask) # Debug: Show corrupted stacks (after scaling) if debug: stack_corrupted_resampled_sitk = sitk.Resample(stack_corrupted.sitk, stack.sitk) sitkh.show_sitk_image([stack.sitk, stack_corrupted_resampled_sitk], title=["original", "corrupted"]) return stack_corrupted, motion_sitk, motion_2_sitk
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, nargs="+", help="Specify moving image to be warped to fixed space. " "If multiple images are provided, all images will be transformed " "uniformly according to the registration obtained for the first one.") input_parser.add_dir_output(required=True) input_parser.add_dir_input() input_parser.add_suffix_mask(default="_mask") input_parser.add_search_angle(default=180) input_parser.add_option( option_string="--transform-only", type=int, help="Turn on/off functionality to transform moving image(s) to fixed " "image only, i.e. no resampling to fixed image space", default=0) input_parser.add_option( option_string="--write-transform", type=int, help="Turn on/off functionality to write registration transform", default=0) input_parser.add_verbose(default=0) args = input_parser.parse_args() input_parser.print_arguments(args) use_reg_aladin_for_refinement = True # --------------------------------Read Data-------------------------------- ph.print_title("Read Data") data_reader = dr.MultipleImagesReader(args.moving, suffix_mask="_mask") data_reader.read_data() moving = data_reader.get_data() data_reader = dr.MultipleImagesReader([args.fixed], suffix_mask="_mask") data_reader.read_data() fixed = data_reader.get_data()[0] # -------------------Register Reconstruction to Template------------------- ph.print_title("Register Reconstruction to Template") # 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"] ] search_angles = (" ").join(search_angles) options_args = [] options_args.append(search_angles) # cost = "mutualinfo" # options_args.append("-searchcost %s -cost %s" % (cost, cost)) registration = regflirt.FLIRT( fixed=moving[0], moving=fixed, # use_fixed_mask=True, # use_moving_mask=True, # moving mask only seems to work for SB cases registration_type="Rigid", use_verbose=False, options=(" ").join(options_args), ) ph.print_info("Run Registration (FLIRT) ... ", newline=False) registration.run() print("done") transform_sitk = registration.get_registration_transform_sitk() if args.write_transform: path_to_transform = os.path.join(args.dir_output, "registration_transform_sitk.txt") sitk.WriteTransform(transform_sitk, path_to_transform) # Apply rigidly transform to align reconstruction (moving) with template # (fixed) for m in moving: m.update_motion_correction(transform_sitk) # 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 use_reg_aladin_for_refinement: registration = niftyreg.RegAladin( fixed=m, use_fixed_mask=True, moving=fixed, registration_type="Rigid", use_verbose=False, ) ph.print_info("Run Registration (RegAladin) ... ", newline=False) registration.run() print("done") transform2_sitk = registration.get_registration_transform_sitk() m.update_motion_correction(transform2_sitk) transform_sitk = sitkh.get_composite_sitk_affine_transform( transform2_sitk, transform_sitk) if args.transform_only: for m in moving: m.write(args.dir_output, write_mask=False) ph.exit() # Resample reconstruction (moving) to template space (fixed) warped_moving = [ m.get_resampled_stack(fixed.sitk, interpolator="Linear") for m in moving ] for wm in warped_moving: wm.set_filename(wm.get_filename() + "ResamplingToTemplateSpace") if args.verbose: sitkh.show_stacks([fixed, wm], segmentation=fixed) # Write resampled reconstruction (moving) wm.write(args.dir_output, write_mask=False) if args.dir_input is not None: data_reader = dr.ImageSlicesDirectoryReader( path_to_directory=args.dir_input, suffix_mask=args.suffix_mask) data_reader.read_data() stacks = data_reader.get_data() for i, stack in enumerate(stacks): stack.update_motion_correction(transform_sitk) ph.print_info("Stack %d/%d: All slice transforms updated" % (i + 1, len(stacks))) # Write transformed slices stack.write( os.path.join(args.dir_output, "motion_correction"), write_mask=True, write_slices=True, write_transforms=True, suffix_mask=args.suffix_mask, ) elapsed_time_total = ph.stop_timing(time_start) # Summary ph.print_title("Summary") print("Computational Time: %s" % (elapsed_time_total)) return 0