コード例 #1
0
def get_parser(paramregmulti=None):
    # Initialize the parser

    if paramregmulti is None:
        step0 = Paramreg(step='0', type='im', algo='syn', metric='MI', iter='0', shrink='1', smooth='0', gradStep='0.5',
                         slicewise='0', dof='Tx_Ty_Tz_Rx_Ry_Rz')  # only used to put src into dest space
        step1 = Paramreg(step='1', type='im')
        paramregmulti = ParamregMultiStep([step0, step1])

    parser = Parser(__file__)
    parser.usage.set_description('This program co-registers two 3D volumes. The deformation is non-rigid and is '
                                 'constrained along Z direction (i.e., axial plane). Hence, this function assumes '
                                 'that orientation of the destination image is axial (RPI). If you need to register '
                                 'two volumes with large deformations and/or different contrasts, it is recommended to '
                                 'input spinal cord segmentations (binary mask) in order to achieve maximum robustness.'
                                 ' The program outputs a warping field that can be used to register other images to the'
                                 ' destination image. To apply the warping field to another image, use '
                                 'sct_apply_transfo')
    parser.add_option(name="-i",
                      type_value="file",
                      description="Image source.",
                      mandatory=True,
                      example="src.nii.gz")
    parser.add_option(name="-d",
                      type_value="file",
                      description="Image destination.",
                      mandatory=True,
                      example="dest.nii.gz")
    parser.add_option(name="-iseg",
                      type_value="file",
                      description="Segmentation source.",
                      mandatory=False,
                      example="src_seg.nii.gz")
    parser.add_option(name="-dseg",
                      type_value="file",
                      description="Segmentation destination.",
                      mandatory=False,
                      example="dest_seg.nii.gz")
    parser.add_option(name="-ilabel",
                      type_value="file",
                      description="Labels source.",
                      mandatory=False)
    parser.add_option(name="-dlabel",
                      type_value="file",
                      description="Labels destination.",
                      mandatory=False)
    parser.add_option(name='-initwarp',
                      type_value='file',
                      description='Initial warping field to apply to the source image.',
                      mandatory=False)
    parser.add_option(name='-initwarpinv',
                      type_value='file',
                      description='Initial inverse warping field to apply to the destination image (only use if you wish to generate the dest->src warping field).',
                      mandatory=False)
    parser.add_option(name="-m",
                      type_value="file",
                      description="Mask that can be created with sct_create_mask to improve accuracy over region of interest. "
                                  "This mask will be used on the destination image.",
                      mandatory=False,
                      example="mask.nii.gz")
    parser.add_option(name="-o",
                      type_value="file_output",
                      description="Name of output file.",
                      mandatory=False,
                      example="src_reg.nii.gz")
    parser.add_option(name='-owarp',
                      type_value="file_output",
                      description="Name of output forward warping field.",
                      mandatory=False)
    parser.add_option(name="-param",
                      type_value=[[':'], 'str'],
                      description="Parameters for registration. Separate arguments with \",\". Separate steps with \":\".\n"
                                  "step: <int> Step number (starts at 1, except for type=label).\n"
                                  "type: {im, seg, imseg, label} type of data used for registration. Use type=label only at step=0.\n"
                                  "algo: Default=" + paramregmulti.steps['1'].algo + "\n"
                                                                                "  translation: translation in X-Y plane (2dof)\n"
                                                                                "  rigid: translation + rotation in X-Y plane (4dof)\n"
                                                                                "  affine: translation + rotation + scaling in X-Y plane (6dof)\n"
                                                                                "  syn: non-linear symmetric normalization\n"
                                                                                "  bsplinesyn: syn regularized with b-splines\n"
                                                                                "  slicereg: regularized translations (see: goo.gl/Sj3ZeU)\n"
                                                                                "  centermass: slicewise center of mass alignment (seg only).\n"
                                                                                "  centermassrot: slicewise center of mass and rotation alignment using method specified in 'rot_method'\n"
                                                                                "  columnwise: R-L scaling followed by A-P columnwise alignment (seg only).\n"
                                                                                "slicewise: <int> Slice-by-slice 2d transformation. Default=" +
                                  paramregmulti.steps['1'].slicewise + "\n"
                                                                  "metric: {CC,MI,MeanSquares}. Default=" +
                                  paramregmulti.steps['1'].metric + "\n"
                                                               "iter: <int> Number of iterations. Default=" +
                                  paramregmulti.steps['1'].iter + "\n"
                                                             "shrink: <int> Shrink factor (only for syn/bsplinesyn). Default=" +
                                  paramregmulti.steps['1'].shrink + "\n"
                                                               "smooth: <int> Smooth factor (in mm). Note: if algo={centermassrot,columnwise} the smoothing kernel is: SxSx0. Otherwise it is SxSxS. Default=" +
                                  paramregmulti.steps['1'].smooth + "\n"
                                                               "laplacian: <int> Laplacian filter. Default=" +
                                  paramregmulti.steps['1'].laplacian + "\n"
                                                                  "gradStep: <float> Gradient step. Default=" +
                                  paramregmulti.steps['1'].gradStep + "\n"
                                                                 "deformation: ?x?x?: Restrict deformation (for ANTs algo). Replace ? by 0 (no deformation) or 1 (deformation). Default=" +
                                  paramregmulti.steps['1'].deformation + "\n"
                                                                    "init: Initial translation alignment based on:\n"
                                                                    "  geometric: Geometric center of images\n"
                                                                    "  centermass: Center of mass of images\n"
                                                                    "  origin: Physical origin of images\n"
                                                                    "poly: <int> Polynomial degree of regularization (only for algo=slicereg). Default=" +
                                  paramregmulti.steps['1'].poly + "\n"
                                                                    "filter_size: <float> Filter size for regularization (only for algo=centermassrot). Default=" +
                                  str(paramregmulti.steps['1'].filter_size) + "\n"
                                                             "smoothWarpXY: <int> Smooth XY warping field (only for algo=columnwize). Default=" +
                                  paramregmulti.steps['1'].smoothWarpXY + "\n"
                                                                     "pca_eigenratio_th: <int> Min ratio between the two eigenvalues for PCA-based angular adjustment (only for algo=centermassrot and rot_method=pca). Default=" +
                                  paramregmulti.steps['1'].pca_eigenratio_th + "\n"
                                                                          "dof: <str> Degree of freedom for type=label. Separate with '_'. Default=" +
                                  paramregmulti.steps['0'].dof + "\n" +
                                  paramregmulti.steps['1'].rot_method + "\n"
                                                                    "rot_method {pca, hog, pcahog}: rotation method to be used with algo=centermassrot. pca: approximate cord segmentation by an ellipse and finds it orientation using PCA's eigenvectors; hog: finds the orientation using the symmetry of the image; pcahog: tries method pca and if it fails, uses method hog. If using hog or pcahog, type should be set to imseg.",
                      mandatory=False,
                      example="step=1,type=seg,algo=slicereg,metric=MeanSquares:step=2,type=im,algo=syn,metric=MI,iter=5,shrink=2")
    parser.add_option(name="-identity",
                      type_value="multiple_choice",
                      description="just put source into destination (no optimization).",
                      mandatory=False,
                      default_value='0',
                      example=['0', '1'])
    parser.add_option(name="-z",
                      type_value="int",
                      description="""size of z-padding to enable deformation at edges when using SyN.""",
                      mandatory=False,
                      default_value=Param().padding)
    parser.add_option(name="-x",
                      type_value="multiple_choice",
                      description="""Final interpolation.""",
                      mandatory=False,
                      default_value='linear',
                      example=['nn', 'linear', 'spline'])
    parser.add_option(name="-ofolder",
                      type_value="folder_creation",
                      description="Output folder",
                      mandatory=False,
                      example='reg_results/')
    parser.add_option(name='-qc',
                      type_value='folder_creation',
                      description='The path where the quality control generated content will be saved',
                      default_value=None)
    parser.add_option(name='-qc-dataset',
                      type_value='str',
                      description='If provided, this string will be mentioned in the QC report as the dataset the process was run on',
                      )
    parser.add_option(name='-qc-subject',
                      type_value='str',
                      description='If provided, this string will be mentioned in the QC report as the subject the process was run on',
                      )
    parser.add_option(name="-r",
                      type_value="multiple_choice",
                      description="""Remove temporary files.""",
                      mandatory=False,
                      default_value='1',
                      example=['0', '1'])
    parser.add_option(name="-v",
                      type_value="multiple_choice",
                      description="""Verbose.""",
                      mandatory=False,
                      default_value='1',
                      example=['0', '1', '2'])

    return parser
コード例 #2
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'
    elif '-lspinal' in arguments:
        fname_landmarks = arguments['-lspinal']
        label_type = 'spinal'
    else:
        sct.printv('ERROR: Labels should be provided.', 1, 'error')
    if '-ofolder' in arguments:
        path_output = arguments['-ofolder']
    else:
        path_output = ''

    param.path_qc = arguments.get("-qc", None)

    path_template = arguments['-t']
    contrast_template = arguments['-c']
    ref = arguments['-ref']
    param.remove_temp_files = int(arguments.get('-r'))
    verbose = int(arguments.get('-v'))
    sct.init_sct(log_level=verbose, update=True)  # Update log level
    param.verbose = verbose  # TODO: not clean, unify verbose or param.verbose in code, but not both
    param_centerline = ParamCenterline(
        algo_fitting=arguments['-centerline-algo'],
        smooth=arguments['-centerline-smooth'])
    # registration parameters
    if '-param' in arguments:
        # reset parameters but keep step=0 (might be overwritten if user specified step=0)
        paramregmulti = ParamregMultiStep([step0])
        if ref == 'subject':
            paramregmulti.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz'
        # add user parameters
        for paramStep in arguments['-param']:
            paramregmulti.addStep(paramStep)
    else:
        paramregmulti = ParamregMultiStep([step0, step1, step2])
        # if ref=subject, initialize registration using different affine parameters
        if ref == 'subject':
            paramregmulti.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz'

    # initialize other parameters
    zsubsample = param.zsubsample

    # retrieve template file names
    if label_type == 'spinal':
        file_template_labeling = get_file_label(
            os.path.join(path_template, 'template'),
            id_label=14)  # label = point-wise spinal level labels
    else:
        file_template_labeling = get_file_label(
            os.path.join(path_template, 'template'), id_label=7
        )  # label = spinal cord mask with discrete vertebral levels
    id_label_dct = {'T1': 0, 'T2': 1, 'T2S': 2}
    file_template = get_file_label(
        os.path.join(path_template, 'template'),
        id_label=id_label_dct[
            contrast_template.upper()])  # label = *-weighted template
    file_template_seg = get_file_label(
        os.path.join(path_template, 'template'),
        id_label=3)  # label = spinal cord mask (binary)

    # start timer
    start_time = time.time()

    # get fname of the template + template objects
    fname_template = os.path.join(path_template, 'template', file_template)
    fname_template_labeling = os.path.join(path_template, 'template',
                                           file_template_labeling)
    fname_template_seg = os.path.join(path_template, 'template',
                                      file_template_seg)
    fname_template_disc_labeling = os.path.join(path_template, 'template',
                                                'PAM50_label_disc.nii.gz')

    # check file existence
    # TODO: no need to do that!
    sct.printv('\nCheck template files...')
    sct.check_file_exist(fname_template, verbose)
    sct.check_file_exist(fname_template_labeling, verbose)
    sct.check_file_exist(fname_template_seg, verbose)
    path_data, file_data, ext_data = sct.extract_fname(fname_data)

    # sct.printv(arguments)
    sct.printv('\nCheck parameters:', verbose)
    sct.printv('  Data:                 ' + fname_data, verbose)
    sct.printv('  Landmarks:            ' + fname_landmarks, verbose)
    sct.printv('  Segmentation:         ' + fname_seg, verbose)
    sct.printv('  Path template:        ' + path_template, verbose)
    sct.printv('  Remove temp files:    ' + str(param.remove_temp_files),
               verbose)

    # check input labels
    labels = check_labels(fname_landmarks, label_type=label_type)

    level_alignment = False
    if len(labels) > 2 and label_type in ['disc', 'spinal']:
        level_alignment = True

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

    # set temporary file names
    ftmp_data = 'data.nii'
    ftmp_seg = 'seg.nii.gz'
    ftmp_label = 'label.nii.gz'
    ftmp_template = 'template.nii'
    ftmp_template_seg = 'template_seg.nii.gz'
    ftmp_template_label = 'template_label.nii.gz'

    # copy files to temporary folder
    sct.printv('\nCopying input data to tmp folder and convert to nii...',
               verbose)
    Image(fname_data).save(os.path.join(path_tmp, ftmp_data))
    Image(fname_seg).save(os.path.join(path_tmp, ftmp_seg))
    Image(fname_landmarks).save(os.path.join(path_tmp, ftmp_label))
    Image(fname_template).save(os.path.join(path_tmp, ftmp_template))
    Image(fname_template_seg).save(os.path.join(path_tmp, ftmp_template_seg))
    Image(fname_template_labeling).save(
        os.path.join(path_tmp, ftmp_template_label))
    if label_type == 'disc':
        Image(fname_template_disc_labeling).save(
            os.path.join(path_tmp, ftmp_template_label))

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

    # Generate labels from template vertebral labeling
    if label_type == 'body':
        sct.printv('\nGenerate labels from template vertebral labeling',
                   verbose)
        ftmp_template_label_, ftmp_template_label = ftmp_template_label, sct.add_suffix(
            ftmp_template_label, "_body")
        sct_label_utils.main(args=[
            '-i', ftmp_template_label_, '-vert-body', '0', '-o',
            ftmp_template_label
        ])

    # check if provided labels are available in the template
    sct.printv('\nCheck if provided labels are available in the template',
               verbose)
    image_label_template = Image(ftmp_template_label)
    labels_template = image_label_template.getNonZeroCoordinates(
        sorting='value')
    if labels[-1].value > labels_template[-1].value:
        sct.printv(
            'ERROR: Wrong landmarks input. Labels must have correspondence in template space. \nLabel max '
            'provided: ' + str(labels[-1].value) +
            '\nLabel max from template: ' + str(labels_template[-1].value),
            verbose, 'error')

    # if only one label is present, force affine transformation to be Tx,Ty,Tz only (no scaling)
    if len(labels) == 1:
        paramregmulti.steps['0'].dof = 'Tx_Ty_Tz'
        sct.printv(
            'WARNING: Only one label is present. Forcing initial transformation to: '
            + paramregmulti.steps['0'].dof, 1, 'warning')

    # Project labels onto the spinal cord centerline because later, an affine transformation is estimated between the
    # template's labels (centered in the cord) and the subject's labels (assumed to be centered in the cord).
    # If labels are not centered, mis-registration errors are observed (see issue #1826)
    ftmp_label = project_labels_on_spinalcord(ftmp_label, ftmp_seg,
                                              param_centerline)

    # binarize segmentation (in case it has values below 0 caused by manual editing)
    sct.printv('\nBinarize segmentation', verbose)
    ftmp_seg_, ftmp_seg = ftmp_seg, sct.add_suffix(ftmp_seg, "_bin")
    sct_maths.main(['-i', ftmp_seg_, '-bin', '0.5', '-o', ftmp_seg])

    # Switch between modes: subject->template or template->subject
    if ref == 'template':

        # resample data to 1mm isotropic
        sct.printv('\nResample data to 1mm isotropic...', verbose)
        resample_file(ftmp_data, add_suffix(ftmp_data, '_1mm'), '1.0x1.0x1.0',
                      'mm', 'linear', verbose)
        ftmp_data = add_suffix(ftmp_data, '_1mm')
        resample_file(ftmp_seg, add_suffix(ftmp_seg, '_1mm'), '1.0x1.0x1.0',
                      'mm', 'linear', verbose)
        ftmp_seg = add_suffix(ftmp_seg, '_1mm')
        # N.B. resampling of labels is more complicated, because they are single-point labels, therefore resampling
        # with nearest neighbour can make them disappear.
        resample_labels(ftmp_label, ftmp_data, add_suffix(ftmp_label, '_1mm'))
        ftmp_label = add_suffix(ftmp_label, '_1mm')

        # Change orientation of input images to RPI
        sct.printv('\nChange orientation of input images to RPI...', verbose)

        ftmp_data = Image(ftmp_data).change_orientation(
            "RPI", generate_path=True).save().absolutepath
        ftmp_seg = Image(ftmp_seg).change_orientation(
            "RPI", generate_path=True).save().absolutepath
        ftmp_label = Image(ftmp_label).change_orientation(
            "RPI", generate_path=True).save().absolutepath

        ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_crop')
        if level_alignment:
            # cropping the segmentation based on the label coverage to ensure good registration with level alignment
            # See https://github.com/neuropoly/spinalcordtoolbox/pull/1669 for details
            image_labels = Image(ftmp_label)
            coordinates_labels = image_labels.getNonZeroCoordinates(
                sorting='z')
            nx, ny, nz, nt, px, py, pz, pt = image_labels.dim
            offset_crop = 10.0 * pz  # cropping the image 10 mm above and below the highest and lowest label
            cropping_slices = [
                coordinates_labels[0].z - offset_crop,
                coordinates_labels[-1].z + offset_crop
            ]
            # make sure that the cropping slices do not extend outside of the slice range (issue #1811)
            if cropping_slices[0] < 0:
                cropping_slices[0] = 0
            if cropping_slices[1] > nz:
                cropping_slices[1] = nz
            msct_image.spatial_crop(
                Image(ftmp_seg_),
                dict(((2,
                       np.int32(np.round(cropping_slices))), ))).save(ftmp_seg)
        else:
            # if we do not align the vertebral levels, we crop the segmentation from top to bottom
            im_seg_rpi = Image(ftmp_seg_)
            bottom = 0
            for data in msct_image.SlicerOneAxis(im_seg_rpi, "IS"):
                if (data != 0).any():
                    break
                bottom += 1
            top = im_seg_rpi.data.shape[2]
            for data in msct_image.SlicerOneAxis(im_seg_rpi, "SI"):
                if (data != 0).any():
                    break
                top -= 1
            msct_image.spatial_crop(im_seg_rpi, dict(
                ((2, (bottom, top)), ))).save(ftmp_seg)

        # straighten segmentation
        sct.printv(
            '\nStraighten the spinal cord using centerline/segmentation...',
            verbose)

        # check if warp_curve2straight and warp_straight2curve already exist (i.e. no need to do it another time)
        fn_warp_curve2straight = os.path.join(curdir,
                                              "warp_curve2straight.nii.gz")
        fn_warp_straight2curve = os.path.join(curdir,
                                              "warp_straight2curve.nii.gz")
        fn_straight_ref = os.path.join(curdir, "straight_ref.nii.gz")

        cache_input_files = [ftmp_seg]
        if level_alignment:
            cache_input_files += [
                ftmp_template_seg,
                ftmp_label,
                ftmp_template_label,
            ]
        cache_sig = sct.cache_signature(input_files=cache_input_files, )
        cachefile = os.path.join(curdir, "straightening.cache")
        if sct.cache_valid(
                cachefile, cache_sig
        ) and os.path.isfile(fn_warp_curve2straight) and os.path.isfile(
                fn_warp_straight2curve) and os.path.isfile(fn_straight_ref):
            sct.printv(
                'Reusing existing warping field which seems to be valid',
                verbose, 'warning')
            sct.copy(fn_warp_curve2straight, 'warp_curve2straight.nii.gz')
            sct.copy(fn_warp_straight2curve, 'warp_straight2curve.nii.gz')
            sct.copy(fn_straight_ref, 'straight_ref.nii.gz')
            # apply straightening
            sct_apply_transfo.main(args=[
                '-i', ftmp_seg, '-w', 'warp_curve2straight.nii.gz', '-d',
                'straight_ref.nii.gz', '-o',
                add_suffix(ftmp_seg, '_straight')
            ])
        else:
            from spinalcordtoolbox.straightening import SpinalCordStraightener
            sc_straight = SpinalCordStraightener(ftmp_seg, ftmp_seg)
            sc_straight.param_centerline = param_centerline
            sc_straight.output_filename = add_suffix(ftmp_seg, '_straight')
            sc_straight.path_output = './'
            sc_straight.qc = '0'
            sc_straight.remove_temp_files = param.remove_temp_files
            sc_straight.verbose = verbose

            if level_alignment:
                sc_straight.centerline_reference_filename = ftmp_template_seg
                sc_straight.use_straight_reference = True
                sc_straight.discs_input_filename = ftmp_label
                sc_straight.discs_ref_filename = ftmp_template_label

            sc_straight.straighten()
            sct.cache_save(cachefile, cache_sig)

        # N.B. DO NOT UPDATE VARIABLE ftmp_seg BECAUSE TEMPORARY USED LATER
        # re-define warping field using non-cropped space (to avoid issue #367)
        sct_concat_transfo.main(args=[
            '-w', 'warp_straight2curve.nii.gz', '-d', ftmp_data, '-o',
            'warp_straight2curve.nii.gz'
        ])

        if level_alignment:
            sct.copy('warp_curve2straight.nii.gz',
                     'warp_curve2straightAffine.nii.gz')
        else:
            # Label preparation:
            # --------------------------------------------------------------------------------
            # Remove unused label on template. Keep only label present in the input label image
            sct.printv(
                '\nRemove unused label on template. Keep only label present in the input label image...',
                verbose)
            sct.run([
                'sct_label_utils', '-i', ftmp_template_label, '-o',
                ftmp_template_label, '-remove-reference', ftmp_label
            ])

            # Dilating the input label so they can be straighten without losing them
            sct.printv('\nDilating input labels using 3vox ball radius')
            sct_maths.main([
                '-i', ftmp_label, '-dilate', '3', '-o',
                add_suffix(ftmp_label, '_dilate')
            ])
            ftmp_label = add_suffix(ftmp_label, '_dilate')

            # Apply straightening to labels
            sct.printv('\nApply straightening to labels...', verbose)
            sct_apply_transfo.main(args=[
                '-i', ftmp_label, '-o',
                add_suffix(ftmp_label, '_straight'), '-d',
                add_suffix(ftmp_seg, '_straight'), '-w',
                'warp_curve2straight.nii.gz', '-x', 'nn'
            ])
            ftmp_label = add_suffix(ftmp_label, '_straight')

            # Compute rigid transformation straight landmarks --> template landmarks
            sct.printv('\nEstimate transformation for step #0...', verbose)
            try:
                register_landmarks(ftmp_label,
                                   ftmp_template_label,
                                   paramregmulti.steps['0'].dof,
                                   fname_affine='straight2templateAffine.txt',
                                   verbose=verbose)
            except RuntimeError:
                raise (
                    'Input labels do not seem to be at the right place. Please check the position of the labels. '
                    'See documentation for more details: https://www.slideshare.net/neuropoly/sct-course-20190121/42'
                )

            # Concatenate transformations: curve --> straight --> affine
            sct.printv(
                '\nConcatenate transformations: curve --> straight --> affine...',
                verbose)
            sct_concat_transfo.main(args=[
                '-w',
                ['warp_curve2straight.nii.gz', 'straight2templateAffine.txt'],
                '-d', 'template.nii', '-o', 'warp_curve2straightAffine.nii.gz'
            ])

        # Apply transformation
        sct.printv('\nApply transformation...', verbose)
        sct_apply_transfo.main(args=[
            '-i', ftmp_data, '-o',
            add_suffix(ftmp_data, '_straightAffine'), '-d', ftmp_template,
            '-w', 'warp_curve2straightAffine.nii.gz'
        ])
        ftmp_data = add_suffix(ftmp_data, '_straightAffine')
        sct_apply_transfo.main(args=[
            '-i', ftmp_seg, '-o',
            add_suffix(ftmp_seg, '_straightAffine'), '-d', ftmp_template, '-w',
            'warp_curve2straightAffine.nii.gz', '-x', 'linear'
        ])
        ftmp_seg = add_suffix(ftmp_seg, '_straightAffine')
        """
        # Benjamin: Issue from Allan Martin, about the z=0 slice that is screwed up, caused by the affine transform.
        # Solution found: remove slices below and above landmarks to avoid rotation effects
        points_straight = []
        for coord in landmark_template:
            points_straight.append(coord.z)
        min_point, max_point = int(np.round(np.min(points_straight))), int(np.round(np.max(points_straight)))
        ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_black')
        msct_image.spatial_crop(Image(ftmp_seg_), dict(((2, (min_point,max_point)),))).save(ftmp_seg)

        """
        # open segmentation
        im = Image(ftmp_seg)
        im_new = msct_image.empty_like(im)
        # binarize
        im_new.data = im.data > 0.5
        # find min-max of anat2template (for subsequent cropping)
        zmin_template, zmax_template = msct_image.find_zmin_zmax(im_new,
                                                                 threshold=0.5)
        # save binarized segmentation
        im_new.save(add_suffix(ftmp_seg, '_bin'))  # unused?
        # crop template in z-direction (for faster processing)
        # TODO: refactor to use python module instead of doing i/o
        sct.printv('\nCrop data in template space (for faster processing)...',
                   verbose)
        ftmp_template_, ftmp_template = ftmp_template, add_suffix(
            ftmp_template, '_crop')
        msct_image.spatial_crop(Image(ftmp_template_),
                                dict(
                                    ((2,
                                      (zmin_template,
                                       zmax_template)), ))).save(ftmp_template)

        ftmp_template_seg_, ftmp_template_seg = ftmp_template_seg, add_suffix(
            ftmp_template_seg, '_crop')
        msct_image.spatial_crop(
            Image(ftmp_template_seg_),
            dict(((2, (zmin_template,
                       zmax_template)), ))).save(ftmp_template_seg)

        ftmp_data_, ftmp_data = ftmp_data, add_suffix(ftmp_data, '_crop')
        msct_image.spatial_crop(Image(ftmp_data_),
                                dict(((2, (zmin_template,
                                           zmax_template)), ))).save(ftmp_data)

        ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_crop')
        msct_image.spatial_crop(Image(ftmp_seg_),
                                dict(((2, (zmin_template,
                                           zmax_template)), ))).save(ftmp_seg)

        # sub-sample in z-direction
        # TODO: refactor to use python module instead of doing i/o
        sct.printv('\nSub-sample in z-direction (for faster processing)...',
                   verbose)
        sct.run([
            'sct_resample', '-i', ftmp_template, '-o',
            add_suffix(ftmp_template, '_sub'), '-f', '1x1x' + zsubsample
        ], verbose)
        ftmp_template = add_suffix(ftmp_template, '_sub')
        sct.run([
            'sct_resample', '-i', ftmp_template_seg, '-o',
            add_suffix(ftmp_template_seg, '_sub'), '-f', '1x1x' + zsubsample
        ], verbose)
        ftmp_template_seg = add_suffix(ftmp_template_seg, '_sub')
        sct.run([
            'sct_resample', '-i', ftmp_data, '-o',
            add_suffix(ftmp_data, '_sub'), '-f', '1x1x' + zsubsample
        ], verbose)
        ftmp_data = add_suffix(ftmp_data, '_sub')
        sct.run([
            'sct_resample', '-i', ftmp_seg, '-o',
            add_suffix(ftmp_seg, '_sub'), '-f', '1x1x' + zsubsample
        ], verbose)
        ftmp_seg = add_suffix(ftmp_seg, '_sub')

        # Registration straight spinal cord to template
        sct.printv('\nRegister straight spinal cord to template...', verbose)

        # TODO: find a way to input initwarp, corresponding to straightening warp
        # Set the angle of the template orientation to 0 (destination image)
        for key in list(paramregmulti.steps.keys()):
            paramregmulti.steps[key].rot_dest = 0
        fname_src2dest, fname_dest2src, warp_forward, warp_inverse = register_wrapper(
            ftmp_data,
            ftmp_template,
            param,
            paramregmulti,
            fname_src_seg=ftmp_seg,
            fname_dest_seg=ftmp_template_seg,
            same_space=True)

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

        # Concatenate transformations: template --> anat
        sct.printv('\nConcatenate transformations: template --> anat...',
                   verbose)
        # TODO: make sure the commented code below is consistent with the new implementation
        # warp_inverse.reverse()
        if level_alignment:
            sct_concat_transfo.main(args=[
                '-w', [warp_inverse, 'warp_straight2curve.nii.gz'], '-d',
                'data.nii', '-o', 'warp_template2anat.nii.gz'
            ])
        else:
            sct_concat_transfo.main(args=[
                '-w',
                [
                    warp_inverse, 'straight2templateAffine.txt',
                    'warp_straight2curve.nii.gz'
                ], '-winv', ['straight2templateAffine.txt'], '-d', 'data.nii',
                '-o', 'warp_template2anat.nii.gz'
            ])

    # register template->subject
    elif ref == 'subject':

        # Change orientation of input images to RPI
        sct.printv('\nChange orientation of input images to RPI...', verbose)
        ftmp_data = Image(ftmp_data).change_orientation(
            "RPI", generate_path=True).save().absolutepath
        ftmp_seg = Image(ftmp_seg).change_orientation(
            "RPI", generate_path=True).save().absolutepath
        ftmp_label = Image(ftmp_label).change_orientation(
            "RPI", generate_path=True).save().absolutepath

        # Remove unused label on template. Keep only label present in the input label image
        sct.printv(
            '\nRemove unused label on template. Keep only label present in the input label image...',
            verbose)
        sct.run([
            'sct_label_utils', '-i', ftmp_template_label, '-o',
            ftmp_template_label, '-remove-reference', ftmp_label
        ])

        # Add one label because at least 3 orthogonal labels are required to estimate an affine transformation. This
        # new label is added at the level of the upper most label (lowest value), at 1cm to the right.
        for i_file in [ftmp_label, ftmp_template_label]:
            im_label = Image(i_file)
            coord_label = im_label.getCoordinatesAveragedByValue(
            )  # N.B. landmarks are sorted by value
            # Create new label
            from copy import deepcopy
            new_label = deepcopy(coord_label[0])
            # move it 5mm to the left (orientation is RAS)
            nx, ny, nz, nt, px, py, pz, pt = im_label.dim
            new_label.x = np.round(coord_label[0].x + 5.0 / px)
            # assign value 99
            new_label.value = 99
            # Add to existing image
            im_label.data[int(new_label.x),
                          int(new_label.y),
                          int(new_label.z)] = new_label.value
            # Overwrite label file
            # im_label.absolutepath = 'label_rpi_modif.nii.gz'
            im_label.save()
        # Set the angle of the template orientation to 0 (source image)
        for key in list(paramregmulti.steps.keys()):
            paramregmulti.steps[key].rot_src = 0
        fname_src2dest, fname_dest2src, warp_forward, warp_inverse = register_wrapper(
            ftmp_template,
            ftmp_data,
            param,
            paramregmulti,
            fname_src_seg=ftmp_template_seg,
            fname_dest_seg=ftmp_seg,
            fname_src_label=ftmp_template_label,
            fname_dest_label=ftmp_label,
            same_space=False)
        # Renaming for code compatibility
        os.rename(warp_forward, 'warp_template2anat.nii.gz')
        os.rename(warp_inverse, 'warp_anat2template.nii.gz')

    # Apply warping fields to anat and template
    sct.run([
        'sct_apply_transfo', '-i', 'template.nii', '-o',
        'template2anat.nii.gz', '-d', 'data.nii', '-w',
        'warp_template2anat.nii.gz', '-crop', '0'
    ], verbose)
    sct.run([
        'sct_apply_transfo', '-i', 'data.nii', '-o', 'anat2template.nii.gz',
        '-d', 'template.nii', '-w', 'warp_anat2template.nii.gz', '-crop', '0'
    ], verbose)

    # come back
    os.chdir(curdir)

    # Generate output files
    sct.printv('\nGenerate output files...', verbose)
    fname_template2anat = os.path.join(path_output, 'template2anat' + ext_data)
    fname_anat2template = os.path.join(path_output, 'anat2template' + ext_data)
    sct.generate_output_file(
        os.path.join(path_tmp, "warp_template2anat.nii.gz"),
        os.path.join(path_output, "warp_template2anat.nii.gz"), verbose)
    sct.generate_output_file(
        os.path.join(path_tmp, "warp_anat2template.nii.gz"),
        os.path.join(path_output, "warp_anat2template.nii.gz"), verbose)
    sct.generate_output_file(os.path.join(path_tmp, "template2anat.nii.gz"),
                             fname_template2anat, verbose)
    sct.generate_output_file(os.path.join(path_tmp, "anat2template.nii.gz"),
                             fname_anat2template, verbose)
    if ref == 'template':
        # copy straightening files in case subsequent SCT functions need them
        sct.generate_output_file(
            os.path.join(path_tmp, "warp_curve2straight.nii.gz"),
            os.path.join(path_output, "warp_curve2straight.nii.gz"), verbose)
        sct.generate_output_file(
            os.path.join(path_tmp, "warp_straight2curve.nii.gz"),
            os.path.join(path_output, "warp_straight2curve.nii.gz"), verbose)
        sct.generate_output_file(
            os.path.join(path_tmp, "straight_ref.nii.gz"),
            os.path.join(path_output, "straight_ref.nii.gz"), verbose)

    # Delete temporary files
    if param.remove_temp_files:
        sct.printv('\nDelete temporary files...', verbose)
        sct.rmtree(path_tmp, verbose=verbose)

    # display elapsed time
    elapsed_time = time.time() - start_time
    sct.printv(
        '\nFinished! Elapsed time: ' + str(int(np.round(elapsed_time))) + 's',
        verbose)

    qc_dataset = arguments.get("-qc-dataset", None)
    qc_subject = arguments.get("-qc-subject", None)
    if param.path_qc is not None:
        generate_qc(fname_data,
                    fname_in2=fname_template2anat,
                    fname_seg=fname_seg,
                    args=args,
                    path_qc=os.path.abspath(param.path_qc),
                    dataset=qc_dataset,
                    subject=qc_subject,
                    process='sct_register_to_template')
    sct.display_viewer_syntax([fname_data, fname_template2anat],
                              verbose=verbose)
    sct.display_viewer_syntax([fname_template, fname_anat2template],
                              verbose=verbose)
コード例 #3
0
def main(args=None):
    if args is None:
        args = sys.argv[1:]

    # initialize parameters
    param = Param()

    # Initialization
    fname_output = ''
    path_out = ''
    fname_src_seg = ''
    fname_dest_seg = ''
    fname_src_label = ''
    fname_dest_label = ''
    generate_warpinv = 1

    start_time = time.time()

    # get default registration parameters
    # step1 = Paramreg(step='1', type='im', algo='syn', metric='MI', iter='5', shrink='1', smooth='0', gradStep='0.5')
    step0 = Paramreg(step='0', type='im', algo='syn', metric='MI', iter='0', shrink='1', smooth='0', gradStep='0.5',
                     slicewise='0', dof='Tx_Ty_Tz_Rx_Ry_Rz')  # only used to put src into dest space
    step1 = Paramreg(step='1', type='im')
    paramregmulti = ParamregMultiStep([step0, step1])

    parser = get_parser(paramregmulti=paramregmulti)

    arguments = parser.parse(args)

    # get arguments
    fname_src = arguments['-i']
    fname_dest = arguments['-d']
    if '-iseg' in arguments:
        fname_src_seg = arguments['-iseg']
    if '-dseg' in arguments:
        fname_dest_seg = arguments['-dseg']
    if '-ilabel' in arguments:
        fname_src_label = arguments['-ilabel']
    if '-dlabel' in arguments:
        fname_dest_label = arguments['-dlabel']
    if '-o' in arguments:
        fname_output = arguments['-o']
    if '-ofolder' in arguments:
        path_out = arguments['-ofolder']
    if '-owarp' in arguments:
        fname_output_warp = arguments['-owarp']
    else:
        fname_output_warp = ''
    if '-initwarp' in arguments:
        fname_initwarp = os.path.abspath(arguments['-initwarp'])
    else:
        fname_initwarp = ''
    if '-initwarpinv' in arguments:
        fname_initwarpinv = os.path.abspath(arguments['-initwarpinv'])
    else:
        fname_initwarpinv = ''
    if '-m' in arguments:
        fname_mask = arguments['-m']
    else:
        fname_mask = ''
    padding = arguments['-z']
    if "-param" in arguments:
        paramregmulti_user = arguments['-param']
        # update registration parameters
        for paramStep in paramregmulti_user:
            paramregmulti.addStep(paramStep)
    path_qc = arguments.get("-qc", None)
    qc_dataset = arguments.get("-qc-dataset", None)
    qc_subject = arguments.get("-qc-subject", None)

    identity = int(arguments['-identity'])
    interp = arguments['-x']
    remove_temp_files = int(arguments['-r'])
    verbose = int(arguments.get('-v'))
    sct.init_sct(log_level=verbose, update=True)  # Update log level

    # sct.printv(arguments)
    sct.printv('\nInput parameters:')
    sct.printv('  Source .............. ' + fname_src)
    sct.printv('  Destination ......... ' + fname_dest)
    sct.printv('  Init transfo ........ ' + fname_initwarp)
    sct.printv('  Mask ................ ' + fname_mask)
    sct.printv('  Output name ......... ' + fname_output)
    # sct.printv('  Algorithm ........... '+paramregmulti.algo)
    # sct.printv('  Number of iterations  '+paramregmulti.iter)
    # sct.printv('  Metric .............. '+paramregmulti.metric)
    sct.printv('  Remove temp files ... ' + str(remove_temp_files))
    sct.printv('  Verbose ............. ' + str(verbose))

    # update param
    param.verbose = verbose
    param.padding = padding
    param.fname_mask = fname_mask
    param.remove_temp_files = remove_temp_files

    # Get if input is 3D
    sct.printv('\nCheck if input data are 3D...', verbose)
    sct.check_dim(fname_src, dim_lst=[3])
    sct.check_dim(fname_dest, dim_lst=[3])

    # Check if user selected type=seg, but did not input segmentation data
    if 'paramregmulti_user' in locals():
        if True in ['type=seg' in paramregmulti_user[i] for i in range(len(paramregmulti_user))]:
            if fname_src_seg == '' or fname_dest_seg == '':
                sct.printv('\nERROR: if you select type=seg you must specify -iseg and -dseg flags.\n', 1, 'error')

    # Put source into destination space using header (no estimation -- purely based on header)
    # TODO: Check if necessary to do that
    # TODO: use that as step=0
    # sct.printv('\nPut source into destination space using header...', verbose)
    # sct.run('isct_antsRegistration -d 3 -t Translation[0] -m MI[dest_pad.nii,src.nii,1,16] -c 0 -f 1 -s 0 -o
    # [regAffine,src_regAffine.nii] -n BSpline[3]', verbose)
    # if segmentation, also do it for seg

    fname_src2dest, fname_dest2src, _, _ = \
        register_wrapper(fname_src, fname_dest, param, paramregmulti, fname_src_seg=fname_src_seg,
                         fname_dest_seg=fname_dest_seg, fname_src_label=fname_src_label,
                         fname_dest_label=fname_dest_label, fname_mask=fname_mask, fname_initwarp=fname_initwarp,
                         fname_initwarpinv=fname_initwarpinv, identity=identity, interp=interp,
                         fname_output=fname_output,
                         fname_output_warp=fname_output_warp,
                         path_out=path_out)

    # display elapsed time
    elapsed_time = time.time() - start_time
    sct.printv('\nFinished! Elapsed time: ' + str(int(np.round(elapsed_time))) + 's', verbose)

    if path_qc is not None:
        if fname_dest_seg:
            generate_qc(fname_src2dest, fname_in2=fname_dest, fname_seg=fname_dest_seg, args=args,
                        path_qc=os.path.abspath(path_qc), dataset=qc_dataset, subject=qc_subject,
                        process='sct_register_multimodal')
        else:
            sct.printv('WARNING: Cannot generate QC because it requires destination segmentation.', 1, 'warning')

    if generate_warpinv:
        sct.display_viewer_syntax([fname_src, fname_dest2src], verbose=verbose)
    sct.display_viewer_syntax([fname_dest, fname_src2dest], verbose=verbose)
コード例 #4
0
# Note: step0 is used as pre-registration
step0 = Paramreg(
    step='0', type='label', dof='Tx_Ty_Tz_Sz'
)  # if ref=template, we only need translations and z-scaling because the cord is already straight
step1 = Paramreg(step='1',
                 type='imseg',
                 algo='centermassrot',
                 rot_method='pcahog')
step2 = Paramreg(step='2',
                 type='seg',
                 algo='bsplinesyn',
                 metric='MeanSquares',
                 iter='3',
                 smooth='1',
                 slicewise='0')
paramregmulti = ParamregMultiStep([step0, step1, step2])


# PARSER
# ==========================================================================================
def get_parser():
    param = Param()
    parser = Parser(__file__)
    parser.usage.set_description(
        'Register an anatomical image to the spinal cord MRI template (default: PAM50).\n\n'
        'The registration process includes three main registration steps:\n'
        '1. straightening of the image using the spinal cord segmentation (see sct_straighten_spinalcord for details);\n'
        '2. vertebral alignment between the image and the template, using labels along the spine;\n'
        '3. iterative slice-wise non-linear registration (see sct_register_multimodal for details)\n\n'
        'To register a subject to the template, try the default command:\n'
        'sct_register_to_template -i data.nii.gz -s data_seg.nii.gz -l data_labels.nii.gz\n\n'