def get_parser(paramregmulti=None): # Initialize the parser if paramregmulti is None: step0 = Paramreg(step='0', type='im', algo='syn', metric='MI', iter='0', shrink='1', smooth='0', gradStep='0.5', slicewise='0', dof='Tx_Ty_Tz_Rx_Ry_Rz') # only used to put src into dest space step1 = Paramreg(step='1', type='im') paramregmulti = ParamregMultiStep([step0, step1]) parser = Parser(__file__) parser.usage.set_description('This program co-registers two 3D volumes. The deformation is non-rigid and is ' 'constrained along Z direction (i.e., axial plane). Hence, this function assumes ' 'that orientation of the destination image is axial (RPI). If you need to register ' 'two volumes with large deformations and/or different contrasts, it is recommended to ' 'input spinal cord segmentations (binary mask) in order to achieve maximum robustness.' ' The program outputs a warping field that can be used to register other images to the' ' destination image. To apply the warping field to another image, use ' 'sct_apply_transfo') parser.add_option(name="-i", type_value="file", description="Image source.", mandatory=True, example="src.nii.gz") parser.add_option(name="-d", type_value="file", description="Image destination.", mandatory=True, example="dest.nii.gz") parser.add_option(name="-iseg", type_value="file", description="Segmentation source.", mandatory=False, example="src_seg.nii.gz") parser.add_option(name="-dseg", type_value="file", description="Segmentation destination.", mandatory=False, example="dest_seg.nii.gz") parser.add_option(name="-ilabel", type_value="file", description="Labels source.", mandatory=False) parser.add_option(name="-dlabel", type_value="file", description="Labels destination.", mandatory=False) parser.add_option(name='-initwarp', type_value='file', description='Initial warping field to apply to the source image.', mandatory=False) parser.add_option(name='-initwarpinv', type_value='file', description='Initial inverse warping field to apply to the destination image (only use if you wish to generate the dest->src warping field).', mandatory=False) parser.add_option(name="-m", type_value="file", description="Mask that can be created with sct_create_mask to improve accuracy over region of interest. " "This mask will be used on the destination image.", mandatory=False, example="mask.nii.gz") parser.add_option(name="-o", type_value="file_output", description="Name of output file.", mandatory=False, example="src_reg.nii.gz") parser.add_option(name='-owarp', type_value="file_output", description="Name of output forward warping field.", mandatory=False) parser.add_option(name="-param", type_value=[[':'], 'str'], description="Parameters for registration. Separate arguments with \",\". Separate steps with \":\".\n" "step: <int> Step number (starts at 1, except for type=label).\n" "type: {im, seg, imseg, label} type of data used for registration. Use type=label only at step=0.\n" "algo: Default=" + paramregmulti.steps['1'].algo + "\n" " translation: translation in X-Y plane (2dof)\n" " rigid: translation + rotation in X-Y plane (4dof)\n" " affine: translation + rotation + scaling in X-Y plane (6dof)\n" " syn: non-linear symmetric normalization\n" " bsplinesyn: syn regularized with b-splines\n" " slicereg: regularized translations (see: goo.gl/Sj3ZeU)\n" " centermass: slicewise center of mass alignment (seg only).\n" " centermassrot: slicewise center of mass and rotation alignment using method specified in 'rot_method'\n" " columnwise: R-L scaling followed by A-P columnwise alignment (seg only).\n" "slicewise: <int> Slice-by-slice 2d transformation. Default=" + paramregmulti.steps['1'].slicewise + "\n" "metric: {CC,MI,MeanSquares}. Default=" + paramregmulti.steps['1'].metric + "\n" "iter: <int> Number of iterations. Default=" + paramregmulti.steps['1'].iter + "\n" "shrink: <int> Shrink factor (only for syn/bsplinesyn). Default=" + paramregmulti.steps['1'].shrink + "\n" "smooth: <int> Smooth factor (in mm). Note: if algo={centermassrot,columnwise} the smoothing kernel is: SxSx0. Otherwise it is SxSxS. Default=" + paramregmulti.steps['1'].smooth + "\n" "laplacian: <int> Laplacian filter. Default=" + paramregmulti.steps['1'].laplacian + "\n" "gradStep: <float> Gradient step. Default=" + paramregmulti.steps['1'].gradStep + "\n" "deformation: ?x?x?: Restrict deformation (for ANTs algo). Replace ? by 0 (no deformation) or 1 (deformation). Default=" + paramregmulti.steps['1'].deformation + "\n" "init: Initial translation alignment based on:\n" " geometric: Geometric center of images\n" " centermass: Center of mass of images\n" " origin: Physical origin of images\n" "poly: <int> Polynomial degree of regularization (only for algo=slicereg). Default=" + paramregmulti.steps['1'].poly + "\n" "filter_size: <float> Filter size for regularization (only for algo=centermassrot). Default=" + str(paramregmulti.steps['1'].filter_size) + "\n" "smoothWarpXY: <int> Smooth XY warping field (only for algo=columnwize). Default=" + paramregmulti.steps['1'].smoothWarpXY + "\n" "pca_eigenratio_th: <int> Min ratio between the two eigenvalues for PCA-based angular adjustment (only for algo=centermassrot and rot_method=pca). Default=" + paramregmulti.steps['1'].pca_eigenratio_th + "\n" "dof: <str> Degree of freedom for type=label. Separate with '_'. Default=" + paramregmulti.steps['0'].dof + "\n" + paramregmulti.steps['1'].rot_method + "\n" "rot_method {pca, hog, pcahog}: rotation method to be used with algo=centermassrot. pca: approximate cord segmentation by an ellipse and finds it orientation using PCA's eigenvectors; hog: finds the orientation using the symmetry of the image; pcahog: tries method pca and if it fails, uses method hog. If using hog or pcahog, type should be set to imseg.", mandatory=False, example="step=1,type=seg,algo=slicereg,metric=MeanSquares:step=2,type=im,algo=syn,metric=MI,iter=5,shrink=2") parser.add_option(name="-identity", type_value="multiple_choice", description="just put source into destination (no optimization).", mandatory=False, default_value='0', example=['0', '1']) parser.add_option(name="-z", type_value="int", description="""size of z-padding to enable deformation at edges when using SyN.""", mandatory=False, default_value=Param().padding) parser.add_option(name="-x", type_value="multiple_choice", description="""Final interpolation.""", mandatory=False, default_value='linear', example=['nn', 'linear', 'spline']) parser.add_option(name="-ofolder", type_value="folder_creation", description="Output folder", mandatory=False, example='reg_results/') parser.add_option(name='-qc', type_value='folder_creation', description='The path where the quality control generated content will be saved', default_value=None) parser.add_option(name='-qc-dataset', type_value='str', description='If provided, this string will be mentioned in the QC report as the dataset the process was run on', ) parser.add_option(name='-qc-subject', type_value='str', description='If provided, this string will be mentioned in the QC report as the subject the process was run on', ) parser.add_option(name="-r", type_value="multiple_choice", description="""Remove temporary files.""", mandatory=False, default_value='1', example=['0', '1']) parser.add_option(name="-v", type_value="multiple_choice", description="""Verbose.""", mandatory=False, default_value='1', example=['0', '1', '2']) return parser
def main(args=None): # initializations param = Param() # check user arguments if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) fname_data = arguments['-i'] fname_seg = arguments['-s'] if '-l' in arguments: fname_landmarks = arguments['-l'] label_type = 'body' elif '-ldisc' in arguments: fname_landmarks = arguments['-ldisc'] label_type = 'disc' elif '-lspinal' in arguments: fname_landmarks = arguments['-lspinal'] label_type = 'spinal' else: sct.printv('ERROR: Labels should be provided.', 1, 'error') if '-ofolder' in arguments: path_output = arguments['-ofolder'] else: path_output = '' param.path_qc = arguments.get("-qc", None) path_template = arguments['-t'] contrast_template = arguments['-c'] ref = arguments['-ref'] param.remove_temp_files = int(arguments.get('-r')) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level param.verbose = verbose # TODO: not clean, unify verbose or param.verbose in code, but not both param_centerline = ParamCenterline( algo_fitting=arguments['-centerline-algo'], smooth=arguments['-centerline-smooth']) # registration parameters if '-param' in arguments: # reset parameters but keep step=0 (might be overwritten if user specified step=0) paramregmulti = ParamregMultiStep([step0]) if ref == 'subject': paramregmulti.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz' # add user parameters for paramStep in arguments['-param']: paramregmulti.addStep(paramStep) else: paramregmulti = ParamregMultiStep([step0, step1, step2]) # if ref=subject, initialize registration using different affine parameters if ref == 'subject': paramregmulti.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz' # initialize other parameters zsubsample = param.zsubsample # retrieve template file names if label_type == 'spinal': file_template_labeling = get_file_label( os.path.join(path_template, 'template'), id_label=14) # label = point-wise spinal level labels else: file_template_labeling = get_file_label( os.path.join(path_template, 'template'), id_label=7 ) # label = spinal cord mask with discrete vertebral levels id_label_dct = {'T1': 0, 'T2': 1, 'T2S': 2} file_template = get_file_label( os.path.join(path_template, 'template'), id_label=id_label_dct[ contrast_template.upper()]) # label = *-weighted template file_template_seg = get_file_label( os.path.join(path_template, 'template'), id_label=3) # label = spinal cord mask (binary) # start timer start_time = time.time() # get fname of the template + template objects fname_template = os.path.join(path_template, 'template', file_template) fname_template_labeling = os.path.join(path_template, 'template', file_template_labeling) fname_template_seg = os.path.join(path_template, 'template', file_template_seg) fname_template_disc_labeling = os.path.join(path_template, 'template', 'PAM50_label_disc.nii.gz') # check file existence # TODO: no need to do that! sct.printv('\nCheck template files...') sct.check_file_exist(fname_template, verbose) sct.check_file_exist(fname_template_labeling, verbose) sct.check_file_exist(fname_template_seg, verbose) path_data, file_data, ext_data = sct.extract_fname(fname_data) # sct.printv(arguments) sct.printv('\nCheck parameters:', verbose) sct.printv(' Data: ' + fname_data, verbose) sct.printv(' Landmarks: ' + fname_landmarks, verbose) sct.printv(' Segmentation: ' + fname_seg, verbose) sct.printv(' Path template: ' + path_template, verbose) sct.printv(' Remove temp files: ' + str(param.remove_temp_files), verbose) # check input labels labels = check_labels(fname_landmarks, label_type=label_type) level_alignment = False if len(labels) > 2 and label_type in ['disc', 'spinal']: level_alignment = True path_tmp = sct.tmp_create(basename="register_to_template", verbose=verbose) # set temporary file names ftmp_data = 'data.nii' ftmp_seg = 'seg.nii.gz' ftmp_label = 'label.nii.gz' ftmp_template = 'template.nii' ftmp_template_seg = 'template_seg.nii.gz' ftmp_template_label = 'template_label.nii.gz' # copy files to temporary folder sct.printv('\nCopying input data to tmp folder and convert to nii...', verbose) Image(fname_data).save(os.path.join(path_tmp, ftmp_data)) Image(fname_seg).save(os.path.join(path_tmp, ftmp_seg)) Image(fname_landmarks).save(os.path.join(path_tmp, ftmp_label)) Image(fname_template).save(os.path.join(path_tmp, ftmp_template)) Image(fname_template_seg).save(os.path.join(path_tmp, ftmp_template_seg)) Image(fname_template_labeling).save( os.path.join(path_tmp, ftmp_template_label)) if label_type == 'disc': Image(fname_template_disc_labeling).save( os.path.join(path_tmp, ftmp_template_label)) # go to tmp folder curdir = os.getcwd() os.chdir(path_tmp) # Generate labels from template vertebral labeling if label_type == 'body': sct.printv('\nGenerate labels from template vertebral labeling', verbose) ftmp_template_label_, ftmp_template_label = ftmp_template_label, sct.add_suffix( ftmp_template_label, "_body") sct_label_utils.main(args=[ '-i', ftmp_template_label_, '-vert-body', '0', '-o', ftmp_template_label ]) # check if provided labels are available in the template sct.printv('\nCheck if provided labels are available in the template', verbose) image_label_template = Image(ftmp_template_label) labels_template = image_label_template.getNonZeroCoordinates( sorting='value') if labels[-1].value > labels_template[-1].value: sct.printv( 'ERROR: Wrong landmarks input. Labels must have correspondence in template space. \nLabel max ' 'provided: ' + str(labels[-1].value) + '\nLabel max from template: ' + str(labels_template[-1].value), verbose, 'error') # if only one label is present, force affine transformation to be Tx,Ty,Tz only (no scaling) if len(labels) == 1: paramregmulti.steps['0'].dof = 'Tx_Ty_Tz' sct.printv( 'WARNING: Only one label is present. Forcing initial transformation to: ' + paramregmulti.steps['0'].dof, 1, 'warning') # Project labels onto the spinal cord centerline because later, an affine transformation is estimated between the # template's labels (centered in the cord) and the subject's labels (assumed to be centered in the cord). # If labels are not centered, mis-registration errors are observed (see issue #1826) ftmp_label = project_labels_on_spinalcord(ftmp_label, ftmp_seg, param_centerline) # binarize segmentation (in case it has values below 0 caused by manual editing) sct.printv('\nBinarize segmentation', verbose) ftmp_seg_, ftmp_seg = ftmp_seg, sct.add_suffix(ftmp_seg, "_bin") sct_maths.main(['-i', ftmp_seg_, '-bin', '0.5', '-o', ftmp_seg]) # Switch between modes: subject->template or template->subject if ref == 'template': # resample data to 1mm isotropic sct.printv('\nResample data to 1mm isotropic...', verbose) resample_file(ftmp_data, add_suffix(ftmp_data, '_1mm'), '1.0x1.0x1.0', 'mm', 'linear', verbose) ftmp_data = add_suffix(ftmp_data, '_1mm') resample_file(ftmp_seg, add_suffix(ftmp_seg, '_1mm'), '1.0x1.0x1.0', 'mm', 'linear', verbose) ftmp_seg = add_suffix(ftmp_seg, '_1mm') # N.B. resampling of labels is more complicated, because they are single-point labels, therefore resampling # with nearest neighbour can make them disappear. resample_labels(ftmp_label, ftmp_data, add_suffix(ftmp_label, '_1mm')) ftmp_label = add_suffix(ftmp_label, '_1mm') # Change orientation of input images to RPI sct.printv('\nChange orientation of input images to RPI...', verbose) ftmp_data = Image(ftmp_data).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_seg = Image(ftmp_seg).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_label = Image(ftmp_label).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_crop') if level_alignment: # cropping the segmentation based on the label coverage to ensure good registration with level alignment # See https://github.com/neuropoly/spinalcordtoolbox/pull/1669 for details image_labels = Image(ftmp_label) coordinates_labels = image_labels.getNonZeroCoordinates( sorting='z') nx, ny, nz, nt, px, py, pz, pt = image_labels.dim offset_crop = 10.0 * pz # cropping the image 10 mm above and below the highest and lowest label cropping_slices = [ coordinates_labels[0].z - offset_crop, coordinates_labels[-1].z + offset_crop ] # make sure that the cropping slices do not extend outside of the slice range (issue #1811) if cropping_slices[0] < 0: cropping_slices[0] = 0 if cropping_slices[1] > nz: cropping_slices[1] = nz msct_image.spatial_crop( Image(ftmp_seg_), dict(((2, np.int32(np.round(cropping_slices))), ))).save(ftmp_seg) else: # if we do not align the vertebral levels, we crop the segmentation from top to bottom im_seg_rpi = Image(ftmp_seg_) bottom = 0 for data in msct_image.SlicerOneAxis(im_seg_rpi, "IS"): if (data != 0).any(): break bottom += 1 top = im_seg_rpi.data.shape[2] for data in msct_image.SlicerOneAxis(im_seg_rpi, "SI"): if (data != 0).any(): break top -= 1 msct_image.spatial_crop(im_seg_rpi, dict( ((2, (bottom, top)), ))).save(ftmp_seg) # straighten segmentation sct.printv( '\nStraighten the spinal cord using centerline/segmentation...', verbose) # check if warp_curve2straight and warp_straight2curve already exist (i.e. no need to do it another time) fn_warp_curve2straight = os.path.join(curdir, "warp_curve2straight.nii.gz") fn_warp_straight2curve = os.path.join(curdir, "warp_straight2curve.nii.gz") fn_straight_ref = os.path.join(curdir, "straight_ref.nii.gz") cache_input_files = [ftmp_seg] if level_alignment: cache_input_files += [ ftmp_template_seg, ftmp_label, ftmp_template_label, ] cache_sig = sct.cache_signature(input_files=cache_input_files, ) cachefile = os.path.join(curdir, "straightening.cache") if sct.cache_valid( cachefile, cache_sig ) and os.path.isfile(fn_warp_curve2straight) and os.path.isfile( fn_warp_straight2curve) and os.path.isfile(fn_straight_ref): sct.printv( 'Reusing existing warping field which seems to be valid', verbose, 'warning') sct.copy(fn_warp_curve2straight, 'warp_curve2straight.nii.gz') sct.copy(fn_warp_straight2curve, 'warp_straight2curve.nii.gz') sct.copy(fn_straight_ref, 'straight_ref.nii.gz') # apply straightening sct_apply_transfo.main(args=[ '-i', ftmp_seg, '-w', 'warp_curve2straight.nii.gz', '-d', 'straight_ref.nii.gz', '-o', add_suffix(ftmp_seg, '_straight') ]) else: from spinalcordtoolbox.straightening import SpinalCordStraightener sc_straight = SpinalCordStraightener(ftmp_seg, ftmp_seg) sc_straight.param_centerline = param_centerline sc_straight.output_filename = add_suffix(ftmp_seg, '_straight') sc_straight.path_output = './' sc_straight.qc = '0' sc_straight.remove_temp_files = param.remove_temp_files sc_straight.verbose = verbose if level_alignment: sc_straight.centerline_reference_filename = ftmp_template_seg sc_straight.use_straight_reference = True sc_straight.discs_input_filename = ftmp_label sc_straight.discs_ref_filename = ftmp_template_label sc_straight.straighten() sct.cache_save(cachefile, cache_sig) # N.B. DO NOT UPDATE VARIABLE ftmp_seg BECAUSE TEMPORARY USED LATER # re-define warping field using non-cropped space (to avoid issue #367) sct_concat_transfo.main(args=[ '-w', 'warp_straight2curve.nii.gz', '-d', ftmp_data, '-o', 'warp_straight2curve.nii.gz' ]) if level_alignment: sct.copy('warp_curve2straight.nii.gz', 'warp_curve2straightAffine.nii.gz') else: # Label preparation: # -------------------------------------------------------------------------------- # Remove unused label on template. Keep only label present in the input label image sct.printv( '\nRemove unused label on template. Keep only label present in the input label image...', verbose) sct.run([ 'sct_label_utils', '-i', ftmp_template_label, '-o', ftmp_template_label, '-remove-reference', ftmp_label ]) # Dilating the input label so they can be straighten without losing them sct.printv('\nDilating input labels using 3vox ball radius') sct_maths.main([ '-i', ftmp_label, '-dilate', '3', '-o', add_suffix(ftmp_label, '_dilate') ]) ftmp_label = add_suffix(ftmp_label, '_dilate') # Apply straightening to labels sct.printv('\nApply straightening to labels...', verbose) sct_apply_transfo.main(args=[ '-i', ftmp_label, '-o', add_suffix(ftmp_label, '_straight'), '-d', add_suffix(ftmp_seg, '_straight'), '-w', 'warp_curve2straight.nii.gz', '-x', 'nn' ]) ftmp_label = add_suffix(ftmp_label, '_straight') # Compute rigid transformation straight landmarks --> template landmarks sct.printv('\nEstimate transformation for step #0...', verbose) try: register_landmarks(ftmp_label, ftmp_template_label, paramregmulti.steps['0'].dof, fname_affine='straight2templateAffine.txt', verbose=verbose) except RuntimeError: raise ( 'Input labels do not seem to be at the right place. Please check the position of the labels. ' 'See documentation for more details: https://www.slideshare.net/neuropoly/sct-course-20190121/42' ) # Concatenate transformations: curve --> straight --> affine sct.printv( '\nConcatenate transformations: curve --> straight --> affine...', verbose) sct_concat_transfo.main(args=[ '-w', ['warp_curve2straight.nii.gz', 'straight2templateAffine.txt'], '-d', 'template.nii', '-o', 'warp_curve2straightAffine.nii.gz' ]) # Apply transformation sct.printv('\nApply transformation...', verbose) sct_apply_transfo.main(args=[ '-i', ftmp_data, '-o', add_suffix(ftmp_data, '_straightAffine'), '-d', ftmp_template, '-w', 'warp_curve2straightAffine.nii.gz' ]) ftmp_data = add_suffix(ftmp_data, '_straightAffine') sct_apply_transfo.main(args=[ '-i', ftmp_seg, '-o', add_suffix(ftmp_seg, '_straightAffine'), '-d', ftmp_template, '-w', 'warp_curve2straightAffine.nii.gz', '-x', 'linear' ]) ftmp_seg = add_suffix(ftmp_seg, '_straightAffine') """ # Benjamin: Issue from Allan Martin, about the z=0 slice that is screwed up, caused by the affine transform. # Solution found: remove slices below and above landmarks to avoid rotation effects points_straight = [] for coord in landmark_template: points_straight.append(coord.z) min_point, max_point = int(np.round(np.min(points_straight))), int(np.round(np.max(points_straight))) ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_black') msct_image.spatial_crop(Image(ftmp_seg_), dict(((2, (min_point,max_point)),))).save(ftmp_seg) """ # open segmentation im = Image(ftmp_seg) im_new = msct_image.empty_like(im) # binarize im_new.data = im.data > 0.5 # find min-max of anat2template (for subsequent cropping) zmin_template, zmax_template = msct_image.find_zmin_zmax(im_new, threshold=0.5) # save binarized segmentation im_new.save(add_suffix(ftmp_seg, '_bin')) # unused? # crop template in z-direction (for faster processing) # TODO: refactor to use python module instead of doing i/o sct.printv('\nCrop data in template space (for faster processing)...', verbose) ftmp_template_, ftmp_template = ftmp_template, add_suffix( ftmp_template, '_crop') msct_image.spatial_crop(Image(ftmp_template_), dict( ((2, (zmin_template, zmax_template)), ))).save(ftmp_template) ftmp_template_seg_, ftmp_template_seg = ftmp_template_seg, add_suffix( ftmp_template_seg, '_crop') msct_image.spatial_crop( Image(ftmp_template_seg_), dict(((2, (zmin_template, zmax_template)), ))).save(ftmp_template_seg) ftmp_data_, ftmp_data = ftmp_data, add_suffix(ftmp_data, '_crop') msct_image.spatial_crop(Image(ftmp_data_), dict(((2, (zmin_template, zmax_template)), ))).save(ftmp_data) ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_crop') msct_image.spatial_crop(Image(ftmp_seg_), dict(((2, (zmin_template, zmax_template)), ))).save(ftmp_seg) # sub-sample in z-direction # TODO: refactor to use python module instead of doing i/o sct.printv('\nSub-sample in z-direction (for faster processing)...', verbose) sct.run([ 'sct_resample', '-i', ftmp_template, '-o', add_suffix(ftmp_template, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_template = add_suffix(ftmp_template, '_sub') sct.run([ 'sct_resample', '-i', ftmp_template_seg, '-o', add_suffix(ftmp_template_seg, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_template_seg = add_suffix(ftmp_template_seg, '_sub') sct.run([ 'sct_resample', '-i', ftmp_data, '-o', add_suffix(ftmp_data, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_data = add_suffix(ftmp_data, '_sub') sct.run([ 'sct_resample', '-i', ftmp_seg, '-o', add_suffix(ftmp_seg, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_seg = add_suffix(ftmp_seg, '_sub') # Registration straight spinal cord to template sct.printv('\nRegister straight spinal cord to template...', verbose) # TODO: find a way to input initwarp, corresponding to straightening warp # Set the angle of the template orientation to 0 (destination image) for key in list(paramregmulti.steps.keys()): paramregmulti.steps[key].rot_dest = 0 fname_src2dest, fname_dest2src, warp_forward, warp_inverse = register_wrapper( ftmp_data, ftmp_template, param, paramregmulti, fname_src_seg=ftmp_seg, fname_dest_seg=ftmp_template_seg, same_space=True) # Concatenate transformations: anat --> template sct.printv('\nConcatenate transformations: anat --> template...', verbose) sct_concat_transfo.main(args=[ '-w', ['warp_curve2straightAffine.nii.gz', warp_forward], '-d', 'template.nii', '-o', 'warp_anat2template.nii.gz' ]) # Concatenate transformations: template --> anat sct.printv('\nConcatenate transformations: template --> anat...', verbose) # TODO: make sure the commented code below is consistent with the new implementation # warp_inverse.reverse() if level_alignment: sct_concat_transfo.main(args=[ '-w', [warp_inverse, 'warp_straight2curve.nii.gz'], '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz' ]) else: sct_concat_transfo.main(args=[ '-w', [ warp_inverse, 'straight2templateAffine.txt', 'warp_straight2curve.nii.gz' ], '-winv', ['straight2templateAffine.txt'], '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz' ]) # register template->subject elif ref == 'subject': # Change orientation of input images to RPI sct.printv('\nChange orientation of input images to RPI...', verbose) ftmp_data = Image(ftmp_data).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_seg = Image(ftmp_seg).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_label = Image(ftmp_label).change_orientation( "RPI", generate_path=True).save().absolutepath # Remove unused label on template. Keep only label present in the input label image sct.printv( '\nRemove unused label on template. Keep only label present in the input label image...', verbose) sct.run([ 'sct_label_utils', '-i', ftmp_template_label, '-o', ftmp_template_label, '-remove-reference', ftmp_label ]) # Add one label because at least 3 orthogonal labels are required to estimate an affine transformation. This # new label is added at the level of the upper most label (lowest value), at 1cm to the right. for i_file in [ftmp_label, ftmp_template_label]: im_label = Image(i_file) coord_label = im_label.getCoordinatesAveragedByValue( ) # N.B. landmarks are sorted by value # Create new label from copy import deepcopy new_label = deepcopy(coord_label[0]) # move it 5mm to the left (orientation is RAS) nx, ny, nz, nt, px, py, pz, pt = im_label.dim new_label.x = np.round(coord_label[0].x + 5.0 / px) # assign value 99 new_label.value = 99 # Add to existing image im_label.data[int(new_label.x), int(new_label.y), int(new_label.z)] = new_label.value # Overwrite label file # im_label.absolutepath = 'label_rpi_modif.nii.gz' im_label.save() # Set the angle of the template orientation to 0 (source image) for key in list(paramregmulti.steps.keys()): paramregmulti.steps[key].rot_src = 0 fname_src2dest, fname_dest2src, warp_forward, warp_inverse = register_wrapper( ftmp_template, ftmp_data, param, paramregmulti, fname_src_seg=ftmp_template_seg, fname_dest_seg=ftmp_seg, fname_src_label=ftmp_template_label, fname_dest_label=ftmp_label, same_space=False) # Renaming for code compatibility os.rename(warp_forward, 'warp_template2anat.nii.gz') os.rename(warp_inverse, 'warp_anat2template.nii.gz') # Apply warping fields to anat and template sct.run([ 'sct_apply_transfo', '-i', 'template.nii', '-o', 'template2anat.nii.gz', '-d', 'data.nii', '-w', 'warp_template2anat.nii.gz', '-crop', '0' ], verbose) sct.run([ 'sct_apply_transfo', '-i', 'data.nii', '-o', 'anat2template.nii.gz', '-d', 'template.nii', '-w', 'warp_anat2template.nii.gz', '-crop', '0' ], verbose) # come back os.chdir(curdir) # Generate output files sct.printv('\nGenerate output files...', verbose) fname_template2anat = os.path.join(path_output, 'template2anat' + ext_data) fname_anat2template = os.path.join(path_output, 'anat2template' + ext_data) sct.generate_output_file( os.path.join(path_tmp, "warp_template2anat.nii.gz"), os.path.join(path_output, "warp_template2anat.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "warp_anat2template.nii.gz"), os.path.join(path_output, "warp_anat2template.nii.gz"), verbose) sct.generate_output_file(os.path.join(path_tmp, "template2anat.nii.gz"), fname_template2anat, verbose) sct.generate_output_file(os.path.join(path_tmp, "anat2template.nii.gz"), fname_anat2template, verbose) if ref == 'template': # copy straightening files in case subsequent SCT functions need them sct.generate_output_file( os.path.join(path_tmp, "warp_curve2straight.nii.gz"), os.path.join(path_output, "warp_curve2straight.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "warp_straight2curve.nii.gz"), os.path.join(path_output, "warp_straight2curve.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "straight_ref.nii.gz"), os.path.join(path_output, "straight_ref.nii.gz"), verbose) # Delete temporary files if param.remove_temp_files: sct.printv('\nDelete temporary files...', verbose) sct.rmtree(path_tmp, verbose=verbose) # display elapsed time elapsed_time = time.time() - start_time sct.printv( '\nFinished! Elapsed time: ' + str(int(np.round(elapsed_time))) + 's', verbose) qc_dataset = arguments.get("-qc-dataset", None) qc_subject = arguments.get("-qc-subject", None) if param.path_qc is not None: generate_qc(fname_data, fname_in2=fname_template2anat, fname_seg=fname_seg, args=args, path_qc=os.path.abspath(param.path_qc), dataset=qc_dataset, subject=qc_subject, process='sct_register_to_template') sct.display_viewer_syntax([fname_data, fname_template2anat], verbose=verbose) sct.display_viewer_syntax([fname_template, fname_anat2template], verbose=verbose)
def main(args=None): if args is None: args = sys.argv[1:] # initialize parameters param = Param() # Initialization fname_output = '' path_out = '' fname_src_seg = '' fname_dest_seg = '' fname_src_label = '' fname_dest_label = '' generate_warpinv = 1 start_time = time.time() # get default registration parameters # step1 = Paramreg(step='1', type='im', algo='syn', metric='MI', iter='5', shrink='1', smooth='0', gradStep='0.5') step0 = Paramreg(step='0', type='im', algo='syn', metric='MI', iter='0', shrink='1', smooth='0', gradStep='0.5', slicewise='0', dof='Tx_Ty_Tz_Rx_Ry_Rz') # only used to put src into dest space step1 = Paramreg(step='1', type='im') paramregmulti = ParamregMultiStep([step0, step1]) parser = get_parser(paramregmulti=paramregmulti) arguments = parser.parse(args) # get arguments fname_src = arguments['-i'] fname_dest = arguments['-d'] if '-iseg' in arguments: fname_src_seg = arguments['-iseg'] if '-dseg' in arguments: fname_dest_seg = arguments['-dseg'] if '-ilabel' in arguments: fname_src_label = arguments['-ilabel'] if '-dlabel' in arguments: fname_dest_label = arguments['-dlabel'] if '-o' in arguments: fname_output = arguments['-o'] if '-ofolder' in arguments: path_out = arguments['-ofolder'] if '-owarp' in arguments: fname_output_warp = arguments['-owarp'] else: fname_output_warp = '' if '-initwarp' in arguments: fname_initwarp = os.path.abspath(arguments['-initwarp']) else: fname_initwarp = '' if '-initwarpinv' in arguments: fname_initwarpinv = os.path.abspath(arguments['-initwarpinv']) else: fname_initwarpinv = '' if '-m' in arguments: fname_mask = arguments['-m'] else: fname_mask = '' padding = arguments['-z'] if "-param" in arguments: paramregmulti_user = arguments['-param'] # update registration parameters for paramStep in paramregmulti_user: paramregmulti.addStep(paramStep) path_qc = arguments.get("-qc", None) qc_dataset = arguments.get("-qc-dataset", None) qc_subject = arguments.get("-qc-subject", None) identity = int(arguments['-identity']) interp = arguments['-x'] remove_temp_files = int(arguments['-r']) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level # sct.printv(arguments) sct.printv('\nInput parameters:') sct.printv(' Source .............. ' + fname_src) sct.printv(' Destination ......... ' + fname_dest) sct.printv(' Init transfo ........ ' + fname_initwarp) sct.printv(' Mask ................ ' + fname_mask) sct.printv(' Output name ......... ' + fname_output) # sct.printv(' Algorithm ........... '+paramregmulti.algo) # sct.printv(' Number of iterations '+paramregmulti.iter) # sct.printv(' Metric .............. '+paramregmulti.metric) sct.printv(' Remove temp files ... ' + str(remove_temp_files)) sct.printv(' Verbose ............. ' + str(verbose)) # update param param.verbose = verbose param.padding = padding param.fname_mask = fname_mask param.remove_temp_files = remove_temp_files # Get if input is 3D sct.printv('\nCheck if input data are 3D...', verbose) sct.check_dim(fname_src, dim_lst=[3]) sct.check_dim(fname_dest, dim_lst=[3]) # Check if user selected type=seg, but did not input segmentation data if 'paramregmulti_user' in locals(): if True in ['type=seg' in paramregmulti_user[i] for i in range(len(paramregmulti_user))]: if fname_src_seg == '' or fname_dest_seg == '': sct.printv('\nERROR: if you select type=seg you must specify -iseg and -dseg flags.\n', 1, 'error') # Put source into destination space using header (no estimation -- purely based on header) # TODO: Check if necessary to do that # TODO: use that as step=0 # sct.printv('\nPut source into destination space using header...', verbose) # sct.run('isct_antsRegistration -d 3 -t Translation[0] -m MI[dest_pad.nii,src.nii,1,16] -c 0 -f 1 -s 0 -o # [regAffine,src_regAffine.nii] -n BSpline[3]', verbose) # if segmentation, also do it for seg fname_src2dest, fname_dest2src, _, _ = \ register_wrapper(fname_src, fname_dest, param, paramregmulti, fname_src_seg=fname_src_seg, fname_dest_seg=fname_dest_seg, fname_src_label=fname_src_label, fname_dest_label=fname_dest_label, fname_mask=fname_mask, fname_initwarp=fname_initwarp, fname_initwarpinv=fname_initwarpinv, identity=identity, interp=interp, fname_output=fname_output, fname_output_warp=fname_output_warp, path_out=path_out) # display elapsed time elapsed_time = time.time() - start_time sct.printv('\nFinished! Elapsed time: ' + str(int(np.round(elapsed_time))) + 's', verbose) if path_qc is not None: if fname_dest_seg: generate_qc(fname_src2dest, fname_in2=fname_dest, fname_seg=fname_dest_seg, args=args, path_qc=os.path.abspath(path_qc), dataset=qc_dataset, subject=qc_subject, process='sct_register_multimodal') else: sct.printv('WARNING: Cannot generate QC because it requires destination segmentation.', 1, 'warning') if generate_warpinv: sct.display_viewer_syntax([fname_src, fname_dest2src], verbose=verbose) sct.display_viewer_syntax([fname_dest, fname_src2dest], verbose=verbose)
# Note: step0 is used as pre-registration step0 = Paramreg( step='0', type='label', dof='Tx_Ty_Tz_Sz' ) # if ref=template, we only need translations and z-scaling because the cord is already straight step1 = Paramreg(step='1', type='imseg', algo='centermassrot', rot_method='pcahog') step2 = Paramreg(step='2', type='seg', algo='bsplinesyn', metric='MeanSquares', iter='3', smooth='1', slicewise='0') paramregmulti = ParamregMultiStep([step0, step1, step2]) # PARSER # ========================================================================================== def get_parser(): param = Param() parser = Parser(__file__) parser.usage.set_description( 'Register an anatomical image to the spinal cord MRI template (default: PAM50).\n\n' 'The registration process includes three main registration steps:\n' '1. straightening of the image using the spinal cord segmentation (see sct_straighten_spinalcord for details);\n' '2. vertebral alignment between the image and the template, using labels along the spine;\n' '3. iterative slice-wise non-linear registration (see sct_register_multimodal for details)\n\n' 'To register a subject to the template, try the default command:\n' 'sct_register_to_template -i data.nii.gz -s data_seg.nii.gz -l data_labels.nii.gz\n\n'