Ejemplo n.º 1
0
def test_crop(fake_3dimage_sct):

    im_src = fake_3dimage_sct.copy()

    crop_spec = dict(((0, (1, 3)), (1, (2, 4)), (2, (3, 5))))
    print(crop_spec)

    im_dst = msct_image.spatial_crop(im_src, crop_spec)

    print("Check shape")
    assert im_dst.data.shape == (3, 3, 3)
    print("Check world pos")
    aff_src = im_src.header.get_best_affine()
    aff_dst = im_dst.header.get_best_affine()
    pos_src = np.matmul(aff_src, np.array([[1, 2, 3, 1]]).T)
    pos_dst = np.matmul(aff_dst, np.array([[0, 0, 0, 1]]).T)
    assert (pos_src == pos_dst).all()
Ejemplo n.º 2
0
def test_crop(fake_3dimage_sct):


    im_src = fake_3dimage_sct.copy()

    crop_spec = dict(((0, (1,3)),(1, (2,4)),(2, (3,5))))
    print(crop_spec)

    im_dst = msct_image.spatial_crop(im_src, crop_spec)

    print("Check shape")
    assert im_dst.data.shape == (3,3,3)
    print("Check world pos")
    aff_src = im_src.header.get_best_affine()
    aff_dst = im_dst.header.get_best_affine()
    pos_src = np.matmul(aff_src, np.array([[1,2,3,1]]).T)
    pos_dst = np.matmul(aff_dst, np.array([[0,0,0,1]]).T)
    assert (pos_src == pos_dst).all()
Ejemplo n.º 3
0
def register(src, dest, paramreg, param, i_step_str):
    # initiate default parameters of antsRegistration transformation
    ants_registration_params = {
        'rigid': '',
        'affine': '',
        'compositeaffine': '',
        'similarity': '',
        'translation': '',
        'bspline': ',10',
        'gaussiandisplacementfield': ',3,0',
        'bsplinedisplacementfield': ',5,10',
        'syn': ',3,0',
        'bsplinesyn': ',1,3'
    }
    output = ''  # default output if problem

    # display arguments
    sct.printv('Registration parameters:', param.verbose)
    sct.printv('  type ........... ' + paramreg.steps[i_step_str].type,
               param.verbose)
    sct.printv('  algo ........... ' + paramreg.steps[i_step_str].algo,
               param.verbose)
    sct.printv('  slicewise ...... ' + paramreg.steps[i_step_str].slicewise,
               param.verbose)
    sct.printv('  metric ......... ' + paramreg.steps[i_step_str].metric,
               param.verbose)
    sct.printv('  iter ........... ' + paramreg.steps[i_step_str].iter,
               param.verbose)
    sct.printv('  smooth ......... ' + paramreg.steps[i_step_str].smooth,
               param.verbose)
    sct.printv('  laplacian ...... ' + paramreg.steps[i_step_str].laplacian,
               param.verbose)
    sct.printv('  shrink ......... ' + paramreg.steps[i_step_str].shrink,
               param.verbose)
    sct.printv('  gradStep ....... ' + paramreg.steps[i_step_str].gradStep,
               param.verbose)
    sct.printv('  deformation .... ' + paramreg.steps[i_step_str].deformation,
               param.verbose)
    sct.printv('  init ........... ' + paramreg.steps[i_step_str].init,
               param.verbose)
    sct.printv('  poly ........... ' + paramreg.steps[i_step_str].poly,
               param.verbose)
    sct.printv('  dof ............ ' + paramreg.steps[i_step_str].dof,
               param.verbose)
    sct.printv('  smoothWarpXY ... ' + paramreg.steps[i_step_str].smoothWarpXY,
               param.verbose)

    # set metricSize
    if paramreg.steps[i_step_str].metric == 'MI':
        metricSize = '32'  # corresponds to number of bins
    else:
        metricSize = '4'  # corresponds to radius (for CC, MeanSquares...)

    # set masking
    if param.fname_mask:
        fname_mask = 'mask.nii.gz'
        masking = ['-x', 'mask.nii.gz']
    else:
        fname_mask = ''
        masking = []

    if paramreg.steps[i_step_str].algo == 'slicereg':
        # check if user used type=label
        if paramreg.steps[i_step_str].type == 'label':
            sct.printv(
                '\nERROR: this algo is not compatible with type=label. Please use type=im or type=seg',
                1, 'error')
        else:
            # Find the min (and max) z-slice index below which (and above which) slices only have voxels below a given
            # threshold.
            list_fname = [src, dest]
            if not masking == []:
                list_fname.append(fname_mask)
            zmin_global, zmax_global = 0, 99999  # this is assuming that typical image has less slice than 99999
            for fname in list_fname:
                im = Image(fname)
                zmin, zmax = msct_image.find_zmin_zmax(im, threshold=0.1)
                if zmin > zmin_global:
                    zmin_global = zmin
                if zmax < zmax_global:
                    zmax_global = zmax
            # crop images (see issue #293)
            src_crop = sct.add_suffix(src, '_crop')
            msct_image.spatial_crop(Image(src),
                                    dict(
                                        ((2, (zmin_global,
                                              zmax_global)), ))).save(src_crop)
            dest_crop = sct.add_suffix(dest, '_crop')
            msct_image.spatial_crop(Image(dest),
                                    dict(((2,
                                           (zmin_global,
                                            zmax_global)), ))).save(dest_crop)
            # update variables
            src = src_crop
            dest = dest_crop
            scr_regStep = sct.add_suffix(src, '_regStep' + i_step_str)
            # estimate transfo
            # TODO fixup isct_ants* parsers
            cmd = [
                'isct_antsSliceRegularizedRegistration',
                '-t',
                'Translation[' + paramreg.steps[i_step_str].gradStep + ']',
                '-m',
                paramreg.steps[i_step_str].metric + '[' + dest + ',' + src +
                ',1,' + metricSize + ',Regular,0.2]',
                '-p',
                paramreg.steps[i_step_str].poly,
                '-i',
                paramreg.steps[i_step_str].iter,
                '-f',
                paramreg.steps[i_step_str].shrink,
                '-s',
                paramreg.steps[i_step_str].smooth,
                '-v',
                '1',  # verbose (verbose=2 does not exist, so we force it to 1)
                '-o',
                '[step' + i_step_str + ',' + scr_regStep +
                ']',  # here the warp name is stage10 because
                # antsSliceReg add "Warp"
            ] + masking
            warp_forward_out = 'step' + i_step_str + 'Warp.nii.gz'
            warp_inverse_out = 'step' + i_step_str + 'InverseWarp.nii.gz'
            # run command
            status, output = sct.run(cmd, param.verbose)

    # ANTS 3d
    elif paramreg.steps[i_step_str].algo.lower() in ants_registration_params \
            and paramreg.steps[i_step_str].slicewise == '0':
        # make sure type!=label. If type==label, this will be addressed later in the code.
        if not paramreg.steps[i_step_str].type == 'label':
            # Pad the destination image (because ants doesn't deform the extremities)
            # N.B. no need to pad if iter = 0
            if not paramreg.steps[i_step_str].iter == '0':
                dest_pad = sct.add_suffix(dest, '_pad')
                sct.run([
                    'sct_image', '-i', dest, '-o', dest_pad, '-pad',
                    '0,0,' + str(param.padding)
                ])
                dest = dest_pad
            # apply Laplacian filter
            if not paramreg.steps[i_step_str].laplacian == '0':
                sct.printv('\nApply Laplacian filter', param.verbose)
                sct.run([
                    'sct_maths', '-i', src, '-laplacian',
                    paramreg.steps[i_step_str].laplacian + ',' +
                    paramreg.steps[i_step_str].laplacian + ',0', '-o',
                    sct.add_suffix(src, '_laplacian')
                ])
                sct.run([
                    'sct_maths', '-i', dest, '-laplacian',
                    paramreg.steps[i_step_str].laplacian + ',' +
                    paramreg.steps[i_step_str].laplacian + ',0', '-o',
                    sct.add_suffix(dest, '_laplacian')
                ])
                src = sct.add_suffix(src, '_laplacian')
                dest = sct.add_suffix(dest, '_laplacian')
            # Estimate transformation
            sct.printv('\nEstimate transformation', param.verbose)
            scr_regStep = sct.add_suffix(src, '_regStep' + i_step_str)
            # TODO fixup isct_ants* parsers
            cmd = [
                'isct_antsRegistration',
                '--dimensionality',
                '3',
                '--transform',
                paramreg.steps[i_step_str].algo + '[' +
                paramreg.steps[i_step_str].gradStep + ants_registration_params[
                    paramreg.steps[i_step_str].algo.lower()] + ']',
                '--metric',
                paramreg.steps[i_step_str].metric + '[' + dest + ',' + src +
                ',1,' + metricSize + ']',
                '--convergence',
                paramreg.steps[i_step_str].iter,
                '--shrink-factors',
                paramreg.steps[i_step_str].shrink,
                '--smoothing-sigmas',
                paramreg.steps[i_step_str].smooth + 'mm',
                '--restrict-deformation',
                paramreg.steps[i_step_str].deformation,
                '--output',
                '[step' + i_step_str + ',' + scr_regStep + ']',
                '--interpolation',
                'BSpline[3]',
                '--verbose',
                '1',
            ] + masking
            # add init translation
            if not paramreg.steps[i_step_str].init == '':
                init_dict = {
                    'geometric': '0',
                    'centermass': '1',
                    'origin': '2'
                }
                cmd += [
                    '-r', '[' + dest + ',' + src + ',' +
                    init_dict[paramreg.steps[i_step_str].init] + ']'
                ]
            # run command
            status, output = sct.run(cmd, param.verbose)
            # get appropriate file name for transformation
            if paramreg.steps[i_step_str].algo in [
                    'rigid', 'affine', 'translation'
            ]:
                warp_forward_out = 'step' + i_step_str + '0GenericAffine.mat'
                warp_inverse_out = '-step' + i_step_str + '0GenericAffine.mat'
            else:
                warp_forward_out = 'step' + i_step_str + '0Warp.nii.gz'
                warp_inverse_out = 'step' + i_step_str + '0InverseWarp.nii.gz'

    # ANTS 2d
    elif paramreg.steps[i_step_str].algo.lower() in ants_registration_params \
            and paramreg.steps[i_step_str].slicewise == '1':
        # make sure type!=label. If type==label, this will be addressed later in the code.
        if not paramreg.steps[i_step_str].type == 'label':
            from msct_register import register_slicewise
            # if shrink!=1, force it to be 1 (otherwise, it generates a wrong 3d warping field). TODO: fix that!
            if not paramreg.steps[i_step_str].shrink == '1':
                sct.printv(
                    '\nWARNING: when using slicewise with SyN or BSplineSyN, shrink factor needs to be one. '
                    'Forcing shrink=1.', 1, 'warning')
                paramreg.steps[i_step_str].shrink = '1'
            warp_forward_out = 'step' + i_step_str + 'Warp.nii.gz'
            warp_inverse_out = 'step' + i_step_str + 'InverseWarp.nii.gz'
            register_slicewise(
                src,
                dest,
                paramreg=paramreg.steps[i_step_str],
                fname_mask=fname_mask,
                warp_forward_out=warp_forward_out,
                warp_inverse_out=warp_inverse_out,
                ants_registration_params=ants_registration_params,
                remove_temp_files=param.remove_temp_files,
                verbose=param.verbose)

    # slice-wise transfo
    elif paramreg.steps[i_step_str].algo in [
            'centermass', 'centermassrot', 'columnwise'
    ]:
        # if type=im, sends warning
        if paramreg.steps[i_step_str].type == 'im':
            sct.printv(
                '\nWARNING: algo ' + paramreg.steps[i_step_str].algo +
                ' should be used with type=seg.\n', 1, 'warning')
        # if type=label, exit with error
        elif paramreg.steps[i_step_str].type == 'label':
            sct.printv(
                '\nERROR: this algo is not compatible with type=label. Please use type=im or type=seg',
                1, 'error')
        # check if user provided a mask-- if so, inform it will be ignored
        if not fname_mask == '':
            sct.printv(
                '\nWARNING: algo ' + paramreg.steps[i_step_str].algo +
                ' will ignore the provided mask.\n', 1, 'warning')
        # smooth data
        if not paramreg.steps[i_step_str].smooth == '0':
            sct.printv('\nSmooth data', param.verbose)
            sct.run([
                'sct_maths', '-i', src, '-smooth',
                paramreg.steps[i_step_str].smooth + ',' +
                paramreg.steps[i_step_str].smooth + ',0', '-o',
                sct.add_suffix(src, '_smooth')
            ])
            sct.run([
                'sct_maths', '-i', dest, '-smooth',
                paramreg.steps[i_step_str].smooth + ',' +
                paramreg.steps[i_step_str].smooth + ',0', '-o',
                sct.add_suffix(dest, '_smooth')
            ])
            src = sct.add_suffix(src, '_smooth')
            dest = sct.add_suffix(dest, '_smooth')
        from msct_register import register_slicewise
        warp_forward_out = 'step' + i_step_str + 'Warp.nii.gz'
        warp_inverse_out = 'step' + i_step_str + 'InverseWarp.nii.gz'
        register_slicewise(src,
                           dest,
                           paramreg=paramreg.steps[i_step_str],
                           fname_mask=fname_mask,
                           warp_forward_out=warp_forward_out,
                           warp_inverse_out=warp_inverse_out,
                           ants_registration_params=ants_registration_params,
                           remove_temp_files=param.remove_temp_files,
                           verbose=param.verbose)

    else:
        sct.printv(
            '\nERROR: algo ' + paramreg.steps[i_step_str].algo +
            ' does not exist. Exit program\n', 1, 'error')

    # landmark-based registration
    if paramreg.steps[i_step_str].type in ['label']:
        # check if user specified ilabel and dlabel
        # TODO
        warp_forward_out = 'step' + i_step_str + '0GenericAffine.txt'
        warp_inverse_out = '-step' + i_step_str + '0GenericAffine.txt'
        from msct_register_landmarks import register_landmarks
        register_landmarks(src,
                           dest,
                           paramreg.steps[i_step_str].dof,
                           fname_affine=warp_forward_out,
                           verbose=param.verbose)

    if not os.path.isfile(warp_forward_out):
        # no forward warping field for rigid and affine
        sct.printv(
            '\nERROR: file ' + warp_forward_out +
            ' doesn\'t exist (or is not a file).\n' + output +
            '\nERROR: ANTs failed. Exit program.\n', 1, 'error')
    elif not os.path.isfile(warp_inverse_out) and \
            paramreg.steps[i_step_str].algo not in ['rigid', 'affine', 'translation'] and \
            paramreg.steps[i_step_str].type not in ['label']:
        # no inverse warping field for rigid and affine
        sct.printv(
            '\nERROR: file ' + warp_inverse_out +
            ' doesn\'t exist (or is not a file).\n' + output +
            '\nERROR: ANTs failed. Exit program.\n', 1, 'error')
    else:
        # rename warping fields
        if (paramreg.steps[i_step_str].algo.lower()
                in ['rigid', 'affine', 'translation']
                and paramreg.steps[i_step_str].slicewise == '0'):
            # if ANTs is used with affine/rigid --> outputs .mat file
            warp_forward = 'warp_forward_' + i_step_str + '.mat'
            os.rename(warp_forward_out, warp_forward)
            warp_inverse = '-warp_forward_' + i_step_str + '.mat'
        elif paramreg.steps[i_step_str].type in ['label']:
            # if label-based registration is used --> outputs .txt file
            warp_forward = 'warp_forward_' + i_step_str + '.txt'
            os.rename(warp_forward_out, warp_forward)
            warp_inverse = '-warp_forward_' + i_step_str + '.txt'
        else:
            warp_forward = 'warp_forward_' + i_step_str + '.nii.gz'
            warp_inverse = 'warp_inverse_' + i_step_str + '.nii.gz'
            os.rename(warp_forward_out, warp_forward)
            os.rename(warp_inverse_out, warp_inverse)

    return warp_forward, warp_inverse
Ejemplo n.º 4
0
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'
    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)
        paramreg = ParamregMultiStep([step0])
        if ref == 'subject':
            paramreg.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz'
        # add user parameters
        for paramStep in arguments['-param']:
            paramreg.addStep(paramStep)
    else:
        paramreg = ParamregMultiStep([step0, step1, step2])
        # if ref=subject, initialize registration using different affine parameters
        if ref == 'subject':
            paramreg.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz'

    # initialize other parameters
    zsubsample = param.zsubsample

    # retrieve template file names
    file_template_vertebral_labeling = get_file_label(os.path.join(path_template, 'template'), 'vertebral labeling')
    file_template = get_file_label(os.path.join(path_template, 'template'), contrast_template.upper() + '-weighted template')
    file_template_seg = get_file_label(os.path.join(path_template, 'template'), 'spinal cord')

    # 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_vertebral_labeling = os.path.join(path_template, 'template', file_template_vertebral_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_vertebral_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)

    vertebral_alignment = False
    if len(labels) > 2 and label_type == 'disc':
        vertebral_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_vertebral_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:
        paramreg.steps['0'].dof = 'Tx_Ty_Tz'
        sct.printv('WARNING: Only one label is present. Forcing initial transformation to: ' + paramreg.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 vertebral_alignment:
            # cropping the segmentation based on the label coverage to ensure good registration with vertebral 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 vertebral_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 vertebral_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 vertebral_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, paramreg.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)

        # loop across registration steps
        warp_forward = []
        warp_inverse = []
        for i_step in range(1, len(paramreg.steps)):
            sct.printv('\nEstimate transformation for step #' + str(i_step) + '...', verbose)
            # identify which is the src and dest
            if paramreg.steps[str(i_step)].type == 'im':
                src = ftmp_data
                dest = ftmp_template
                interp_step = 'linear'
            elif paramreg.steps[str(i_step)].type == 'seg':
                src = ftmp_seg
                dest = ftmp_template_seg
                interp_step = 'nn'
            else:
                sct.printv('ERROR: Wrong image type.', 1, 'error')

            if paramreg.steps[str(i_step)].algo == 'centermassrot' and paramreg.steps[str(i_step)].rot_method == 'hog':
                src_seg = ftmp_seg
                dest_seg = ftmp_template_seg
            # if step>1, apply warp_forward_concat to the src image to be used
            if i_step > 1:
                # apply transformation from previous step, to use as new src for registration
                sct_apply_transfo.main(args=[
                    '-i', src,
                    '-d', dest,
                    '-w', warp_forward,
                    '-o', add_suffix(src, '_regStep' + str(i_step - 1)),
                    '-x', interp_step])
                src = add_suffix(src, '_regStep' + str(i_step - 1))
                if paramreg.steps[str(i_step)].algo == 'centermassrot' and paramreg.steps[str(i_step)].rot_method == 'hog':  # also apply transformation to the seg
                    sct_apply_transfo.main(args=[
                        '-i', src_seg,
                        '-d', dest_seg,
                        '-w', warp_forward,
                        '-o', add_suffix(src, '_regStep' + str(i_step - 1)),
                        '-x', interp_step])
                    src_seg = add_suffix(src_seg, '_regStep' + str(i_step - 1))
            # register src --> dest
            # TODO: display param for debugging
            if paramreg.steps[str(i_step)].algo == 'centermassrot' and paramreg.steps[str(i_step)].rot_method == 'hog': # im_seg case
                warp_forward_out, warp_inverse_out = register([src, src_seg], [dest, dest_seg], paramreg, param, str(i_step))
            else:
                warp_forward_out, warp_inverse_out = register(src, dest, paramreg, param, str(i_step))
            warp_forward.append(warp_forward_out)
            warp_inverse.append(warp_inverse_out)

        # Concatenate transformations: anat --> template
        sct.printv('\nConcatenate transformations: anat --> template...', verbose)
        warp_forward.insert(0, 'warp_curve2straightAffine.nii.gz')
        sct_concat_transfo.main(args=[
            '-w', warp_forward,
            '-d', 'template.nii',
            '-o', 'warp_anat2template.nii.gz'])

        # Concatenate transformations: template --> anat
        sct.printv('\nConcatenate transformations: template --> anat...', verbose)
        warp_inverse.reverse()
        if vertebral_alignment:
            warp_inverse.append('warp_straight2curve.nii.gz')
            sct_concat_transfo.main(args=[
                '-w', warp_inverse,
                '-d', 'data.nii',
                '-o', 'warp_template2anat.nii.gz'])
        else:
            warp_inverse.append('straight2templateAffine.txt')
            warp_inverse.append('warp_straight2curve.nii.gz')
            sct_concat_transfo.main(args=[
                '-w', warp_inverse,
                '-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()

        # Bring template to subject space using landmark-based transformation
        sct.printv('\nEstimate transformation for step #0...', verbose)
        warp_forward = ['template2subjectAffine.txt']
        warp_inverse = ['template2subjectAffine.txt']
        try:
            register_landmarks(ftmp_template_label, ftmp_label, paramreg.steps['0'].dof, fname_affine=warp_forward[0], verbose=verbose, path_qc="./")
        except Exception:
            sct.printv('ERROR: 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', verbose=verbose, type='error')

        # loop across registration steps
        for i_step in range(1, len(paramreg.steps)):
            sct.printv('\nEstimate transformation for step #' + str(i_step) + '...', verbose)
            # identify which is the src and dest
            if paramreg.steps[str(i_step)].type == 'im':
                src = ftmp_template
                dest = ftmp_data
                interp_step = 'linear'
            elif paramreg.steps[str(i_step)].type == 'seg':
                src = ftmp_template_seg
                dest = ftmp_seg
                interp_step = 'nn'
            else:
                sct.printv('ERROR: Wrong image type.', 1, 'error')
            # apply transformation from previous step, to use as new src for registration
            sct_apply_transfo.main(args=[
                '-i', src,
                '-d', dest,
                '-w', warp_forward,
                '-o', add_suffix(src, '_regStep' + str(i_step - 1)),
                '-x', interp_step])
            src = add_suffix(src, '_regStep' + str(i_step - 1))
            # register src --> dest
            # TODO: display param for debugging
            warp_forward_out, warp_inverse_out = register(src, dest, paramreg, param, str(i_step))
            warp_forward.append(warp_forward_out)
            warp_inverse.insert(0, warp_inverse_out)

        # Concatenate transformations:
        sct.printv('\nConcatenate transformations: template --> subject...', verbose)
        sct_concat_transfo.main(args=[
            '-w', warp_forward,
            '-d', 'data.nii',
            '-o', 'warp_template2anat.nii.gz'])
        sct.printv('\nConcatenate transformations: subject --> template...', verbose)
        sct_concat_transfo.main(args=[
            '-w', warp_inverse,
            '-winv', ['template2subjectAffine.txt'],
            '-d', 'template.nii',
            '-o', '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', '1'], verbose)
    sct.run(['sct_apply_transfo', '-i', 'data.nii', '-o', 'anat2template.nii.gz', '-d', 'template.nii', '-w', 'warp_anat2template.nii.gz', '-crop', '1'], 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)
Ejemplo n.º 5
0
    def straighten(self):
        """
        Straighten spinal cord. Steps: (everything is done in physical space)
        1. open input image and centreline image
        2. extract bspline fitting of the centreline, and its derivatives
        3. compute length of centerline
        4. compute and generate straight space
        5. compute transformations
            for each voxel of one space: (done using matrices --> improves speed by a factor x300)
                a. determine which plane of spinal cord centreline it is included
                b. compute the position of the voxel in the plane (X and Y distance from centreline, along the plane)
                c. find the correspondant centreline point in the other space
                d. find the correspondance of the voxel in the corresponding plane
        6. generate warping fields for each transformations
        7. write warping fields and apply them

        step 5.b: how to find the corresponding plane?
            The centerline plane corresponding to a voxel correspond to the nearest point of the centerline.
            However, we need to compute the distance between the voxel position and the plane to be sure it is part of the plane and not too distant.
            If it is more far than a threshold, warping value should be 0.

        step 5.d: how to make the correspondance between centerline point in both images?
            Both centerline have the same lenght. Therefore, we can map centerline point via their position along the curve.
            If we use the same number of points uniformely along the spinal cord (1000 for example), the correspondance is straight-forward.

        :return:
        """
        # Initialization
        fname_anat = self.input_filename
        fname_centerline = self.centerline_filename
        fname_output = self.output_filename
        remove_temp_files = self.remove_temp_files
        verbose = self.verbose
        interpolation_warp = self.interpolation_warp  # TODO: remove this

        # start timer
        start_time = time.time()

        # Extract path/file/extension
        path_anat, file_anat, ext_anat = sct.extract_fname(fname_anat)

        path_tmp = sct.tmp_create(basename="straighten_spinalcord",
                                  verbose=verbose)

        # Copying input data to tmp folder
        sct.printv('\nCopy files to tmp folder...', verbose)
        Image(fname_anat).save(os.path.join(path_tmp, "data.nii"))
        Image(fname_centerline).save(
            os.path.join(path_tmp, "centerline.nii.gz"))

        if self.use_straight_reference:
            Image(self.centerline_reference_filename).save(
                os.path.join(path_tmp, "centerline_ref.nii.gz"))
        if self.discs_input_filename != '':
            Image(self.discs_input_filename).save(
                os.path.join(path_tmp, "labels_input.nii.gz"))
        if self.discs_ref_filename != '':
            Image(self.discs_ref_filename).save(
                os.path.join(path_tmp, "labels_ref.nii.gz"))

        # go to tmp folder
        curdir = os.getcwd()
        os.chdir(path_tmp)

        # Change orientation of the input centerline into RPI
        image_centerline = Image("centerline.nii.gz").change_orientation(
            "RPI").save("centerline_rpi.nii.gz", mutable=True)

        # Get dimension
        nx, ny, nz, nt, px, py, pz, pt = image_centerline.dim
        if self.speed_factor != 1.0:
            intermediate_resampling = True
            px_r, py_r, pz_r = px * self.speed_factor, py * self.speed_factor, pz * self.speed_factor
        else:
            intermediate_resampling = False

        if intermediate_resampling:
            sct.mv('centerline_rpi.nii.gz', 'centerline_rpi_native.nii.gz')
            pz_native = pz
            # TODO: remove system call
            sct.run([
                'sct_resample', '-i', 'centerline_rpi_native.nii.gz', '-mm',
                str(px_r) + 'x' + str(py_r) + 'x' + str(pz_r), '-o',
                'centerline_rpi.nii.gz'
            ])
            image_centerline = Image('centerline_rpi.nii.gz')
            nx, ny, nz, nt, px, py, pz, pt = image_centerline.dim

        if np.min(image_centerline.data) < 0 or np.max(
                image_centerline.data) > 1:
            image_centerline.data[image_centerline.data < 0] = 0
            image_centerline.data[image_centerline.data > 1] = 1
            image_centerline.save()

        # 2. extract bspline fitting of the centerline, and its derivatives
        img_ctl = Image('centerline_rpi.nii.gz')
        centerline = _get_centerline(img_ctl, self.param_centerline, verbose)
        number_of_points = centerline.number_of_points

        # ==========================================================================================
        logger.info('Create the straight space and the safe zone')
        # 3. compute length of centerline
        # compute the length of the spinal cord based on fitted centerline and size of centerline in z direction

        # Computation of the safe zone.
        # The safe zone is defined as the length of the spinal cord for which an axial segmentation will be complete
        # The safe length (to remove) is computed using the safe radius (given as parameter) and the angle of the
        # last centerline point with the inferior-superior direction. Formula: Ls = Rs * sin(angle)
        # Calculate Ls for both edges and remove appropriate number of centerline points
        radius_safe = 0.0  # mm

        # inferior edge
        u = centerline.derivatives[0]
        v = np.array([0, 0, -1])

        angle_inferior = np.arctan2(np.linalg.norm(np.cross(u, v)),
                                    np.dot(u, v))
        length_safe_inferior = radius_safe * np.sin(angle_inferior)

        # superior edge
        u = centerline.derivatives[-1]
        v = np.array([0, 0, 1])
        angle_superior = np.arctan2(np.linalg.norm(np.cross(u, v)),
                                    np.dot(u, v))
        length_safe_superior = radius_safe * np.sin(angle_superior)

        # remove points
        inferior_bound = bisect.bisect(centerline.progressive_length,
                                       length_safe_inferior) - 1
        superior_bound = centerline.number_of_points - bisect.bisect(
            centerline.progressive_length_inverse, length_safe_superior)

        z_centerline = centerline.points[:, 2]
        length_centerline = centerline.length
        size_z_centerline = z_centerline[-1] - z_centerline[0]

        # compute the size factor between initial centerline and straight bended centerline
        factor_curved_straight = length_centerline / size_z_centerline
        middle_slice = (z_centerline[0] + z_centerline[-1]) / 2.0

        bound_curved = [
            z_centerline[inferior_bound], z_centerline[superior_bound]
        ]
        bound_straight = [(z_centerline[inferior_bound] - middle_slice) *
                          factor_curved_straight + middle_slice,
                          (z_centerline[superior_bound] - middle_slice) *
                          factor_curved_straight + middle_slice]

        logger.info('Length of spinal cord: {}'.format(length_centerline))
        logger.info(
            'Size of spinal cord in z direction: {}'.format(size_z_centerline))
        logger.info('Ratio length/size: {}'.format(factor_curved_straight))
        logger.info(
            'Safe zone boundaries (curved space): {}'.format(bound_curved))
        logger.info(
            'Safe zone boundaries (straight space): {}'.format(bound_straight))

        # 4. compute and generate straight space
        # points along curved centerline are already regularly spaced.
        # calculate position of points along straight centerline

        # Create straight NIFTI volumes.
        # ==========================================================================================
        # TODO: maybe this if case is not needed?
        if self.use_straight_reference:
            image_centerline_pad = Image('centerline_rpi.nii.gz')
            nx, ny, nz, nt, px, py, pz, pt = image_centerline_pad.dim

            fname_ref = 'centerline_ref_rpi.nii.gz'
            image_centerline_straight = Image('centerline_ref.nii.gz') \
                .change_orientation("RPI") \
                .save(fname_ref, mutable=True)
            centerline_straight = _get_centerline(image_centerline_straight,
                                                  algo_fitting, self.degree,
                                                  verbose)
            nx_s, ny_s, nz_s, nt_s, px_s, py_s, pz_s, pt_s = image_centerline_straight.dim

            # Prepare warping fields headers
            hdr_warp = image_centerline_pad.hdr.copy()
            hdr_warp.set_data_dtype('float32')
            hdr_warp_s = image_centerline_straight.hdr.copy()
            hdr_warp_s.set_data_dtype('float32')

            if self.discs_input_filename != "" and self.discs_ref_filename != "":
                discs_input_image = Image('labels_input.nii.gz')
                coord = discs_input_image.getNonZeroCoordinates(
                    sorting='z', reverse_coord=True)
                coord_physical = []
                for c in coord:
                    c_p = discs_input_image.transfo_pix2phys([[c.x, c.y, c.z]
                                                              ]).tolist()[0]
                    c_p.append(c.value)
                    coord_physical.append(c_p)
                centerline.compute_vertebral_distribution(coord_physical)
                centerline.save_centerline(
                    image=discs_input_image,
                    fname_output='discs_input_image.nii.gz')

                discs_ref_image = Image('labels_ref.nii.gz')
                coord = discs_ref_image.getNonZeroCoordinates(
                    sorting='z', reverse_coord=True)
                coord_physical = []
                for c in coord:
                    c_p = discs_ref_image.transfo_pix2phys([[c.x, c.y,
                                                             c.z]]).tolist()[0]
                    c_p.append(c.value)
                    coord_physical.append(c_p)
                centerline_straight.compute_vertebral_distribution(
                    coord_physical)
                centerline_straight.save_centerline(
                    image=discs_ref_image,
                    fname_output='discs_ref_image.nii.gz')

        else:
            logger.info(
                'Pad input volume to account for spinal cord length...')

            start_point, end_point = bound_straight[0], bound_straight[1]
            offset_z = 0

            # if the destination image is resampled, we still create the straight reference space with the native
            # resolution.
            # TODO: Maybe this if case is not needed?
            if intermediate_resampling:
                padding_z = int(
                    np.ceil(1.5 *
                            ((length_centerline - size_z_centerline) / 2.0) /
                            pz_native))
                sct.run([
                    'sct_image', '-i', 'centerline_rpi_native.nii.gz', '-o',
                    'tmp.centerline_pad_native.nii.gz', '-pad',
                    '0,0,' + str(padding_z)
                ])
                image_centerline_pad = Image('centerline_rpi_native.nii.gz')
                nx, ny, nz, nt, px, py, pz, pt = image_centerline_pad.dim
                start_point_coord_native = image_centerline_pad.transfo_phys2pix(
                    [[0, 0, start_point]])[0]
                end_point_coord_native = image_centerline_pad.transfo_phys2pix(
                    [[0, 0, end_point]])[0]
                straight_size_x = int(self.xy_size / px)
                straight_size_y = int(self.xy_size / py)
                warp_space_x = [
                    int(np.round(nx / 2)) - straight_size_x,
                    int(np.round(nx / 2)) + straight_size_x
                ]
                warp_space_y = [
                    int(np.round(ny / 2)) - straight_size_y,
                    int(np.round(ny / 2)) + straight_size_y
                ]
                if warp_space_x[0] < 0:
                    warp_space_x[1] += warp_space_x[0] - 2
                    warp_space_x[0] = 0
                if warp_space_y[0] < 0:
                    warp_space_y[1] += warp_space_y[0] - 2
                    warp_space_y[0] = 0

                spec = dict((
                    (0, warp_space_x),
                    (1, warp_space_y),
                    (2, (0, end_point_coord_native[2] -
                         start_point_coord_native[2])),
                ))
                msct_image.spatial_crop(
                    Image("tmp.centerline_pad_native.nii.gz"),
                    spec).save("tmp.centerline_pad_crop_native.nii.gz")

                fname_ref = 'tmp.centerline_pad_crop_native.nii.gz'
                offset_z = 4
            else:
                fname_ref = 'tmp.centerline_pad_crop.nii.gz'

            nx, ny, nz, nt, px, py, pz, pt = image_centerline.dim
            padding_z = int(
                np.ceil(1.5 * ((length_centerline - size_z_centerline) / 2.0) /
                        pz)) + offset_z
            image_centerline_pad = pad_image(image_centerline,
                                             pad_z_i=padding_z,
                                             pad_z_f=padding_z)
            nx, ny, nz = image_centerline_pad.data.shape
            hdr_warp = image_centerline_pad.hdr.copy()
            hdr_warp.set_data_dtype('float32')
            start_point_coord = image_centerline_pad.transfo_phys2pix(
                [[0, 0, start_point]])[0]
            end_point_coord = image_centerline_pad.transfo_phys2pix(
                [[0, 0, end_point]])[0]

            straight_size_x = int(self.xy_size / px)
            straight_size_y = int(self.xy_size / py)
            warp_space_x = [
                int(np.round(nx / 2)) - straight_size_x,
                int(np.round(nx / 2)) + straight_size_x
            ]
            warp_space_y = [
                int(np.round(ny / 2)) - straight_size_y,
                int(np.round(ny / 2)) + straight_size_y
            ]

            if warp_space_x[0] < 0:
                warp_space_x[1] += warp_space_x[0] - 2
                warp_space_x[0] = 0
            if warp_space_x[1] >= nx:
                warp_space_x[1] = nx - 1
            if warp_space_y[0] < 0:
                warp_space_y[1] += warp_space_y[0] - 2
                warp_space_y[0] = 0
            if warp_space_y[1] >= ny:
                warp_space_y[1] = ny - 1

            spec = dict((
                (0, warp_space_x),
                (1, warp_space_y),
                (2, (0, end_point_coord[2] - start_point_coord[2] + offset_z)),
            ))
            image_centerline_straight = msct_image.spatial_crop(
                image_centerline_pad, spec)

            nx_s, ny_s, nz_s, nt_s, px_s, py_s, pz_s, pt_s = image_centerline_straight.dim
            hdr_warp_s = image_centerline_straight.hdr.copy()
            hdr_warp_s.set_data_dtype('float32')

            if self.template_orientation == 1:
                raise NotImplementedError()

            start_point_coord = image_centerline_pad.transfo_phys2pix(
                [[0, 0, start_point]])[0]
            end_point_coord = image_centerline_pad.transfo_phys2pix(
                [[0, 0, end_point]])[0]

            number_of_voxel = nx * ny * nz
            logger.debug('Number of voxels: {}'.format(number_of_voxel))

            time_centerlines = time.time()

            coord_straight = np.empty((number_of_points, 3))
            coord_straight[..., 0] = int(np.round(nx_s / 2))
            coord_straight[..., 1] = int(np.round(ny_s / 2))
            coord_straight[..., 2] = np.linspace(
                0, end_point_coord[2] - start_point_coord[2], number_of_points)
            coord_phys_straight = image_centerline_straight.transfo_pix2phys(
                coord_straight)
            derivs_straight = np.empty((number_of_points, 3))
            derivs_straight[..., 0] = derivs_straight[..., 1] = 0
            derivs_straight[..., 2] = 1
            dx_straight, dy_straight, dz_straight = derivs_straight.T
            centerline_straight = Centerline(coord_phys_straight[:, 0],
                                             coord_phys_straight[:, 1],
                                             coord_phys_straight[:, 2],
                                             dx_straight, dy_straight,
                                             dz_straight)

            time_centerlines = time.time() - time_centerlines
            logger.info('Time to generate centerline: {} ms'.format(
                np.round(time_centerlines * 1000.0)))

        if verbose == 2:
            # TODO: use OO
            import matplotlib.pyplot as plt
            from datetime import datetime
            curved_points = centerline.progressive_length
            straight_points = centerline_straight.progressive_length
            range_points = np.linspace(0, 1, number_of_points)
            dist_curved = np.zeros(number_of_points)
            dist_straight = np.zeros(number_of_points)
            for i in range(1, number_of_points):
                dist_curved[i] = dist_curved[
                    i - 1] + curved_points[i - 1] / centerline.length
                dist_straight[i] = dist_straight[i - 1] + straight_points[
                    i - 1] / centerline_straight.length
            plt.plot(range_points, dist_curved)
            plt.plot(range_points, dist_straight)
            plt.grid(True)
            plt.savefig('fig_straighten_' +
                        datetime.now().strftime("%y%m%d%H%M%S%f") + '.png')
            plt.close()

        # alignment_mode = 'length'
        alignment_mode = 'levels'

        lookup_curved2straight = list(range(centerline.number_of_points))
        if self.discs_input_filename != "":
            # create look-up table curved to straight
            for index in range(centerline.number_of_points):
                disc_label = centerline.l_points[index]
                if alignment_mode == 'length':
                    relative_position = centerline.dist_points[index]
                else:
                    relative_position = centerline.dist_points_rel[index]
                idx_closest = centerline_straight.get_closest_to_absolute_position(
                    disc_label,
                    relative_position,
                    backup_index=index,
                    backup_centerline=centerline_straight,
                    mode=alignment_mode)
                if idx_closest is not None:
                    lookup_curved2straight[index] = idx_closest
                else:
                    lookup_curved2straight[index] = 0
        for p in range(0, len(lookup_curved2straight) // 2):
            if lookup_curved2straight[p] == lookup_curved2straight[p + 1]:
                lookup_curved2straight[p] = 0
            else:
                break
        for p in range(
                len(lookup_curved2straight) - 1,
                len(lookup_curved2straight) // 2, -1):
            if lookup_curved2straight[p] == lookup_curved2straight[p - 1]:
                lookup_curved2straight[p] = 0
            else:
                break
        lookup_curved2straight = np.array(lookup_curved2straight)

        lookup_straight2curved = list(
            range(centerline_straight.number_of_points))
        if self.discs_input_filename != "":
            for index in range(centerline_straight.number_of_points):
                disc_label = centerline_straight.l_points[index]
                if alignment_mode == 'length':
                    relative_position = centerline_straight.dist_points[index]
                else:
                    relative_position = centerline_straight.dist_points_rel[
                        index]
                idx_closest = centerline.get_closest_to_absolute_position(
                    disc_label,
                    relative_position,
                    backup_index=index,
                    backup_centerline=centerline_straight,
                    mode=alignment_mode)
                if idx_closest is not None:
                    lookup_straight2curved[index] = idx_closest
        for p in range(0, len(lookup_straight2curved) // 2):
            if lookup_straight2curved[p] == lookup_straight2curved[p + 1]:
                lookup_straight2curved[p] = 0
            else:
                break
        for p in range(
                len(lookup_straight2curved) - 1,
                len(lookup_straight2curved) // 2, -1):
            if lookup_straight2curved[p] == lookup_straight2curved[p - 1]:
                lookup_straight2curved[p] = 0
            else:
                break
        lookup_straight2curved = np.array(lookup_straight2curved)

        # Create volumes containing curved and straight warping fields
        data_warp_curved2straight = np.zeros((nx_s, ny_s, nz_s, 1, 3))
        data_warp_straight2curved = np.zeros((nx, ny, nz, 1, 3))

        # 5. compute transformations
        # Curved and straight images and the same dimensions, so we compute both warping fields at the same time.
        # b. determine which plane of spinal cord centreline it is included
        # sct.printv(nx * ny * nz, nx_s * ny_s * nz_s)

        if self.curved2straight:
            for u in tqdm(range(nz_s)):
                x_s, y_s, z_s = np.mgrid[0:nx_s, 0:ny_s, u:u + 1]
                indexes_straight = np.array(
                    list(zip(x_s.ravel(), y_s.ravel(), z_s.ravel())))
                physical_coordinates_straight = image_centerline_straight.transfo_pix2phys(
                    indexes_straight)
                nearest_indexes_straight = centerline_straight.find_nearest_indexes(
                    physical_coordinates_straight)
                distances_straight = centerline_straight.get_distances_from_planes(
                    physical_coordinates_straight, nearest_indexes_straight)
                lookup = lookup_straight2curved[nearest_indexes_straight]
                indexes_out_distance_straight = np.logical_or(
                    np.logical_or(
                        distances_straight > self.threshold_distance,
                        distances_straight < -self.threshold_distance),
                    lookup == 0)
                projected_points_straight = centerline_straight.get_projected_coordinates_on_planes(
                    physical_coordinates_straight, nearest_indexes_straight)
                coord_in_planes_straight = centerline_straight.get_in_plans_coordinates(
                    projected_points_straight, nearest_indexes_straight)

                coord_straight2curved = centerline.get_inverse_plans_coordinates(
                    coord_in_planes_straight, lookup)
                displacements_straight = coord_straight2curved - physical_coordinates_straight
                # Invert Z coordinate as ITK & ANTs physical coordinate system is LPS- (RAI+)
                # while ours is LPI-
                # Refs: https://sourceforge.net/p/advants/discussion/840261/thread/2a1e9307/#fb5a
                #  https://www.slicer.org/wiki/Coordinate_systems
                displacements_straight[:, 2] = -displacements_straight[:, 2]
                displacements_straight[indexes_out_distance_straight] = [
                    100000.0, 100000.0, 100000.0
                ]

                data_warp_curved2straight[indexes_straight[:, 0], indexes_straight[:, 1], indexes_straight[:, 2], 0, :]\
                    = -displacements_straight

        if self.straight2curved:
            for u in tqdm(range(nz)):
                x, y, z = np.mgrid[0:nx, 0:ny, u:u + 1]
                indexes = np.array(list(zip(x.ravel(), y.ravel(), z.ravel())))
                physical_coordinates = image_centerline_pad.transfo_pix2phys(
                    indexes)
                nearest_indexes_curved = centerline.find_nearest_indexes(
                    physical_coordinates)
                distances_curved = centerline.get_distances_from_planes(
                    physical_coordinates, nearest_indexes_curved)
                lookup = lookup_curved2straight[nearest_indexes_curved]
                indexes_out_distance_curved = np.logical_or(
                    np.logical_or(distances_curved > self.threshold_distance,
                                  distances_curved < -self.threshold_distance),
                    lookup == 0)
                projected_points_curved = centerline.get_projected_coordinates_on_planes(
                    physical_coordinates, nearest_indexes_curved)
                coord_in_planes_curved = centerline.get_in_plans_coordinates(
                    projected_points_curved, nearest_indexes_curved)

                coord_curved2straight = centerline_straight.points[lookup]
                coord_curved2straight[:, 0:2] += coord_in_planes_curved[:, 0:2]
                coord_curved2straight[:, 2] += distances_curved

                displacements_curved = coord_curved2straight - physical_coordinates

                displacements_curved[:, 2] = -displacements_curved[:, 2]
                displacements_curved[indexes_out_distance_curved] = [
                    100000.0, 100000.0, 100000.0
                ]

                data_warp_straight2curved[indexes[:, 0], indexes[:, 1],
                                          indexes[:, 2],
                                          0, :] = -displacements_curved

        # Creation of the safe zone based on pre-calculated safe boundaries
        coord_bound_curved_inf, coord_bound_curved_sup = image_centerline_pad.transfo_phys2pix(
            [[0, 0, bound_curved[0]]]), image_centerline_pad.transfo_phys2pix(
                [[0, 0, bound_curved[1]]])
        coord_bound_straight_inf, coord_bound_straight_sup = image_centerline_straight.transfo_phys2pix(
            [[0, 0,
              bound_straight[0]]]), image_centerline_straight.transfo_phys2pix(
                  [[0, 0, bound_straight[1]]])

        if radius_safe > 0:
            data_warp_curved2straight[:, :, 0:coord_bound_straight_inf[0][2],
                                      0, :] = 100000.0
            data_warp_curved2straight[:, :, coord_bound_straight_sup[0][2]:,
                                      0, :] = 100000.0
            data_warp_straight2curved[:, :, 0:coord_bound_curved_inf[0][2],
                                      0, :] = 100000.0
            data_warp_straight2curved[:, :, coord_bound_curved_sup[0][2]:,
                                      0, :] = 100000.0

        # Generate warp files as a warping fields
        hdr_warp_s.set_intent('vector', (), '')
        hdr_warp_s.set_data_dtype('float32')
        hdr_warp.set_intent('vector', (), '')
        hdr_warp.set_data_dtype('float32')
        if self.curved2straight:
            img = Nifti1Image(data_warp_curved2straight, None, hdr_warp_s)
            save(img, 'tmp.curve2straight.nii.gz')
            logger.info('Warping field generated: tmp.curve2straight.nii.gz')

        if self.straight2curved:
            img = Nifti1Image(data_warp_straight2curved, None, hdr_warp)
            save(img, 'tmp.straight2curve.nii.gz')
            logger.info('Warping field generated: tmp.straight2curve.nii.gz')

        image_centerline_straight.save(fname_ref)
        if self.curved2straight:
            logger.info('Apply transformation to input image...')
            sct.run([
                'isct_antsApplyTransforms', '-d', '3', '-r', fname_ref, '-i',
                'data.nii', '-o', 'tmp.anat_rigid_warp.nii.gz', '-t',
                'tmp.curve2straight.nii.gz', '-n', 'BSpline[3]'
            ],
                    is_sct_binary=True,
                    verbose=verbose)

        if self.accuracy_results:
            time_accuracy_results = time.time()
            # compute the error between the straightened centerline/segmentation and the central vertical line.
            # Ideally, the error should be zero.
            # Apply deformation to input image
            logger.info('Apply transformation to centerline image...')
            sct.run([
                'isct_antsApplyTransforms', '-d', '3', '-r', fname_ref, '-i',
                'centerline.nii.gz', '-o', 'tmp.centerline_straight.nii.gz',
                '-t', 'tmp.curve2straight.nii.gz', '-n', 'NearestNeighbor'
            ],
                    is_sct_binary=True,
                    verbose=verbose)
            file_centerline_straight = Image('tmp.centerline_straight.nii.gz',
                                             verbose=verbose)
            nx, ny, nz, nt, px, py, pz, pt = file_centerline_straight.dim
            coordinates_centerline = file_centerline_straight.getNonZeroCoordinates(
                sorting='z')
            mean_coord = []
            for z in range(coordinates_centerline[0].z,
                           coordinates_centerline[-1].z):
                temp_mean = [
                    coord.value for coord in coordinates_centerline
                    if coord.z == z
                ]
                if temp_mean:
                    mean_value = np.mean(temp_mean)
                    mean_coord.append(
                        np.mean([[
                            coord.x * coord.value / mean_value,
                            coord.y * coord.value / mean_value
                        ] for coord in coordinates_centerline if coord.z == z],
                                axis=0))

            # compute error between the straightened centerline and the straight line.
            x0 = file_centerline_straight.data.shape[0] / 2.0
            y0 = file_centerline_straight.data.shape[1] / 2.0
            count_mean = 0
            if number_of_points >= 10:
                mean_c = mean_coord[
                    2:
                    -2]  # we don't include the four extrema because there are usually messy.
            else:
                mean_c = mean_coord
            for coord_z in mean_c:
                if not np.isnan(np.sum(coord_z)):
                    dist = ((x0 - coord_z[0]) * px)**2 + (
                        (y0 - coord_z[1]) * py)**2
                    self.mse_straightening += dist
                    dist = np.sqrt(dist)
                    if dist > self.max_distance_straightening:
                        self.max_distance_straightening = dist
                    count_mean += 1
            self.mse_straightening = np.sqrt(self.mse_straightening /
                                             float(count_mean))

            self.elapsed_time_accuracy = time.time() - time_accuracy_results

        os.chdir(curdir)

        # Generate output file (in current folder)
        # TODO: do not uncompress the warping field, it is too time consuming!
        logger.info('Generate output files...')
        if self.curved2straight:
            sct.generate_output_file(
                os.path.join(path_tmp, "tmp.curve2straight.nii.gz"),
                os.path.join(self.path_output, "warp_curve2straight.nii.gz"),
                verbose)
        if self.straight2curved:
            sct.generate_output_file(
                os.path.join(path_tmp, "tmp.straight2curve.nii.gz"),
                os.path.join(self.path_output, "warp_straight2curve.nii.gz"),
                verbose)

        # create ref_straight.nii.gz file that can be used by other SCT functions that need a straight reference space
        if self.curved2straight:
            sct.copy(os.path.join(path_tmp, "tmp.anat_rigid_warp.nii.gz"),
                     os.path.join(self.path_output, "straight_ref.nii.gz"))
            # move straightened input file
            if fname_output == '':
                fname_straight = sct.generate_output_file(
                    os.path.join(path_tmp, "tmp.anat_rigid_warp.nii.gz"),
                    os.path.join(self.path_output,
                                 file_anat + "_straight" + ext_anat), verbose)
            else:
                fname_straight = sct.generate_output_file(
                    os.path.join(path_tmp, "tmp.anat_rigid_warp.nii.gz"),
                    os.path.join(self.path_output, fname_output),
                    verbose)  # straightened anatomic

        # Remove temporary files
        if remove_temp_files:
            logger.info('Remove temporary files...')
            sct.rmtree(path_tmp)

        if self.accuracy_results:
            logger.info('Maximum x-y error: {} mm'.format(
                self.max_distance_straightening))
            logger.info('Accuracy of straightening (MSE): {} mm'.format(
                self.mse_straightening))

        # display elapsed time
        self.elapsed_time = int(np.round(time.time() - start_time))

        return fname_straight
Ejemplo n.º 6
0
    def straighten(self):
        """
        Straighten spinal cord. Steps: (everything is done in physical space)
        1. open input image and centreline image
        2. extract bspline fitting of the centreline, and its derivatives
        3. compute length of centerline
        4. compute and generate straight space
        5. compute transformations
            for each voxel of one space: (done using matrices --> improves speed by a factor x300)
                a. determine which plane of spinal cord centreline it is included
                b. compute the position of the voxel in the plane (X and Y distance from centreline, along the plane)
                c. find the correspondant centreline point in the other space
                d. find the correspondance of the voxel in the corresponding plane
        6. generate warping fields for each transformations
        7. write warping fields and apply them

        step 5.b: how to find the corresponding plane?
            The centerline plane corresponding to a voxel correspond to the nearest point of the centerline.
            However, we need to compute the distance between the voxel position and the plane to be sure it is part of the plane and not too distant.
            If it is more far than a threshold, warping value should be 0.

        step 5.d: how to make the correspondance between centerline point in both images?
            Both centerline have the same lenght. Therefore, we can map centerline point via their position along the curve.
            If we use the same number of points uniformely along the spinal cord (1000 for example), the correspondance is straight-forward.

        :return:
        """
        # Initialization
        fname_anat = self.input_filename
        fname_centerline = self.centerline_filename
        fname_output = self.output_filename
        remove_temp_files = self.remove_temp_files
        verbose = self.verbose
        interpolation_warp = self.interpolation_warp
        algo_fitting = self.algo_fitting

        # start timer
        start_time = time.time()

        # Extract path/file/extension
        path_anat, file_anat, ext_anat = sct.extract_fname(fname_anat)

        path_tmp = sct.tmp_create(basename="straighten_spinalcord", verbose=verbose)

        # Copying input data to tmp folder
        sct.printv('\nCopy files to tmp folder...', verbose)
        Image(fname_anat).save(os.path.join(path_tmp, "data.nii"))
        Image(fname_centerline).save(os.path.join(path_tmp, "centerline.nii.gz"))

        if self.use_straight_reference:
            Image(self.centerline_reference_filename).save(os.path.join(path_tmp, "centerline_ref.nii.gz"))
        if self.discs_input_filename != '':
            Image(self.discs_input_filename).save(os.path.join(path_tmp, "labels_input.nii.gz"))
        if self.discs_ref_filename != '':
            Image(self.discs_ref_filename).save(os.path.join(path_tmp, "labels_ref.nii.gz"))

        # go to tmp folder
        curdir = os.getcwd()
        os.chdir(path_tmp)

        # Change orientation of the input centerline into RPI
        image_centerline = Image("centerline.nii.gz").change_orientation("RPI").save("centerline_rpi.nii.gz",
                                                                                     mutable=True)

        # Get dimension
        nx, ny, nz, nt, px, py, pz, pt = image_centerline.dim
        if self.speed_factor != 1.0:
            intermediate_resampling = True
            px_r, py_r, pz_r = px * self.speed_factor, py * self.speed_factor, pz * self.speed_factor
        else:
            intermediate_resampling = False

        if intermediate_resampling:
            sct.mv('centerline_rpi.nii.gz', 'centerline_rpi_native.nii.gz')
            pz_native = pz
            # TODO: remove system call
            sct.run(['sct_resample', '-i', 'centerline_rpi_native.nii.gz', '-mm',
                     str(px_r) + 'x' + str(py_r) + 'x' + str(pz_r), '-o', 'centerline_rpi.nii.gz'])
            image_centerline = Image('centerline_rpi.nii.gz')
            nx, ny, nz, nt, px, py, pz, pt = image_centerline.dim

        if np.min(image_centerline.data) < 0 or np.max(image_centerline.data) > 1:
            image_centerline.data[image_centerline.data < 0] = 0
            image_centerline.data[image_centerline.data > 1] = 1
            image_centerline.save()

        # 2. extract bspline fitting of the centerline, and its derivatives
        img_ctl = Image('centerline_rpi.nii.gz')
        centerline = _get_centerline(img_ctl, algo_fitting, self.degree, verbose)
        number_of_points = centerline.number_of_points

        # ==========================================================================================
        logger.info('Create the straight space and the safe zone')
        # 3. compute length of centerline
        # compute the length of the spinal cord based on fitted centerline and size of centerline in z direction

        # Computation of the safe zone.
        # The safe zone is defined as the length of the spinal cord for which an axial segmentation will be complete
        # The safe length (to remove) is computed using the safe radius (given as parameter) and the angle of the
        # last centerline point with the inferior-superior direction. Formula: Ls = Rs * sin(angle)
        # Calculate Ls for both edges and remove appropriate number of centerline points
        radius_safe = 0.0  # mm

        # inferior edge
        u = centerline.derivatives[0]
        v = np.array([0, 0, -1])

        angle_inferior = np.arctan2(np.linalg.norm(np.cross(u, v)), np.dot(u, v))
        length_safe_inferior = radius_safe * np.sin(angle_inferior)

        # superior edge
        u = centerline.derivatives[-1]
        v = np.array([0, 0, 1])
        angle_superior = np.arctan2(np.linalg.norm(np.cross(u, v)), np.dot(u, v))
        length_safe_superior = radius_safe * np.sin(angle_superior)

        # remove points
        inferior_bound = bisect.bisect(centerline.progressive_length, length_safe_inferior) - 1
        superior_bound = centerline.number_of_points - bisect.bisect(centerline.progressive_length_inverse,
                                                                     length_safe_superior)

        z_centerline = centerline.points[:, 2]
        length_centerline = centerline.length
        size_z_centerline = z_centerline[-1] - z_centerline[0]

        # compute the size factor between initial centerline and straight bended centerline
        factor_curved_straight = length_centerline / size_z_centerline
        middle_slice = (z_centerline[0] + z_centerline[-1]) / 2.0

        bound_curved = [z_centerline[inferior_bound], z_centerline[superior_bound]]
        bound_straight = [(z_centerline[inferior_bound] - middle_slice) * factor_curved_straight + middle_slice,
                          (z_centerline[superior_bound] - middle_slice) * factor_curved_straight + middle_slice]

        logger.info('Length of spinal cord: {}'.format(length_centerline))
        logger.info('Size of spinal cord in z direction: {}'.format(size_z_centerline))
        logger.info('Ratio length/size: {}'.format(factor_curved_straight))
        logger.info('Safe zone boundaries (curved space): {}'.format(bound_curved))
        logger.info('Safe zone boundaries (straight space): {}'.format(bound_straight))

        # 4. compute and generate straight space
        # points along curved centerline are already regularly spaced.
        # calculate position of points along straight centerline

        # Create straight NIFTI volumes.
        # ==========================================================================================
        # TODO: maybe this if case is not needed?
        if self.use_straight_reference:
            image_centerline_pad = Image('centerline_rpi.nii.gz')
            nx, ny, nz, nt, px, py, pz, pt = image_centerline_pad.dim

            fname_ref = 'centerline_ref_rpi.nii.gz'
            image_centerline_straight = Image('centerline_ref.nii.gz') \
                .change_orientation("RPI") \
                .save(fname_ref, mutable=True)
            centerline_straight = _get_centerline(image_centerline_straight, algo_fitting, self.degree, verbose)
            nx_s, ny_s, nz_s, nt_s, px_s, py_s, pz_s, pt_s = image_centerline_straight.dim

            # Prepare warping fields headers
            hdr_warp = image_centerline_pad.hdr.copy()
            hdr_warp.set_data_dtype('float32')
            hdr_warp_s = image_centerline_straight.hdr.copy()
            hdr_warp_s.set_data_dtype('float32')

            if self.discs_input_filename != "" and self.discs_ref_filename != "":
                discs_input_image = Image('labels_input.nii.gz')
                coord = discs_input_image.getNonZeroCoordinates(sorting='z', reverse_coord=True)
                coord_physical = []
                for c in coord:
                    c_p = discs_input_image.transfo_pix2phys([[c.x, c.y, c.z]]).tolist()[0]
                    c_p.append(c.value)
                    coord_physical.append(c_p)
                centerline.compute_vertebral_distribution(coord_physical)
                centerline.save_centerline(image=discs_input_image, fname_output='discs_input_image.nii.gz')

                discs_ref_image = Image('labels_ref.nii.gz')
                coord = discs_ref_image.getNonZeroCoordinates(sorting='z', reverse_coord=True)
                coord_physical = []
                for c in coord:
                    c_p = discs_ref_image.transfo_pix2phys([[c.x, c.y, c.z]]).tolist()[0]
                    c_p.append(c.value)
                    coord_physical.append(c_p)
                centerline_straight.compute_vertebral_distribution(coord_physical)
                centerline_straight.save_centerline(image=discs_ref_image, fname_output='discs_ref_image.nii.gz')

        else:
            logger.info('Pad input volume to account for spinal cord length...')

            start_point, end_point = bound_straight[0], bound_straight[1]
            offset_z = 0

            # if the destination image is resampled, we still create the straight reference space with the native
            # resolution.
            # TODO: Maybe this if case is not needed?
            if intermediate_resampling:
                padding_z = int(np.ceil(1.5 * ((length_centerline - size_z_centerline) / 2.0) / pz_native))
                sct.run(
                    ['sct_image', '-i', 'centerline_rpi_native.nii.gz', '-o', 'tmp.centerline_pad_native.nii.gz',
                     '-pad', '0,0,' + str(padding_z)])
                image_centerline_pad = Image('centerline_rpi_native.nii.gz')
                nx, ny, nz, nt, px, py, pz, pt = image_centerline_pad.dim
                start_point_coord_native = image_centerline_pad.transfo_phys2pix([[0, 0, start_point]])[0]
                end_point_coord_native = image_centerline_pad.transfo_phys2pix([[0, 0, end_point]])[0]
                straight_size_x = int(self.xy_size / px)
                straight_size_y = int(self.xy_size / py)
                warp_space_x = [int(np.round(nx / 2)) - straight_size_x, int(np.round(nx / 2)) + straight_size_x]
                warp_space_y = [int(np.round(ny / 2)) - straight_size_y, int(np.round(ny / 2)) + straight_size_y]
                if warp_space_x[0] < 0:
                    warp_space_x[1] += warp_space_x[0] - 2
                    warp_space_x[0] = 0
                if warp_space_y[0] < 0:
                    warp_space_y[1] += warp_space_y[0] - 2
                    warp_space_y[0] = 0

                spec = dict((
                    (0, warp_space_x),
                    (1, warp_space_y),
                    (2, (0, end_point_coord_native[2] - start_point_coord_native[2])),
                ))
                msct_image.spatial_crop(Image("tmp.centerline_pad_native.nii.gz"), spec).save(
                    "tmp.centerline_pad_crop_native.nii.gz")

                fname_ref = 'tmp.centerline_pad_crop_native.nii.gz'
                offset_z = 4
            else:
                fname_ref = 'tmp.centerline_pad_crop.nii.gz'

            nx, ny, nz, nt, px, py, pz, pt = image_centerline.dim
            padding_z = int(np.ceil(1.5 * ((length_centerline - size_z_centerline) / 2.0) / pz)) + offset_z
            image_centerline_pad = pad_image(image_centerline, pad_z_i=padding_z, pad_z_f=padding_z)
            nx, ny, nz = image_centerline_pad.data.shape
            hdr_warp = image_centerline_pad.hdr.copy()
            hdr_warp.set_data_dtype('float32')
            start_point_coord = image_centerline_pad.transfo_phys2pix([[0, 0, start_point]])[0]
            end_point_coord = image_centerline_pad.transfo_phys2pix([[0, 0, end_point]])[0]

            straight_size_x = int(self.xy_size / px)
            straight_size_y = int(self.xy_size / py)
            warp_space_x = [int(np.round(nx / 2)) - straight_size_x, int(np.round(nx / 2)) + straight_size_x]
            warp_space_y = [int(np.round(ny / 2)) - straight_size_y, int(np.round(ny / 2)) + straight_size_y]

            if warp_space_x[0] < 0:
                warp_space_x[1] += warp_space_x[0] - 2
                warp_space_x[0] = 0
            if warp_space_x[1] >= nx:
                warp_space_x[1] = nx - 1
            if warp_space_y[0] < 0:
                warp_space_y[1] += warp_space_y[0] - 2
                warp_space_y[0] = 0
            if warp_space_y[1] >= ny:
                warp_space_y[1] = ny - 1

            spec = dict((
                (0, warp_space_x),
                (1, warp_space_y),
                (2, (0, end_point_coord[2] - start_point_coord[2] + offset_z)),
            ))
            image_centerline_straight = msct_image.spatial_crop(image_centerline_pad, spec)

            nx_s, ny_s, nz_s, nt_s, px_s, py_s, pz_s, pt_s = image_centerline_straight.dim
            hdr_warp_s = image_centerline_straight.hdr.copy()
            hdr_warp_s.set_data_dtype('float32')

            if self.template_orientation == 1:
                raise NotImplementedError()

            start_point_coord = image_centerline_pad.transfo_phys2pix([[0, 0, start_point]])[0]
            end_point_coord = image_centerline_pad.transfo_phys2pix([[0, 0, end_point]])[0]

            number_of_voxel = nx * ny * nz
            logger.debug('Number of voxels: {}'.format(number_of_voxel))

            time_centerlines = time.time()

            coord_straight = np.empty((number_of_points, 3))
            coord_straight[..., 0] = int(np.round(nx_s / 2))
            coord_straight[..., 1] = int(np.round(ny_s / 2))
            coord_straight[..., 2] = np.linspace(0, end_point_coord[2] - start_point_coord[2], number_of_points)
            coord_phys_straight = image_centerline_straight.transfo_pix2phys(coord_straight)
            derivs_straight = np.empty((number_of_points, 3))
            derivs_straight[..., 0] = derivs_straight[..., 1] = 0
            derivs_straight[..., 2] = 1
            dx_straight, dy_straight, dz_straight = derivs_straight.T
            centerline_straight = Centerline(coord_phys_straight[:, 0], coord_phys_straight[:, 1],
                                             coord_phys_straight[:, 2],
                                             dx_straight, dy_straight, dz_straight)

            time_centerlines = time.time() - time_centerlines
            logger.info('Time to generate centerline: {} ms'.format(np.round(time_centerlines * 1000.0)))

        if verbose == 2:
            # TODO: use OO
            import matplotlib.pyplot as plt
            from datetime import datetime
            curved_points = centerline.progressive_length
            straight_points = centerline_straight.progressive_length
            range_points = np.linspace(0, 1, number_of_points)
            dist_curved = np.zeros(number_of_points)
            dist_straight = np.zeros(number_of_points)
            for i in range(1, number_of_points):
                dist_curved[i] = dist_curved[i - 1] + curved_points[i - 1] / centerline.length
                dist_straight[i] = dist_straight[i - 1] + straight_points[i - 1] / centerline_straight.length
            plt.plot(range_points, dist_curved)
            plt.plot(range_points, dist_straight)
            plt.grid(True)
            plt.savefig('fig_straighten_' + datetime.now().strftime("%y%m%d%H%M%S%f") + '.png')
            plt.close()

        # alignment_mode = 'length'
        alignment_mode = 'levels'

        lookup_curved2straight = list(range(centerline.number_of_points))
        if self.discs_input_filename != "":
            # create look-up table curved to straight
            for index in range(centerline.number_of_points):
                disc_label = centerline.l_points[index]
                if alignment_mode == 'length':
                    relative_position = centerline.dist_points[index]
                else:
                    relative_position = centerline.dist_points_rel[index]
                idx_closest = centerline_straight.get_closest_to_absolute_position(disc_label, relative_position,
                                                                                   backup_index=index,
                                                                                   backup_centerline=centerline_straight,
                                                                                   mode=alignment_mode)
                if idx_closest is not None:
                    lookup_curved2straight[index] = idx_closest
                else:
                    lookup_curved2straight[index] = 0
        for p in range(0, len(lookup_curved2straight) // 2):
            if lookup_curved2straight[p] == lookup_curved2straight[p + 1]:
                lookup_curved2straight[p] = 0
            else:
                break
        for p in range(len(lookup_curved2straight) - 1, len(lookup_curved2straight) // 2, -1):
            if lookup_curved2straight[p] == lookup_curved2straight[p - 1]:
                lookup_curved2straight[p] = 0
            else:
                break
        lookup_curved2straight = np.array(lookup_curved2straight)

        lookup_straight2curved = list(range(centerline_straight.number_of_points))
        if self.discs_input_filename != "":
            for index in range(centerline_straight.number_of_points):
                disc_label = centerline_straight.l_points[index]
                if alignment_mode == 'length':
                    relative_position = centerline_straight.dist_points[index]
                else:
                    relative_position = centerline_straight.dist_points_rel[index]
                idx_closest = centerline.get_closest_to_absolute_position(disc_label, relative_position,
                                                                          backup_index=index,
                                                                          backup_centerline=centerline_straight,
                                                                          mode=alignment_mode)
                if idx_closest is not None:
                    lookup_straight2curved[index] = idx_closest
        for p in range(0, len(lookup_straight2curved) // 2):
            if lookup_straight2curved[p] == lookup_straight2curved[p + 1]:
                lookup_straight2curved[p] = 0
            else:
                break
        for p in range(len(lookup_straight2curved) - 1, len(lookup_straight2curved) // 2, -1):
            if lookup_straight2curved[p] == lookup_straight2curved[p - 1]:
                lookup_straight2curved[p] = 0
            else:
                break
        lookup_straight2curved = np.array(lookup_straight2curved)

        # Create volumes containing curved and straight warping fields
        data_warp_curved2straight = np.zeros((nx_s, ny_s, nz_s, 1, 3))
        data_warp_straight2curved = np.zeros((nx, ny, nz, 1, 3))

        # 5. compute transformations
        # Curved and straight images and the same dimensions, so we compute both warping fields at the same time.
        # b. determine which plane of spinal cord centreline it is included
        # sct.printv(nx * ny * nz, nx_s * ny_s * nz_s)

        if self.curved2straight:
            for u in tqdm(range(nz_s)):
                x_s, y_s, z_s = np.mgrid[0:nx_s, 0:ny_s, u:u + 1]
                indexes_straight = np.array(list(zip(x_s.ravel(), y_s.ravel(), z_s.ravel())))
                physical_coordinates_straight = image_centerline_straight.transfo_pix2phys(indexes_straight)
                nearest_indexes_straight = centerline_straight.find_nearest_indexes(physical_coordinates_straight)
                distances_straight = centerline_straight.get_distances_from_planes(physical_coordinates_straight,
                                                                                   nearest_indexes_straight)
                lookup = lookup_straight2curved[nearest_indexes_straight]
                indexes_out_distance_straight = np.logical_or(
                    np.logical_or(distances_straight > self.threshold_distance,
                                  distances_straight < -self.threshold_distance), lookup == 0)
                projected_points_straight = centerline_straight.get_projected_coordinates_on_planes(
                    physical_coordinates_straight, nearest_indexes_straight)
                coord_in_planes_straight = centerline_straight.get_in_plans_coordinates(projected_points_straight,
                                                                                        nearest_indexes_straight)

                coord_straight2curved = centerline.get_inverse_plans_coordinates(coord_in_planes_straight, lookup)
                displacements_straight = coord_straight2curved - physical_coordinates_straight
                # Invert Z coordinate as ITK & ANTs physical coordinate system is LPS- (RAI+)
                # while ours is LPI-
                # Refs: https://sourceforge.net/p/advants/discussion/840261/thread/2a1e9307/#fb5a
                #  https://www.slicer.org/wiki/Coordinate_systems
                displacements_straight[:, 2] = -displacements_straight[:, 2]
                displacements_straight[indexes_out_distance_straight] = [100000.0, 100000.0, 100000.0]

                data_warp_curved2straight[indexes_straight[:, 0], indexes_straight[:, 1], indexes_straight[:, 2], 0, :]\
                    = -displacements_straight

        if self.straight2curved:
            for u in tqdm(range(nz)):
                x, y, z = np.mgrid[0:nx, 0:ny, u:u + 1]
                indexes = np.array(list(zip(x.ravel(), y.ravel(), z.ravel())))
                physical_coordinates = image_centerline_pad.transfo_pix2phys(indexes)
                nearest_indexes_curved = centerline.find_nearest_indexes(physical_coordinates)
                distances_curved = centerline.get_distances_from_planes(physical_coordinates,
                                                                        nearest_indexes_curved)
                lookup = lookup_curved2straight[nearest_indexes_curved]
                indexes_out_distance_curved = np.logical_or(
                    np.logical_or(distances_curved > self.threshold_distance,
                                  distances_curved < -self.threshold_distance), lookup == 0)
                projected_points_curved = centerline.get_projected_coordinates_on_planes(physical_coordinates,
                                                                                         nearest_indexes_curved)
                coord_in_planes_curved = centerline.get_in_plans_coordinates(projected_points_curved,
                                                                             nearest_indexes_curved)

                coord_curved2straight = centerline_straight.points[lookup]
                coord_curved2straight[:, 0:2] += coord_in_planes_curved[:, 0:2]
                coord_curved2straight[:, 2] += distances_curved

                displacements_curved = coord_curved2straight - physical_coordinates

                displacements_curved[:, 2] = -displacements_curved[:, 2]
                displacements_curved[indexes_out_distance_curved] = [100000.0, 100000.0, 100000.0]

                data_warp_straight2curved[indexes[:, 0], indexes[:, 1], indexes[:, 2], 0, :] = -displacements_curved

        # Creation of the safe zone based on pre-calculated safe boundaries
        coord_bound_curved_inf, coord_bound_curved_sup = image_centerline_pad.transfo_phys2pix(
            [[0, 0, bound_curved[0]]]), image_centerline_pad.transfo_phys2pix([[0, 0, bound_curved[1]]])
        coord_bound_straight_inf, coord_bound_straight_sup = image_centerline_straight.transfo_phys2pix(
            [[0, 0, bound_straight[0]]]), image_centerline_straight.transfo_phys2pix([[0, 0, bound_straight[1]]])

        if radius_safe > 0:
            data_warp_curved2straight[:, :, 0:coord_bound_straight_inf[0][2], 0, :] = 100000.0
            data_warp_curved2straight[:, :, coord_bound_straight_sup[0][2]:, 0, :] = 100000.0
            data_warp_straight2curved[:, :, 0:coord_bound_curved_inf[0][2], 0, :] = 100000.0
            data_warp_straight2curved[:, :, coord_bound_curved_sup[0][2]:, 0, :] = 100000.0

        # Generate warp files as a warping fields
        hdr_warp_s.set_intent('vector', (), '')
        hdr_warp_s.set_data_dtype('float32')
        hdr_warp.set_intent('vector', (), '')
        hdr_warp.set_data_dtype('float32')
        if self.curved2straight:
            img = Nifti1Image(data_warp_curved2straight, None, hdr_warp_s)
            save(img, 'tmp.curve2straight.nii.gz')
            logger.info('Warping field generated: tmp.curve2straight.nii.gz')

        if self.straight2curved:
            img = Nifti1Image(data_warp_straight2curved, None, hdr_warp)
            save(img, 'tmp.straight2curve.nii.gz')
            logger.info('Warping field generated: tmp.straight2curve.nii.gz')

        image_centerline_straight.save(fname_ref)
        if self.curved2straight:
            logger.info('Apply transformation to input image...')
            sct.run(['isct_antsApplyTransforms',
                     '-d', '3',
                     '-r', fname_ref,
                     '-i', 'data.nii',
                     '-o', 'tmp.anat_rigid_warp.nii.gz',
                     '-t', 'tmp.curve2straight.nii.gz',
                     '-n', 'BSpline[3]'],
                    is_sct_binary=True,
                    verbose=verbose)

        if self.accuracy_results:
            time_accuracy_results = time.time()
            # compute the error between the straightened centerline/segmentation and the central vertical line.
            # Ideally, the error should be zero.
            # Apply deformation to input image
            logger.info('Apply transformation to centerline image...')
            sct.run(['isct_antsApplyTransforms',
                     '-d', '3',
                     '-r', fname_ref,
                     '-i', 'centerline.nii.gz',
                     '-o', 'tmp.centerline_straight.nii.gz',
                     '-t', 'tmp.curve2straight.nii.gz',
                     '-n', 'NearestNeighbor'],
                    is_sct_binary=True,
                    verbose=verbose)
            file_centerline_straight = Image('tmp.centerline_straight.nii.gz', verbose=verbose)
            nx, ny, nz, nt, px, py, pz, pt = file_centerline_straight.dim
            coordinates_centerline = file_centerline_straight.getNonZeroCoordinates(sorting='z')
            mean_coord = []
            for z in range(coordinates_centerline[0].z, coordinates_centerline[-1].z):
                temp_mean = [coord.value for coord in coordinates_centerline if coord.z == z]
                if temp_mean:
                    mean_value = np.mean(temp_mean)
                    mean_coord.append(
                        np.mean([[coord.x * coord.value / mean_value, coord.y * coord.value / mean_value]
                                 for coord in coordinates_centerline if coord.z == z], axis=0))

            # compute error between the straightened centerline and the straight line.
            x0 = file_centerline_straight.data.shape[0] / 2.0
            y0 = file_centerline_straight.data.shape[1] / 2.0
            count_mean = 0
            if number_of_points >= 10:
                mean_c = mean_coord[2:-2]  # we don't include the four extrema because there are usually messy.
            else:
                mean_c = mean_coord
            for coord_z in mean_c:
                if not np.isnan(np.sum(coord_z)):
                    dist = ((x0 - coord_z[0]) * px) ** 2 + ((y0 - coord_z[1]) * py) ** 2
                    self.mse_straightening += dist
                    dist = np.sqrt(dist)
                    if dist > self.max_distance_straightening:
                        self.max_distance_straightening = dist
                    count_mean += 1
            self.mse_straightening = np.sqrt(self.mse_straightening / float(count_mean))

            self.elapsed_time_accuracy = time.time() - time_accuracy_results

        os.chdir(curdir)

        # Generate output file (in current folder)
        # TODO: do not uncompress the warping field, it is too time consuming!
        logger.info('Generate output files...')
        if self.curved2straight:
            sct.generate_output_file(os.path.join(path_tmp, "tmp.curve2straight.nii.gz"),
                                     os.path.join(self.path_output, "warp_curve2straight.nii.gz"), verbose)
        if self.straight2curved:
            sct.generate_output_file(os.path.join(path_tmp, "tmp.straight2curve.nii.gz"),
                                     os.path.join(self.path_output, "warp_straight2curve.nii.gz"), verbose)

        # create ref_straight.nii.gz file that can be used by other SCT functions that need a straight reference space
        if self.curved2straight:
            sct.copy(os.path.join(path_tmp, "tmp.anat_rigid_warp.nii.gz"),
                     os.path.join(self.path_output, "straight_ref.nii.gz"))
            # move straightened input file
            if fname_output == '':
                fname_straight = sct.generate_output_file(os.path.join(path_tmp, "tmp.anat_rigid_warp.nii.gz"),
                                                          os.path.join(self.path_output,
                                                                       file_anat + "_straight" + ext_anat), verbose)
            else:
                fname_straight = sct.generate_output_file(os.path.join(path_tmp, "tmp.anat_rigid_warp.nii.gz"),
                                                          os.path.join(self.path_output, fname_output),
                                                          verbose)  # straightened anatomic

        # Remove temporary files
        if remove_temp_files:
            logger.info('Remove temporary files...')
            sct.rmtree(path_tmp)

        if self.accuracy_results:
            logger.info('Maximum x-y error: {} mm'.format(self.max_distance_straightening))
            logger.info('Accuracy of straightening (MSE): {} mm'.format(self.mse_straightening))

        # display elapsed time
        self.elapsed_time = int(np.round(time.time() - start_time))

        return fname_straight
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'
    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.straighten_fitting = arguments['-straighten-fitting']
    # if '-cpu-nb' in arguments:
    #     arg_cpu = ' -cpu-nb '+str(arguments['-cpu-nb'])
    # else:
    #     arg_cpu = ''
    # registration parameters
    if '-param' in arguments:
        # reset parameters but keep step=0 (might be overwritten if user specified step=0)
        paramreg = ParamregMultiStep([step0])
        if ref == 'subject':
            paramreg.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz'
        # add user parameters
        for paramStep in arguments['-param']:
            paramreg.addStep(paramStep)
    else:
        paramreg = ParamregMultiStep([step0, step1, step2])
        # if ref=subject, initialize registration using different affine parameters
        if ref == 'subject':
            paramreg.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz'

    # initialize other parameters
    zsubsample = param.zsubsample

    # retrieve template file names
    file_template_vertebral_labeling = get_file_label(os.path.join(path_template, 'template'), 'vertebral labeling')
    file_template = get_file_label(os.path.join(path_template, 'template'), contrast_template.upper() + '-weighted template')
    file_template_seg = get_file_label(os.path.join(path_template, 'template'), 'spinal cord')

    # 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_vertebral_labeling = os.path.join(path_template, 'template', file_template_vertebral_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_vertebral_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)

    vertebral_alignment = False
    if len(labels) > 2 and label_type == 'disc':
        vertebral_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_vertebral_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:
        paramreg.steps['0'].dof = 'Tx_Ty_Tz'
        sct.printv('WARNING: Only one label is present. Forcing initial transformation to: ' + paramreg.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)

    # 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 vertebral_alignment:
            # cropping the segmentation based on the label coverage to ensure good registration with vertebral 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 vertebral_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.run(['sct_apply_transfo', '-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.algo_fitting = param.straighten_fitting
            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 vertebral_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)
        s, o = sct.run(['sct_concat_transfo', '-w', 'warp_straight2curve.nii.gz', '-d', ftmp_data, '-o', 'warp_straight2curve.nii.gz'])

        if vertebral_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.run(['sct_apply_transfo', '-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, paramreg.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.run(['sct_concat_transfo', '-w', 'warp_curve2straight.nii.gz,straight2templateAffine.txt', '-d', 'template.nii', '-o', 'warp_curve2straightAffine.nii.gz'])

        # Apply transformation
        sct.printv('\nApply transformation...', verbose)
        sct.run(['sct_apply_transfo', '-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.run(['sct_apply_transfo', '-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)

        # loop across registration steps
        warp_forward = []
        warp_inverse = []
        for i_step in range(1, len(paramreg.steps)):
            sct.printv('\nEstimate transformation for step #' + str(i_step) + '...', verbose)
            # identify which is the src and dest
            if paramreg.steps[str(i_step)].type == 'im':
                src = ftmp_data
                dest = ftmp_template
                interp_step = 'linear'
            elif paramreg.steps[str(i_step)].type == 'seg':
                src = ftmp_seg
                dest = ftmp_template_seg
                interp_step = 'nn'
            else:
                sct.printv('ERROR: Wrong image type.', 1, 'error')

            if paramreg.steps[str(i_step)].algo == 'centermassrot' and paramreg.steps[str(i_step)].rot_method == 'hog':
                src_seg = ftmp_seg
                dest_seg = ftmp_template_seg
            # if step>1, apply warp_forward_concat to the src image to be used
            if i_step > 1:
                # apply transformation from previous step, to use as new src for registration
                sct.run(['sct_apply_transfo', '-i', src, '-d', dest, '-w', ','.join(warp_forward), '-o', add_suffix(src, '_regStep' + str(i_step - 1)), '-x', interp_step], verbose)
                src = add_suffix(src, '_regStep' + str(i_step - 1))
                if paramreg.steps[str(i_step)].algo == 'centermassrot' and paramreg.steps[str(i_step)].rot_method == 'hog':  # also apply transformation to the seg
                    sct.run(['sct_apply_transfo', '-i', src_seg, '-d', dest_seg, '-w', ','.join(warp_forward), '-o', add_suffix(src, '_regStep' + str(i_step - 1)), '-x', interp_step], verbose)
                    src_seg = add_suffix(src_seg, '_regStep' + str(i_step - 1))
            # register src --> dest
            # TODO: display param for debugging
            if paramreg.steps[str(i_step)].algo == 'centermassrot' and paramreg.steps[str(i_step)].rot_method == 'hog': # im_seg case
                warp_forward_out, warp_inverse_out = register([src, src_seg], [dest, dest_seg], paramreg, param, str(i_step))
            else:
                warp_forward_out, warp_inverse_out = register(src, dest, paramreg, param, str(i_step))
            warp_forward.append(warp_forward_out)
            warp_inverse.append(warp_inverse_out)

        # Concatenate transformations:
        sct.printv('\nConcatenate transformations: anat --> template...', verbose)
        sct.run(['sct_concat_transfo', '-w', 'warp_curve2straightAffine.nii.gz,' + ','.join(warp_forward), '-d', 'template.nii', '-o', 'warp_anat2template.nii.gz'], verbose)
        # sct.run('sct_concat_transfo -w warp_curve2straight.nii.gz,straight2templateAffine.txt,'+','.join(warp_forward)+' -d template.nii -o warp_anat2template.nii.gz', verbose)
        sct.printv('\nConcatenate transformations: template --> anat...', verbose)
        warp_inverse.reverse()

        if vertebral_alignment:
            sct.run(['sct_concat_transfo', '-w', ','.join(warp_inverse) + ',warp_straight2curve.nii.gz', '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz'], verbose)
        else:
            sct.run(['sct_concat_transfo', '-w', ','.join(warp_inverse) + ',-straight2templateAffine.txt,warp_straight2curve.nii.gz', '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz'], verbose)

    # 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()

        # Bring template to subject space using landmark-based transformation
        sct.printv('\nEstimate transformation for step #0...', verbose)
        warp_forward = ['template2subjectAffine.txt']
        warp_inverse = ['-template2subjectAffine.txt']
        try:
            register_landmarks(ftmp_template_label, ftmp_label, paramreg.steps['0'].dof, fname_affine=warp_forward[0], verbose=verbose, path_qc="./")
        except Exception:
            sct.printv('ERROR: 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', verbose=verbose, type='error')

        # loop across registration steps
        for i_step in range(1, len(paramreg.steps)):
            sct.printv('\nEstimate transformation for step #' + str(i_step) + '...', verbose)
            # identify which is the src and dest
            if paramreg.steps[str(i_step)].type == 'im':
                src = ftmp_template
                dest = ftmp_data
                interp_step = 'linear'
            elif paramreg.steps[str(i_step)].type == 'seg':
                src = ftmp_template_seg
                dest = ftmp_seg
                interp_step = 'nn'
            else:
                sct.printv('ERROR: Wrong image type.', 1, 'error')
            # apply transformation from previous step, to use as new src for registration
            sct.run(['sct_apply_transfo', '-i', src, '-d', dest, '-w', ','.join(warp_forward), '-o', add_suffix(src, '_regStep' + str(i_step - 1)), '-x', interp_step], verbose)
            src = add_suffix(src, '_regStep' + str(i_step - 1))
            # register src --> dest
            # TODO: display param for debugging
            warp_forward_out, warp_inverse_out = register(src, dest, paramreg, param, str(i_step))
            warp_forward.append(warp_forward_out)
            warp_inverse.insert(0, warp_inverse_out)

        # Concatenate transformations:
        sct.printv('\nConcatenate transformations: template --> subject...', verbose)
        sct.run(['sct_concat_transfo', '-w', ','.join(warp_forward), '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz'], verbose)
        sct.printv('\nConcatenate transformations: subject --> template...', verbose)
        sct.run(['sct_concat_transfo', '-w', ','.join(warp_inverse), '-d', 'template.nii', '-o', 'warp_anat2template.nii.gz'], verbose)

    # 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', '1'], verbose)
    sct.run(['sct_apply_transfo', '-i', 'data.nii', '-o', 'anat2template.nii.gz', '-d', 'template.nii', '-w', 'warp_anat2template.nii.gz', '-crop', '1'], 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 register(src, dest, paramreg, param, i_step_str):
    # initiate default parameters of antsRegistration transformation
    ants_registration_params = {'rigid': '', 'affine': '', 'compositeaffine': '', 'similarity': '', 'translation': '',
                                'bspline': ',10', 'gaussiandisplacementfield': ',3,0',
                                'bsplinedisplacementfield': ',5,10', 'syn': ',3,0', 'bsplinesyn': ',1,3'}
    output = ''  # default output if problem

    if paramreg.steps[i_step_str].algo == "centermassrot" and paramreg.steps[i_step_str].rot_method == 'hog':
        src_im = src[0]  # user is expected to input images to src and dest
        dest_im = dest[0]
        src_seg = src[1]
        dest_seg = dest[1]
        del src
        del dest  # to be sure it is not missused later


    # display arguments
    sct.printv('Registration parameters:', param.verbose)
    sct.printv('  type ........... ' + paramreg.steps[i_step_str].type, param.verbose)
    sct.printv('  algo ........... ' + paramreg.steps[i_step_str].algo, param.verbose)
    sct.printv('  slicewise ...... ' + paramreg.steps[i_step_str].slicewise, param.verbose)
    sct.printv('  metric ......... ' + paramreg.steps[i_step_str].metric, param.verbose)
    sct.printv('  iter ........... ' + paramreg.steps[i_step_str].iter, param.verbose)
    sct.printv('  smooth ......... ' + paramreg.steps[i_step_str].smooth, param.verbose)
    sct.printv('  laplacian ...... ' + paramreg.steps[i_step_str].laplacian, param.verbose)
    sct.printv('  shrink ......... ' + paramreg.steps[i_step_str].shrink, param.verbose)
    sct.printv('  gradStep ....... ' + paramreg.steps[i_step_str].gradStep, param.verbose)
    sct.printv('  deformation .... ' + paramreg.steps[i_step_str].deformation, param.verbose)
    sct.printv('  init ........... ' + paramreg.steps[i_step_str].init, param.verbose)
    sct.printv('  poly ........... ' + paramreg.steps[i_step_str].poly, param.verbose)
    sct.printv('  dof ............ ' + paramreg.steps[i_step_str].dof, param.verbose)
    sct.printv('  smoothWarpXY ... ' + paramreg.steps[i_step_str].smoothWarpXY, param.verbose)
    sct.printv('  rot_method ... ' + paramreg.steps[i_step_str].rot_method, param.verbose)

    # set metricSize
    if paramreg.steps[i_step_str].metric == 'MI':
        metricSize = '32'  # corresponds to number of bins
    else:
        metricSize = '4'  # corresponds to radius (for CC, MeanSquares...)

    # set masking
    if param.fname_mask:
        fname_mask = 'mask.nii.gz'
        masking = ['-x', 'mask.nii.gz']
    else:
        fname_mask = ''
        masking = []

    if paramreg.steps[i_step_str].algo == 'slicereg':
        # check if user used type=label
        if paramreg.steps[i_step_str].type == 'label':
            sct.printv('\nERROR: this algo is not compatible with type=label. Please use type=im or type=seg', 1,
                       'error')
        else:
            # Find the min (and max) z-slice index below which (and above which) slices only have voxels below a given
            # threshold.
            list_fname = [src, dest]
            if not masking == []:
                list_fname.append(fname_mask)
            zmin_global, zmax_global = 0, 99999  # this is assuming that typical image has less slice than 99999
            for fname in list_fname:
                im = Image(fname)
                zmin, zmax = msct_image.find_zmin_zmax(im, threshold=0.1)
                if zmin > zmin_global:
                    zmin_global = zmin
                if zmax < zmax_global:
                    zmax_global = zmax
            # crop images (see issue #293)
            src_crop = sct.add_suffix(src, '_crop')
            msct_image.spatial_crop(Image(src), dict(((2, (zmin_global, zmax_global)),))).save(src_crop)
            dest_crop = sct.add_suffix(dest, '_crop')
            msct_image.spatial_crop(Image(dest), dict(((2, (zmin_global, zmax_global)),))).save(dest_crop)
            # update variables
            src = src_crop
            dest = dest_crop
            scr_regStep = sct.add_suffix(src, '_regStep' + i_step_str)
            # estimate transfo
            # TODO fixup isct_ants* parsers
            cmd = ['isct_antsSliceRegularizedRegistration',
                   '-t', 'Translation[' + paramreg.steps[i_step_str].gradStep + ']',
                   '-m',
                   paramreg.steps[i_step_str].metric + '[' + dest + ',' + src + ',1,' + metricSize + ',Regular,0.2]',
                   '-p', paramreg.steps[i_step_str].poly,
                   '-i', paramreg.steps[i_step_str].iter,
                   '-f', paramreg.steps[i_step_str].shrink,
                   '-s', paramreg.steps[i_step_str].smooth,
                   '-v', '1',  # verbose (verbose=2 does not exist, so we force it to 1)
                   '-o', '[step' + i_step_str + ',' + scr_regStep + ']',  # here the warp name is stage10 because
                   # antsSliceReg add "Warp"
                   ] + masking
            warp_forward_out = 'step' + i_step_str + 'Warp.nii.gz'
            warp_inverse_out = 'step' + i_step_str + 'InverseWarp.nii.gz'
            # run command
            status, output = sct.run(cmd, param.verbose, is_sct_binary=True)

    # ANTS 3d
    elif paramreg.steps[i_step_str].algo.lower() in ants_registration_params \
            and paramreg.steps[i_step_str].slicewise == '0':
        # make sure type!=label. If type==label, this will be addressed later in the code.
        if not paramreg.steps[i_step_str].type == 'label':
            # Pad the destination image (because ants doesn't deform the extremities)
            # N.B. no need to pad if iter = 0
            if not paramreg.steps[i_step_str].iter == '0':
                dest_pad = sct.add_suffix(dest, '_pad')
                sct.run(['sct_image', '-i', dest, '-o', dest_pad, '-pad', '0,0,' + str(param.padding)])
                dest = dest_pad
            # apply Laplacian filter
            if not paramreg.steps[i_step_str].laplacian == '0':
                sct.printv('\nApply Laplacian filter', param.verbose)
                sct.run(['sct_maths', '-i', src, '-laplacian', paramreg.steps[i_step_str].laplacian + ','
                         + paramreg.steps[i_step_str].laplacian + ',0', '-o', sct.add_suffix(src, '_laplacian')])
                sct.run(['sct_maths', '-i', dest, '-laplacian', paramreg.steps[i_step_str].laplacian + ','
                         + paramreg.steps[i_step_str].laplacian + ',0', '-o', sct.add_suffix(dest, '_laplacian')])
                src = sct.add_suffix(src, '_laplacian')
                dest = sct.add_suffix(dest, '_laplacian')
            # Estimate transformation
            sct.printv('\nEstimate transformation', param.verbose)
            scr_regStep = sct.add_suffix(src, '_regStep' + i_step_str)
            # TODO fixup isct_ants* parsers
            cmd = ['isct_antsRegistration',
                   '--dimensionality', '3',
                   '--transform', paramreg.steps[i_step_str].algo + '[' + paramreg.steps[i_step_str].gradStep
                   + ants_registration_params[paramreg.steps[i_step_str].algo.lower()] + ']',
                   '--metric', paramreg.steps[i_step_str].metric + '[' + dest + ',' + src + ',1,' + metricSize + ']',
                   '--convergence', paramreg.steps[i_step_str].iter,
                   '--shrink-factors', paramreg.steps[i_step_str].shrink,
                   '--smoothing-sigmas', paramreg.steps[i_step_str].smooth + 'mm',
                   '--restrict-deformation', paramreg.steps[i_step_str].deformation,
                   '--output', '[step' + i_step_str + ',' + scr_regStep + ']',
                   '--interpolation', 'BSpline[3]',
                   '--verbose', '1',
                   ] + masking
            # add init translation
            if not paramreg.steps[i_step_str].init == '':
                init_dict = {'geometric': '0', 'centermass': '1', 'origin': '2'}
                cmd += ['-r', '[' + dest + ',' + src + ',' + init_dict[paramreg.steps[i_step_str].init] + ']']
            # run command
            status, output = sct.run(cmd, param.verbose, is_sct_binary=True)
            # get appropriate file name for transformation
            if paramreg.steps[i_step_str].algo in ['rigid', 'affine', 'translation']:
                warp_forward_out = 'step' + i_step_str + '0GenericAffine.mat'
                warp_inverse_out = '-step' + i_step_str + '0GenericAffine.mat'
            else:
                warp_forward_out = 'step' + i_step_str + '0Warp.nii.gz'
                warp_inverse_out = 'step' + i_step_str + '0InverseWarp.nii.gz'

    # ANTS 2d
    elif paramreg.steps[i_step_str].algo.lower() in ants_registration_params \
            and paramreg.steps[i_step_str].slicewise == '1':
        # make sure type!=label. If type==label, this will be addressed later in the code.
        if not paramreg.steps[i_step_str].type == 'label':
            from msct_register import register_slicewise
            # if shrink!=1, force it to be 1 (otherwise, it generates a wrong 3d warping field). TODO: fix that!
            if not paramreg.steps[i_step_str].shrink == '1':
                sct.printv('\nWARNING: when using slicewise with SyN or BSplineSyN, shrink factor needs to be one. '
                           'Forcing shrink=1.', 1, 'warning')
                paramreg.steps[i_step_str].shrink = '1'
            warp_forward_out = 'step' + i_step_str + 'Warp.nii.gz'
            warp_inverse_out = 'step' + i_step_str + 'InverseWarp.nii.gz'
            register_slicewise(src,
                               dest,
                               paramreg=paramreg.steps[i_step_str],
                               fname_mask=fname_mask,
                               warp_forward_out=warp_forward_out,
                               warp_inverse_out=warp_inverse_out,
                               ants_registration_params=ants_registration_params,
                               remove_temp_files=param.remove_temp_files,
                               verbose=param.verbose)

    # slice-wise transfo
    elif paramreg.steps[i_step_str].algo in ['centermass', 'centermassrot', 'columnwise']:
        # if type=label, exit with error
        if paramreg.steps[i_step_str].type == 'label':
            sct.printv('\nERROR: this algo is not compatible with type=label. Please use type=im or type=seg', 1,
                       'error')
        # check if user provided a mask-- if so, inform it will be ignored
        if not fname_mask == '':
            sct.printv('\nWARNING: algo ' + paramreg.steps[i_step_str].algo + ' will ignore the provided mask.\n', 1,
                       'warning')
        # smooth data
        if not paramreg.steps[i_step_str].smooth == '0':
            sct.printv('\nSmooth data', param.verbose)
            if paramreg.steps[i_step_str].rot_method == 'pca':
                sct.run(['sct_maths', '-i', src, '-smooth', paramreg.steps[i_step_str].smooth + ','
                         + paramreg.steps[i_step_str].smooth + ',0', '-o', sct.add_suffix(src, '_smooth')])
                sct.run(['sct_maths', '-i', dest, '-smooth', paramreg.steps[i_step_str].smooth + ','
                         + paramreg.steps[i_step_str].smooth + ',0', '-o', sct.add_suffix(dest, '_smooth')])
                src = sct.add_suffix(src, '_smooth')
                dest = sct.add_suffix(dest, '_smooth')
            else:
                sct.run(['sct_maths', '-i', src_im, '-smooth', paramreg.steps[i_step_str].smooth + ','
                         + paramreg.steps[i_step_str].smooth + ',0', '-o', sct.add_suffix(src_im, '_smooth')])
                sct.run(['sct_maths', '-i', src_seg, '-smooth', paramreg.steps[i_step_str].smooth + ','
                         + paramreg.steps[i_step_str].smooth + ',0', '-o', sct.add_suffix(src_seg, '_smooth')])
                sct.run(['sct_maths', '-i', dest_im, '-smooth', paramreg.steps[i_step_str].smooth + ','
                         + paramreg.steps[i_step_str].smooth + ',0', '-o', sct.add_suffix(dest_im, '_smooth')])
                sct.run(['sct_maths', '-i', dest_seg, '-smooth', paramreg.steps[i_step_str].smooth + ','
                         + paramreg.steps[i_step_str].smooth + ',0', '-o', sct.add_suffix(dest_seg, '_smooth')])
                src_im = sct.add_suffix(src_im, '_smooth')
                dest_im = sct.add_suffix(dest_im, '_smooth')
                src_seg = sct.add_suffix(src_seg, '_smooth')
                dest_seg = sct.add_suffix(dest_seg, '_smooth')
        from msct_register import register_slicewise
        warp_forward_out = 'step' + i_step_str + 'Warp.nii.gz'
        warp_inverse_out = 'step' + i_step_str + 'InverseWarp.nii.gz'
        if paramreg.steps[i_step_str].rot_method == 'pca':  #because pca is the default choice, also includes no rotation
            register_slicewise(src,
                           dest,
                           paramreg=paramreg.steps[i_step_str],
                           fname_mask=fname_mask,
                           warp_forward_out=warp_forward_out,
                           warp_inverse_out=warp_inverse_out,
                           ants_registration_params=ants_registration_params,
                           remove_temp_files=param.remove_temp_files,
                           verbose=param.verbose)
        elif paramreg.steps[i_step_str].rot_method == 'hog':  # im_seg case
            register_slicewise([src_im, src_seg],
                           [dest_im, dest_seg],
                           paramreg=paramreg.steps[i_step_str],
                           fname_mask=fname_mask,
                           warp_forward_out=warp_forward_out,
                           warp_inverse_out=warp_inverse_out,
                           ants_registration_params=ants_registration_params,
                           path_qc=param.path_qc,
                           remove_temp_files=param.remove_temp_files,
                           verbose=param.verbose)
        else:
            raise ValueError("rot_method " + paramreg.steps[i_step_str].rot_method + " does not exist")


    else:
        sct.printv('\nERROR: algo ' + paramreg.steps[i_step_str].algo + ' does not exist. Exit program\n', 1, 'error')

    # landmark-based registration
    if paramreg.steps[i_step_str].type in ['label']:
        # check if user specified ilabel and dlabel
        # TODO
        warp_forward_out = 'step' + i_step_str + '0GenericAffine.txt'
        warp_inverse_out = '-step' + i_step_str + '0GenericAffine.txt'
        from msct_register_landmarks import register_landmarks
        register_landmarks(src,
                           dest,
                           paramreg.steps[i_step_str].dof,
                           fname_affine=warp_forward_out,
                           verbose=param.verbose)

    if not os.path.isfile(warp_forward_out):
        # no forward warping field for rigid and affine
        sct.printv('\nERROR: file ' + warp_forward_out + ' doesn\'t exist (or is not a file).\n' + output +
                   '\nERROR: ANTs failed. Exit program.\n', 1, 'error')
    elif not os.path.isfile(warp_inverse_out) and \
            paramreg.steps[i_step_str].algo not in ['rigid', 'affine', 'translation'] and \
            paramreg.steps[i_step_str].type not in ['label']:
        # no inverse warping field for rigid and affine
        sct.printv('\nERROR: file ' + warp_inverse_out + ' doesn\'t exist (or is not a file).\n' + output +
                   '\nERROR: ANTs failed. Exit program.\n', 1, 'error')
    else:
        # rename warping fields
        if (paramreg.steps[i_step_str].algo.lower() in ['rigid', 'affine', 'translation'] and
                paramreg.steps[i_step_str].slicewise == '0'):
            # if ANTs is used with affine/rigid --> outputs .mat file
            warp_forward = 'warp_forward_' + i_step_str + '.mat'
            os.rename(warp_forward_out, warp_forward)
            warp_inverse = '-warp_forward_' + i_step_str + '.mat'
        elif paramreg.steps[i_step_str].type in ['label']:
            # if label-based registration is used --> outputs .txt file
            warp_forward = 'warp_forward_' + i_step_str + '.txt'
            os.rename(warp_forward_out, warp_forward)
            warp_inverse = '-warp_forward_' + i_step_str + '.txt'
        else:
            warp_forward = 'warp_forward_' + i_step_str + '.nii.gz'
            warp_inverse = 'warp_inverse_' + i_step_str + '.nii.gz'
            os.rename(warp_forward_out, warp_forward)
            os.rename(warp_inverse_out, warp_inverse)

    return warp_forward, warp_inverse