def test_integrity(param_test):
    """
    Test integrity of function
    """

    fname_src = param_test.dict_args_with_path["-i"]
    fname_ref = param_test.dict_args_with_path["-d"]
    fname_dst = sct.add_suffix(os.path.basename(fname_src), "_reg")
    #fname_dst = "output.nii.gz"
    img_src = msct_image.Image(fname_src)
    img_ref = msct_image.Image(fname_ref)
    img_dst = msct_image.Image(fname_dst)

    if img_dst.orientation != img_ref.orientation:
        param_test.output += "\nImage has wrong orientation (%s -> %s)" \
         % (img_ref.orientation, img_dst.orientation)
        param_test.status = 1

    if len(img_src.data.shape) > 3:
        # Allowed failure for now
        return param_test

    if not (img_dst.data != 0).any():
        param_test.output += "\nImage is garbage (all zeros)"
        param_test.status = 1

    return param_test
示例#2
0
def test_transfo_skip_pix2phys():
    # "Recipe" useful if you want to skip pix2phys, which is *not* a good idea

    dir_tmp = "."

    print("Achieving a shifting of +1,+1,+1 (in LPI)")

    print(" Create data")

    path_src = "warp-src.nii"
    img_src = fake_3dimage_sct().save(path_src)


    print(" Create a warping field" \
"""
  Now we want to shift things by (+1,+1,+1), meaning
  that a destination voxel at position (x_1, y_1, z_1)
  contains the stuff that was in the source voxel at position
  (x_1-1, y_1-1, z_1-1).
""")
    shape = tuple(list(img_src.data.shape) + [1,3])

    data = np.ones(shape, order="F")
    data[...,2] *= -1 # invert Z so that the result is what we expect
    # See test_transfo_exhaustive_wrt_orientations()

    path_warp = "warp-field111.nii"
    img_warp = fake_image_sct_custom(data)
    img_warp.header.set_intent('vector', (), '')
    img_warp.save(path_warp)

    path_dst = "warp-dst111.nii"
    xform = sct_apply_transfo.Transform(path_src, path_warp, path_src, path_dst)
    xform.apply()

    img_src2 = msct_image.Image(path_src)
    img_dst = msct_image.Image(path_dst)

    print(" Check that the source wasn't altered")
    assert (img_src.data == img_src2.data).all()

    assert img_src.orientation == img_dst.orientation
    assert img_src.data.shape == img_dst.data.shape

    print(" Check the contents")
    dat_src = img_src.data
    dat_dst = np.array(img_dst.data)

    if 0:
        for idx_slice in range(shape[2]):
            print("")
            print(dat_src[...,idx_slice])
            print(dat_dst[...,idx_slice])

    # The volume should be shifted by 1 and the "lowest faces" of the destination
    # should be empty
    assert np.allclose(dat_dst[0,:,:], 0)
    assert np.allclose(dat_dst[:,0,:], 0)
    assert np.allclose(dat_dst[:,:,0], 0)
    assert np.allclose(dat_src[:-1,:-1,:-1], dat_dst[1:,1:,1:])
def main():
    import numpy as np
    import spinalcordtoolbox.image as msct_image

    parser = get_parser()
    args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])
    # Initialization
    fname_mt0 = ''
    fname_mt1 = ''
    fname_mtr = ''
    # register = param.register
    # remove_temp_files = param.remove_temp_files
    # verbose = param.verbose

    # Check input parameters
    fname_mt0 = args.mt0
    fname_mt1 = args.mt1
    fname_mtr = args.o
    verbose = args.v
    sct.init_sct(log_level=verbose, update=True)  # Update log level

    # compute MTR
    sct.printv('\nCompute MTR...', verbose)
    nii_mt1 = msct_image.Image(fname_mt1)
    data_mt1 = nii_mt1.data
    data_mt0 = msct_image.Image(fname_mt0).data
    data_mtr = 100 * (data_mt0 - data_mt1) / data_mt0
    # save MTR file
    nii_mtr = nii_mt1
    nii_mtr.data = data_mtr
    nii_mtr.save(fname_mtr)
    # sct.run(fsloutput+'fslmaths -dt double mt0.nii -sub mt1.nii -mul 100 -div mt0.nii -thr 0 -uthr 100 fname_mtr', verbose)

    sct.display_viewer_syntax([fname_mt0, fname_mt1, fname_mtr])
def test_tolerance_of_affine_mismatch_check():
    """Verify that affine mismatch error is thrown only for mismatches above a certain tolerance."""
    # ERROR NOT EXPECTED (Affine matrices have slight differences, but are close enough to be equivalent)
    # NB: Specific values taken from anonymized data from https://github.com/neuropoly/spinalcordtoolbox/issues/3251
    qform_affine = np.array([[-0.0000000613307, -0.0032542832702, -0.8999945527288, 36.8009071350098],
                             [-0.9322916865349, -0.0000000613307, 0.0000000594134, 214.2190246582031],
                             [0.0000000615451, -0.9322860067718, 0.0031415651366, 122.8873901367188],
                             [0.0000000000000, 0.0000000000000, 0.0000000000000, 1.0000000000000]])
    sform_affine = np.array([[-0.0000000000007, -0.0032542543486, -0.8999945521355, 36.8009071350098],
                             [-0.9322916865349, 0.0000000001912, -0.0000000000000, 214.2190246582031],
                             [-0.0000000001912, -0.9322860240936, 0.0031415862031, 122.8873901367188],
                             [0.0000000000000, 0.0000000000000, 0.0000000000000, 1.0000000000000]])
    header_e7 = nibabel.Nifti1Header()
    header_e7.set_sform(affine=sform_affine)
    header_e7.set_qform(affine=qform_affine)
    msct_image.Image(param=[1, 1, 1], hdr=header_e7, check_sform=True)

    # ERROR NOT EXPECTED (A bigger discrepancy is introduced, but it doesn't exceed the tolerance of the check)
    qform_affine_e3 = qform_affine.copy()
    qform_affine_e3[0, 0] += 1e-3
    header_e3 = nibabel.Nifti1Header()
    header_e3.set_sform(affine=sform_affine)
    header_e3.set_qform(affine=qform_affine_e3)
    msct_image.Image(param=[1, 1, 1], hdr=header_e3, check_sform=True)

    # ERROR EXPECTED (A bigger discrepancy is introduced, and it does exceed the tolerance of the check)
    qform_affine_e2 = qform_affine.copy()
    qform_affine_e2[0, 0] += 1e-2
    header_e2 = nibabel.Nifti1Header()
    header_e2.set_sform(affine=sform_affine)
    header_e2.set_qform(affine=qform_affine_e2)
    with pytest.raises(ValueError) as e:
        msct_image.Image(param=[1, 1, 1], hdr=header_e2, check_sform=True)
    assert "Image sform does not match qform" in str(e.value)
def label_vert(fname_seg, fname_label, verbose=1):
    """
    Label segmentation using vertebral labeling information. No orientation expected.
    :param fname_seg: file name of segmentation.
    :param fname_label: file name for a labelled segmentation that will be used to label the input segmentation
    :param fname_out: file name of the output labeled segmentation. If empty, will add suffix "_labeled" to fname_seg
    :param verbose:
    :return:
    """
    # Open labels
    im_disc = msct_image.Image(fname_label).change_orientation("RPI")
    # retrieve all labels
    coord_label = im_disc.getNonZeroCoordinates()
    # compute list_disc_z and list_disc_value
    list_disc_z = []
    list_disc_value = []
    for i in range(len(coord_label)):
        list_disc_z.insert(0, coord_label[i].z)
        # '-1' to use the convention "disc labelvalue=3 ==> disc C2/C3"
        list_disc_value.insert(0, coord_label[i].value - 1)

    list_disc_value = [x for (y, x) in sorted(zip(list_disc_z, list_disc_value), reverse=True)]
    list_disc_z = [y for (y, x) in sorted(zip(list_disc_z, list_disc_value), reverse=True)]
    # label segmentation
    from sct_label_vertebrae import label_segmentation
    label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=verbose)
def compute_shape(segmentation, algo_fitting='hanning', window_length=50, remove_temp_files=1, verbose=1):
    """
    This function characterizes the shape of the spinal cord, based on the segmentation
    Shape properties are computed along the spinal cord and averaged per z-slices.
    Option is to provide intervertebral disks to average shape properties over vertebral levels (fname_discs).
    WARNING: the segmentation needs to be binary.
    :param segmentation: input segmentation. Could be either an Image or a file name.
    :param algo_fitting:
    :param window_length:
    :param remove_temp_files:
    :param verbose:
    :return metrics: Dict of class Metric()
    """
    im_seg = msct_image.Image(segmentation).change_orientation('RPI')
    # Extract min and max index in Z direction
    data_seg = im_seg.data
    X, Y, Z = (data_seg > 0).nonzero()
    # min_z_index, max_z_index = min(Z), max(Z)

    shape_properties = msct_shape.compute_properties_along_centerline(im_seg=im_seg,
                                                                      smooth_factor=0.0,
                                                                      interpolation_mode=0,
                                                                      algo_fitting=algo_fitting,
                                                                      window_length=window_length,
                                                                      remove_temp_files=remove_temp_files,
                                                                      verbose=verbose)
    metrics = {}
    for key, value in shape_properties.items():
        # Making sure all entries added to metrics have results
        if not value == []:
            metrics[key] = Metric(data=np.array(value), label=key)

    return metrics
示例#7
0
def test_transfo_null():
    dir_tmp = "."

    print("Null warping field")

    print(" Create some recognizable data")
    data = np.ones((7, 8, 9), order="F")
    path_src = "warp-src.nii"
    img_src = fake_image_sct_custom(data).save(path_src)

    print(" Create a null warping field")
    data = np.zeros((7, 8, 9, 1, 3), order="F")
    path_warp = "warp-field.nii"
    img_warp = fake_image_sct_custom(data)
    img_warp.header.set_intent('vector', (), '')
    img_warp.save(path_warp)

    print(" Apply")
    path_dst = "warp-dst.nii"
    xform = sct_apply_transfo.Transform(input_filename=path_src,
                                        fname_dest=path_src,
                                        list_warp=[path_warp],
                                        output_filename=path_dst)
    xform.apply()

    img_src2 = msct_image.Image(path_src)
    img_dst = msct_image.Image(path_dst)

    print(" Check that the source wasn't altered")
    assert (img_src.data == img_src2.data).all()

    assert img_src.orientation == img_dst.orientation
    assert img_src.data.shape == img_dst.data.shape

    print(
        " Check that the destination is identical to the source since the field was null"
    )
    dat_src = img_src.data
    dat_dst = np.array(img_dst.data)
    for idx_slice, (slice_src, slice_dst) in enumerate(
            msct_image.SlicerMany((img_src, img_dst), msct_image.Slicer)):
        slice_src = np.array(slice_src)
        slice_dst = np.array(slice_dst)
        print(slice_src)
        print(slice_dst)
        assert np.allclose(slice_src, slice_dst)
    assert np.allclose(dat_src, dat_dst)
示例#8
0
def convert(fname_in, fname_out, squeeze_data=True, dtype=None, verbose=1):
    """
    Convert data
    :return True/False
    """
    printv('sct_convert -i ' + fname_in + ' -o ' + fname_out, verbose, 'code')

    img = image.Image(fname_in)
    img = image.convert(img, squeeze_data=squeeze_data, dtype=dtype)
    img.save(fname_out, mutable=True, verbose=verbose)
示例#9
0
def fake_3dimage_sct_custom(data):
    """
    :return: an Image (3D) in RAS+ (aka SCT LPI) space
    """
    i = fake_3dimage_custom(data)
    img = msct_image.Image(i.get_data(), hdr=i.header,
                           orientation="LPI",
                           dim=i.header.get_data_shape(),
                           )
    return img
示例#10
0
def convert(fname_in, fname_out, squeeze_data=True, dtype=None, verbose=1):
    """
    Convert data
    :return True/False
    """
    import spinalcordtoolbox.image as msct_image
    printv('sct_convert -i ' + fname_in + ' -o ' + fname_out, verbose, 'code')
    im = msct_image.Image(fname_in)
    if squeeze_data:
        im.data = np.squeeze(im.data)
    if dtype:
        im.change_type(dtype)
    im.save(fname_out, mutable=True, verbose=verbose)
    return im
示例#11
0
def test_change_shape(fake_3dimage_sct):

    # Add dimension
    im_src = fake_3dimage_sct
    shape = tuple(list(im_src.data.shape) + [1])
    im_dst = msct_image.change_shape(im_src, shape)
    path_tmp = tmp_create(basename="test_reshape")
    src_path = os.path.join(path_tmp, "src.nii")
    dst_path = os.path.join(path_tmp, "dst.nii")
    im_src.save(src_path)
    im_dst.save(dst_path)
    im_src = msct_image.Image(src_path)
    im_dst = msct_image.Image(dst_path)
    assert im_dst.data.shape == shape

    data_src = im_src.data
    data_dst = im_dst.data

    assert (data_dst.reshape(data_src.shape) == data_src).all()

    # Remove dimension
    im_dst = im_dst.change_shape(im_src.data.shape)
    assert im_dst.data.shape == im_src.data.shape
def generate_consensus_reading(fname_raterMasks_lst,
                               threshold=0.5,
                               fname_out='./consensus_reading.nii.gz'):
    '''
    This function generate a consensus reading mask from several segmentation mask of the same raw image.
    In the generated mask, 1 indicates that this voxel is part of the consensus reading, 0 otherwise.

    Input:
        - fname_raterMasks_lst [list]: list of nifti binary mask filenames.
            Note that they are suspected to all have the same size,
            since they are all the mask of the same raw image.
        - threshold [float]: value between 0. and 1.
            A voxel is labelled 1 in the output consensus mask
            if [threshold] of the raters segmented this voxel as lesion.
        - fname_out [string]: filename of the consensus reading mask generated.
    '''
    # compute the number of agreement needed to consider a voxel as part of the consensus reading mask
    threshold_n = int(threshold * len(fname_raterMasks_lst))

    # Sum all the rater segmentations
    data_sum = np.sum(np.asarray([
        msct_image.Image(fname_raterMask).data
        for fname_raterMask in fname_raterMasks_lst
    ]),
                      axis=0)

    # Threshold the resulting array according to the threshold
    data_thresh = np.zeros(data_sum.shape)
    data_thresh[np.where(data_sum >= threshold_n)] = 1

    # Save the resulting array as Image in the related ofolder
    im_consensus = msct_image.zeros_like(msct_image.Image(
        fname_raterMasks_lst[0]),
                                         dtype=np.uint8)
    im_consensus.data = data_thresh
    im_consensus.save(fname_out)
示例#13
0
def find_maxVert_perScan(data_folder):
    '''
    Opens each labelled image and find the max vertebral level available.
    Return a dictionary with the scan_name as key, the max vert as value.
    '''
    scan_max_vert_dct = {}
    for scan_foldname in os.listdir(data_folder):
        path_labeled_cord_img = os.path.join(
            data_folder, scan_foldname,
            scan_foldname + '_seg_manual_labeled.nii.gz')
        if not os.path.isfile(path_labeled_cord_img):
            path_labeled_cord_img = os.path.join(
                data_folder, scan_foldname,
                scan_foldname + '_seg_labeled.nii.gz')
        if os.path.isfile(path_labeled_cord_img):
            scan_max_vert_dct[scan_foldname] = np.max(
                np.unique(msct_image.Image(path_labeled_cord_img).data))
    return scan_max_vert_dct
示例#14
0
def main(argv=None):
    """
    Main function
    :param args:
    :return:
    """
    parser = get_parser()
    arguments = parser.parse_args(argv)
    verbose = arguments.v
    set_global_loglevel(verbose=verbose)

    # Building the command, do sanity checks
    fname_in = arguments.i
    fname_out = arguments.o
    squeeze_data = bool(arguments.squeeze)

    # convert file
    img = image.Image(fname_in)
    img = image.convert(img, squeeze_data=squeeze_data)
    img.save(fname_out, mutable=True, verbose=verbose)
示例#15
0
def compute_csa(segmentation,
                algo_fitting='bspline',
                angle_correction=True,
                use_phys_coord=True,
                remove_temp_files=1,
                verbose=1):
    """
    Compute CSA.
    Note: segmentation can be binary or weighted for partial volume effect.
    :param segmentation: input segmentation. Could be either an Image or a file name.
    :param algo_fitting:
    :param angle_correction:
    :param use_phys_coord:
    :return metrics: Dict of class process_seg.Metric()
    """
    # create temporary folder
    path_tmp = sct.tmp_create()
    # open image and save in temp folder
    im_seg = msct_image.Image(segmentation).save(path_tmp, )

    # change orientation to RPI
    im_seg.change_orientation('RPI')
    nx, ny, nz, nt, px, py, pz, pt = im_seg.dim
    fname_seg = os.path.join(path_tmp, 'segmentation_RPI.nii.gz')
    im_seg.save(fname_seg)

    # Extract min and max index in Z direction
    data_seg = im_seg.data
    X, Y, Z = (data_seg > 0).nonzero()
    min_z_index, max_z_index = min(Z), max(Z)

    # if angle correction is required, get segmentation centerline
    # Note: even if angle_correction=0, we should run the code below so that z_centerline_voxel is defined (later used
    # with option -vert). See #1791
    # TODO: check if we need use_phys_coord case with recent changes in centerline
    if use_phys_coord:
        # fit centerline, smooth it and return the first derivative (in physical space)
        _, arr_ctl, arr_ctl_der = get_centerline(im_seg,
                                                 algo_fitting=algo_fitting,
                                                 verbose=verbose)
        x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl
        x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = arr_ctl_der
        centerline = Centerline(x_centerline_fit.tolist(),
                                y_centerline_fit.tolist(),
                                z_centerline.tolist(),
                                x_centerline_deriv.tolist(),
                                y_centerline_deriv.tolist(),
                                z_centerline_deriv.tolist())

        # average centerline coordinates over slices of the image
        x_centerline_fit_rescorr, y_centerline_fit_rescorr, z_centerline_rescorr, x_centerline_deriv_rescorr, \
        y_centerline_deriv_rescorr, z_centerline_deriv_rescorr = centerline.average_coordinates_over_slices(im_seg)

        # compute Z axis of the image, in physical coordinate
        axis_X, axis_Y, axis_Z = im_seg.get_directions()

    else:
        # fit centerline, smooth it and return the first derivative (in voxel space but FITTED coordinates)
        _, arr_ctl, arr_ctl_der = get_centerline(im_seg,
                                                 algo_fitting=algo_fitting,
                                                 verbose=verbose)
        x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl
        x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = arr_ctl_der

        # # correct centerline fitted coordinates according to the data resolution
        # x_centerline_fit_rescorr, y_centerline_fit_rescorr, z_centerline_rescorr, \
        # x_centerline_deriv_rescorr, y_centerline_deriv_rescorr, z_centerline_deriv_rescorr = \
        #     x_centerline_fit * px, y_centerline_fit * py, z_centerline * pz, \
        #     x_centerline_deriv * px, y_centerline_deriv * py, z_centerline_deriv * pz
        #
        # axis_Z = [0.0, 0.0, 1.0]

    # Compute CSA
    sct.printv('\nCompute CSA...', verbose)

    # Initialize 1d array with nan. Each element corresponds to a slice.
    csa = np.full_like(np.empty(nz), np.nan, dtype=np.double)
    angles = np.full_like(np.empty(nz), np.nan, dtype=np.double)

    for iz in range(min_z_index, max_z_index + 1):
        if angle_correction:
            # in the case of problematic segmentation (e.g., non continuous segmentation often at the extremities),
            # display a warning but do not crash
            try:
                # normalize the tangent vector to the centerline (i.e. its derivative)
                tangent_vect = normalize(
                    np.array([
                        x_centerline_deriv[iz - min_z_index] * px,
                        y_centerline_deriv[iz - min_z_index] * py, pz
                    ]))

            except IndexError:
                sct.printv(
                    'WARNING: Your segmentation does not seem continuous, which could cause wrong estimations at the '
                    'problematic slices. Please check it, especially at the extremities.',
                    type='warning')

            # compute the angle between the normal vector of the plane and the vector z
            angle = np.arccos(np.vdot(tangent_vect, np.array([0, 0, 1])))
        else:
            angle = 0.0

        # compute the number of voxels, assuming the segmentation is coded for partial volume effect between 0 and 1.
        number_voxels = np.sum(data_seg[:, :, iz])

        # compute CSA, by scaling with voxel size (in mm) and adjusting for oblique plane
        csa[iz] = number_voxels * px * py * np.cos(angle)
        angles[iz] = math.degrees(angle)

    # Remove temporary files
    if remove_temp_files:
        sct.printv('\nRemove temporary files...')
        sct.rmtree(path_tmp)

    # prepare output
    metrics = {
        'csa': Metric(data=csa, label='CSA [mm^2]'),
        'angle': Metric(data=angles,
                        label='Angle between cord axis and z [deg]')
    }
    return metrics
示例#16
0
def merge_images(list_fname_src, fname_dest, list_fname_warp, param):
    """
    Merge multiple source images onto destination space. All images are warped to the destination space and then added.
    To deal with overlap during merging (e.g. one voxel in destination image is shared with two input images), the
    resulting voxel is divided by the sum of the partial volume of each image. For example, if src(x,y,z)=1 is mapped to
    dest(i,j,k) with a partial volume of 0.5 (because destination voxel is bigger), then its value after linear interpolation
    will be 0.5. To account for partial volume, the resulting voxel will be: dest(i,j,k) = 0.5*0.5/0.5 = 0.5.
    Now, if two voxels overlap in the destination space, let's say: src(x,y,z)=1 and src2'(x',y',z')=1, then the
    resulting value will be: dest(i,j,k) = (0.5*0.5 + 0.5*0.5) / (0.5+0.5) = 0.5. So this function acts like a weighted
    average operator, only in destination voxels that share multiple source voxels.

    Parameters
    ----------
    list_fname_src
    fname_dest
    list_fname_warp
    param

    Returns
    -------

    """

    # create temporary folder
    path_tmp = sct.tmp_create()

    # get dimensions of destination file
    nii_dest = msct_image.Image(fname_dest)

    # initialize variables
    data = np.zeros([
        nii_dest.dim[0], nii_dest.dim[1], nii_dest.dim[2],
        len(list_fname_src)
    ])
    partial_volume = np.zeros([
        nii_dest.dim[0], nii_dest.dim[1], nii_dest.dim[2],
        len(list_fname_src)
    ])
    data_merge = np.zeros([nii_dest.dim[0], nii_dest.dim[1], nii_dest.dim[2]])

    # loop across files
    i_file = 0
    for fname_src in list_fname_src:

        # apply transformation src --> dest
        sct_apply_transfo.main(args=[
            '-i', fname_src, '-d', fname_dest, '-w', list_fname_warp[i_file],
            '-x', param.interp, '-o', 'src_' + str(i_file) +
            '_template.nii.gz', '-v', param.verbose
        ])

        # create binary mask from input file by assigning one to all non-null voxels
        sct_maths.main(args=[
            '-i', fname_src, '-bin',
            str(param.almost_zero), '-o', 'src_' + str(i_file) +
            'native_bin.nii.gz'
        ])

        # apply transformation to binary mask to compute partial volume
        sct_apply_transfo.main(args=[
            '-i', 'src_' + str(i_file) + 'native_bin.nii.gz', '-d', fname_dest,
            '-w', list_fname_warp[i_file], '-x', param.interp, '-o', 'src_' +
            str(i_file) + '_template_partialVolume.nii.gz'
        ])

        # open data
        data[:, :, :, i_file] = msct_image.Image('src_' + str(i_file) +
                                                 '_template.nii.gz').data
        partial_volume[:, :, :, i_file] = msct_image.Image(
            'src_' + str(i_file) + '_template_partialVolume.nii.gz').data
        i_file += 1

    # merge files using partial volume information (and convert nan resulting from division by zero to zeros)
    data_merge = np.divide(np.sum(data * partial_volume, axis=3),
                           np.sum(partial_volume, axis=3))
    data_merge = np.nan_to_num(data_merge)

    # write result in file
    nii_dest.data = data_merge
    nii_dest.save(param.fname_out)

    # remove temporary folder
    if param.rm_tmp:
        sct.rmtree(path_tmp)
示例#17
0
def extract_metrics_scans_asPanda(data_folder, scan_max_vert_dct,
                                  shape_metrics_lst):
    '''
    Loops across each scan results and extract the values, per vertebral level.
    Returns a list of panda dataframes, one per scan with the following column: z_idx, lvl, csa and the shape_metrics_lst
    '''
    scan_results_pd_lst = []
    for scan_foldname, max_vert in sorted(scan_max_vert_dct.items(),
                                          key=operator.itemgetter(1)):
        path_scan_folder = os.path.join(data_folder, scan_foldname)
        if os.path.isdir(path_scan_folder):
            path_labeled_cord_img = os.path.join(
                path_scan_folder, scan_foldname + '_seg_manual_labeled.nii.gz')
            if not os.path.isfile(path_labeled_cord_img):
                path_labeled_cord_img = os.path.join(
                    path_scan_folder, scan_foldname + '_seg_labeled.nii.gz')

            path_shape_csv = os.path.join(
                path_scan_folder, 'metrics',
                scan_foldname + '_seg_manual_shape.csv')
            if not os.path.isfile(path_shape_csv):
                path_shape_csv = os.path.join(path_scan_folder, 'metrics',
                                              scan_foldname + '_seg_shape.csv')

            path_csa_pickle = os.path.join(path_scan_folder, 'metrics',
                                           'csa_per_slice.pickle')

            if os.path.isfile(path_labeled_cord_img) and os.path.isfile(
                    path_shape_csv) and os.path.isfile(path_csa_pickle):
                labeled_cord_img = msct_image.Image(path_labeled_cord_img)
                shape_pd = pd.read_csv(path_shape_csv)[[u'Unnamed: 0'] +
                                                       shape_metrics_lst]
                shape_pd = shape_pd.rename(columns={u'Unnamed: 0': 'z_idx'})
                csa_pd = pd.read_pickle(path_csa_pickle)[[
                    u'CSA (mm^2)', u'Slice (z)'
                ]]
                csa_pd = csa_pd.rename(columns={
                    u'Slice (z)': 'z_idx',
                    u'CSA (mm^2)': 'csa'
                })

                scan_results_tmp_dct = {}
                for m in ['z_idx', 'csa', 'lvl'] + shape_metrics_lst:
                    scan_results_tmp_dct[m] = []
                scan_results_tmp_pd = pd.DataFrame.from_dict(
                    scan_results_tmp_dct)

                for zz in range(labeled_cord_img.dim[1]):
                    lvl = np.max(np.unique(labeled_cord_img.data[:, zz, :]))
                    if lvl:
                        idx_tmp = len(scan_results_tmp_pd.index.values)
                        scan_results_tmp_pd.loc[idx_tmp, 'z_idx'] = zz
                        scan_results_tmp_pd.loc[idx_tmp, 'csa'] = csa_pd[
                            csa_pd.z_idx == zz].csa.values[0]
                        scan_results_tmp_pd.loc[idx_tmp, 'lvl'] = lvl
                        for m_s in shape_metrics_lst:
                            if len(shape_pd[shape_pd.z_idx == zz][m_s].values):
                                scan_results_tmp_pd.loc[
                                    idx_tmp, m_s] = shape_pd[shape_pd.z_idx ==
                                                             zz][m_s].values[0]

                scan_results_pd_lst.append(scan_results_tmp_pd)
                if max_vert == max(scan_max_vert_dct.values()):
                    break
    return scan_results_pd_lst
示例#18
0
def test_more_change_orientation(fake_3dimage_sct, fake_3dimage_sct_vis):
    path_tmp = tmp_create(basename="test_reorient")
    path_tmp = "."

    im_src = fake_3dimage_sct.copy()
    im_src.save(os.path.join(path_tmp, "src.nii"), mutable=True)

    print(im_src.orientation, im_src.data.shape)

    def orient2shape(orient):
        # test-data-specific thing
        letter2dim = dict(
            L=7,
            R=7,
            A=8,
            P=8,
            I=9,
            S=9,
        )
        return tuple([letter2dim[x] for x in orient])

    orientation = im_src.orientation  # LPI
    assert im_src.header.get_best_affine()[:3, 3].tolist() == [0, 0, 0]
    im_dst = msct_image.change_orientation(im_src, "RPI")
    print(im_dst.orientation, im_dst.data.shape)
    assert im_dst.data.shape == orient2shape("RPI")
    assert im_dst.header.get_best_affine()[:3, 3].tolist() == [7 - 1, 0, 0]

    # spot check
    orientation = im_src.orientation  # LPI
    im_dst = msct_image.change_orientation(im_src, "IRP")
    print(im_dst.orientation, im_dst.data.shape)
    assert im_dst.data.shape == orient2shape("IRP")

    # to & fro
    im_dst2 = msct_image.change_orientation(im_dst, orientation)
    print(im_dst2.orientation, im_dst2.data.shape)
    assert im_dst2.orientation == im_src.orientation
    assert im_dst2.data.shape == orient2shape(orientation)
    assert (im_dst2.data == im_src.data).all()
    assert np.allclose(im_src.header.get_best_affine(),
                       im_dst2.header.get_best_affine())

    #fn = os.path.join(path_tmp, "pouet.nii")
    im_ref = fake_3dimage_sct.copy()
    im_src = fake_3dimage_sct.copy()
    orientation = im_src.orientation
    im_src.change_orientation("ASR").change_orientation(orientation)
    assert im_src.orientation == im_ref.orientation
    assert (im_dst2.data == im_src.data).all()
    assert np.allclose(im_src.header.get_best_affine(),
                       im_ref.header.get_best_affine())

    im_dst2 = msct_image.change_orientation(im_dst, orientation)
    print(im_dst2.orientation, im_dst2.data.shape)
    assert im_dst2.orientation == im_src.orientation
    assert im_dst2.data.shape == orient2shape(orientation)
    assert (im_dst2.data == im_src.data).all()
    assert np.allclose(im_src.header.get_best_affine(),
                       im_dst2.header.get_best_affine())

    # copy
    im_dst = im_src.copy().change_orientation("IRP")
    assert im_dst.data.shape == orient2shape("IRP")
    print(im_dst.orientation, im_dst.data.shape)

    print("Testing orientation persistence")
    img = im_src.copy()
    orientation = img.orientation
    fn = os.path.join(path_tmp, "pouet.nii")
    img.change_orientation("PIR").save(fn)
    assert img.data.shape == orient2shape("PIR")
    img = msct_image.Image(fn)
    assert img.orientation == "PIR"
    assert img.data.shape == orient2shape("PIR")
    print(img.orientation, img.data.shape)

    # typical pattern
    img = fake_3dimage_sct_vis.copy()
    print(img.header.get_best_affine())
    orientation = img.orientation
    path_tmp = "."
    fn = os.path.join(path_tmp, "vis.nii")
    fn2 = img.save(fn, mutable=True).change_orientation(
        "ALS", generate_path=True).save().absolutepath
    img = msct_image.Image(fn2)
    assert img.orientation == "ALS"
    assert img.data.shape == orient2shape("ALS")
    print(img.header.get_best_affine())

    fn2 = img.save(fn, mutable=True).change_orientation(
        "RAS", generate_path=True).save().absolutepath
    img = msct_image.Image(fn2)
    assert img.orientation == "RAS"
    assert img.data.shape == orient2shape("RAS")
    print(img.header.get_best_affine())

    fn2 = img.save(fn, mutable=True).change_orientation(
        "RPI", generate_path=True).save().absolutepath
    img = msct_image.Image(fn2)
    assert img.orientation == "RPI"
    assert img.data.shape == orient2shape("RPI")
    print(img.header.get_best_affine())

    fn2 = img.save(fn, mutable=True).change_orientation(
        "PLI", generate_path=True).save().absolutepath
    img = msct_image.Image(fn2)
    assert img.orientation == "PLI"
    assert img.data.shape == orient2shape("PLI")
    print(img.header.get_best_affine())

    # print(src.header)
    possibilities = [
        "ASR",
        "SRA",
        "RAS",
    ]
    possibilities = msct_image.all_refspace_strings()
    for orientation in possibilities:
        dst = msct_image.change_orientation(im_src, orientation)
        # dst.save("pouet-{}.nii".format(dst.orientation))
        print(orientation, dst.orientation, dst.data.shape, dst.dim)
        assert orientation == dst.orientation
示例#19
0
def test_transfo_figure_out_ants_frame_exhaustive():

    dir_tmp = "."
    all_orientations = msct_image.all_refspace_strings()
    #all_orientations = ("LPS", "LPI")

    print("Wondering which orientation is native to ANTs")

    working_orientations = [] # there can't be only one...

    for orientation in all_orientations:
        print(" Shifting +1,+1,+1 (in {})".format(orientation))

        path_src = "warp-{}-src.nii".format(orientation)
        img_src = fake_3dimage_sct().change_orientation(orientation).save(path_src)

        # Create warping field
        shape = tuple(list(img_src.data.shape) + [1,3])
        data = np.ones(shape, order="F")
        path_warp = "warp-{}-field.nii".format(orientation)
        img_warp = fake_image_sct_custom(data)
        img_warp.header.set_intent('vector', (), '')
        img_warp.change_orientation(orientation).save(path_warp)
        print(" Affine:\n{}".format(img_warp.header.get_best_affine()))

        path_dst = "warp-{}-dst.nii".format(orientation)
        xform = sct_apply_transfo.Transform(path_src, path_warp, path_src, path_dst)
        xform.apply()

        img_src2 = msct_image.Image(path_src)
        img_dst = msct_image.Image(path_dst)

        assert img_src.orientation == img_dst.orientation
        assert img_src.data.shape == img_dst.data.shape

        dat_src = img_src.data
        dat_dst = np.array(img_dst.data)

        value = 222
        aff_src = img_dst.header.get_best_affine()
        aff_dst = img_dst.header.get_best_affine()
        pt_src = np.array(np.unravel_index(np.argmin(np.abs(dat_src - value)), dat_src.shape))#, order="F"))
        pt_dst = np.array(np.unravel_index(np.argmin(np.abs(dat_dst - value)), dat_dst.shape))#, order="F"))
        print("Point %s -> %s" % (pt_src, pt_dst))

        pos_src = np.matmul(aff_src, np.hstack((pt_src, [1])).reshape((4,1)))
        pos_dst = np.matmul(aff_dst, np.hstack((pt_dst, [1])).reshape((4,1)))

        displacement = (pos_dst - pos_src).T[:3]
        print("Displacement (physical): %s" % (displacement))
        displacement = pt_dst - pt_src
        print("Displacement (logical): %s" % (displacement))

        assert dat_src.shape == dat_dst.shape

        if 0:
            for idx_slice in range(9):
                print(dat_src[...,idx_slice])
                print(dat_dst[...,idx_slice])
                print("")

        try:
            # Check same as before
            assert np.allclose(dat_dst[0,:,:], 0)
            assert np.allclose(dat_dst[:,0,:], 0)
            assert np.allclose(dat_dst[:,:,0], 0)
            assert np.allclose(dat_src[:-1,:-1,:-1], dat_dst[1:,1:,1:])
            working_orientations.append(orientation)
        except AssertionError as e:
            continue
            print("\x1B[31;1m Failed in {}\x1B[0m".format(orientation))
            for idx_slice in range(shape[2]):
                print(dat_src[...,idx_slice])
                print(dat_dst[...,idx_slice])
                print("")

    print("-> Working orientation: {}".format(" ".join(working_orientations)))
def main(args=None):

    # Initialization
    # fname_anat = ''
    # fname_centerline = ''
    sigma = 3  # default value of the standard deviation for the Gaussian smoothing (in terms of number of voxels)
    param = Param()
    # remove_temp_files = param.remove_temp_files
    # verbose = param.verbose
    start_time = time.time()

    parser = get_parser()
    arguments = parser.parse(sys.argv[1:])

    fname_anat = arguments['-i']
    fname_centerline = arguments['-s']
    if '-smooth' in arguments:
        sigma = arguments['-smooth']
    if '-param' in arguments:
        param.update(arguments['-param'])
    if '-r' in arguments:
        remove_temp_files = int(arguments['-r'])
    if '-v' in arguments:
        verbose = int(arguments['-v'])

    # Display arguments
    sct.printv('\nCheck input arguments...')
    sct.printv('  Volume to smooth .................. ' + fname_anat)
    sct.printv('  Centerline ........................ ' + fname_centerline)
    sct.printv('  Sigma (mm) ........................ ' + str(sigma))
    sct.printv('  Verbose ........................... ' + str(verbose))

    # Check that input is 3D:
    from spinalcordtoolbox.image import Image
    nx, ny, nz, nt, px, py, pz, pt = Image(fname_anat).dim
    dim = 4  # by default, will be adjusted later
    if nt == 1:
        dim = 3
    if nz == 1:
        dim = 2
    if dim == 4:
        sct.printv(
            'WARNING: the input image is 4D, please split your image to 3D before smoothing spinalcord using :\n'
            'sct_image -i ' + fname_anat + ' -split t -o ' + fname_anat,
            verbose, 'warning')
        sct.printv('4D images not supported, aborting ...', verbose, 'error')

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

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

    # Copying input data to tmp folder
    sct.printv('\nCopying input data to tmp folder and convert to nii...',
               verbose)
    sct.copy(fname_anat, os.path.join(path_tmp, "anat" + ext_anat))
    sct.copy(fname_centerline,
             os.path.join(path_tmp, "centerline" + ext_centerline))

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

    # convert to nii format
    convert('anat' + ext_anat, 'anat.nii')
    convert('centerline' + ext_centerline, 'centerline.nii')

    # Change orientation of the input image into RPI
    sct.printv('\nOrient input volume to RPI orientation...')
    fname_anat_rpi = msct_image.Image("anat.nii") \
     .change_orientation("RPI", generate_path=True) \
     .save() \
     .absolutepath

    # Change orientation of the input image into RPI
    sct.printv('\nOrient centerline to RPI orientation...')
    fname_centerline_rpi = msct_image.Image("centerline.nii") \
     .change_orientation("RPI", generate_path=True) \
     .save() \
     .absolutepath

    # Straighten the spinal cord
    # straighten segmentation
    sct.printv('\nStraighten the spinal cord using centerline/segmentation...',
               verbose)
    cache_sig = sct.cache_signature(
        input_files=[fname_anat_rpi, fname_centerline_rpi],
        input_params={"x": "spline"},
    )
    cachefile = os.path.join(curdir, "straightening.cache")
    if sct.cache_valid(cachefile, cache_sig) and os.path.isfile(
            os.path.join(
                curdir, 'warp_curve2straight.nii.gz')) and os.path.isfile(
                    os.path.join(
                        curdir,
                        'warp_straight2curve.nii.gz')) and os.path.isfile(
                            os.path.join(curdir, 'straight_ref.nii.gz')):
        # if they exist, copy them into current folder
        sct.printv('Reusing existing warping field which seems to be valid',
                   verbose, 'warning')
        sct.copy(os.path.join(curdir, 'warp_curve2straight.nii.gz'),
                 'warp_curve2straight.nii.gz')
        sct.copy(os.path.join(curdir, 'warp_straight2curve.nii.gz'),
                 'warp_straight2curve.nii.gz')
        sct.copy(os.path.join(curdir, 'straight_ref.nii.gz'),
                 'straight_ref.nii.gz')
        # apply straightening
        sct.run([
            'sct_apply_transfo', '-i', fname_anat_rpi, '-w',
            'warp_curve2straight.nii.gz', '-d', 'straight_ref.nii.gz', '-o',
            'anat_rpi_straight.nii', '-x', 'spline'
        ], verbose)
    else:
        sct.run([
            'sct_straighten_spinalcord', '-i', fname_anat_rpi, '-o',
            'anat_rpi_straight.nii', '-s', fname_centerline_rpi, '-x',
            'spline', '-param', 'algo_fitting=' + param.algo_fitting
        ], verbose)
        sct.cache_save(cachefile, cache_sig)

    # Smooth the straightened image along z
    sct.printv('\nSmooth the straightened image along z...')
    sct.run([
        'sct_maths', '-i', 'anat_rpi_straight.nii', '-smooth',
        '0,0,' + str(sigma), '-o', 'anat_rpi_straight_smooth.nii'
    ], verbose)

    # Apply the reversed warping field to get back the curved spinal cord
    sct.printv(
        '\nApply the reversed warping field to get back the curved spinal cord...'
    )
    sct.run([
        'sct_apply_transfo', '-i', 'anat_rpi_straight_smooth.nii', '-o',
        'anat_rpi_straight_smooth_curved.nii', '-d', 'anat.nii', '-w',
        'warp_straight2curve.nii.gz', '-x', 'spline'
    ], verbose)

    # replace zeroed voxels by original image (issue #937)
    sct.printv('\nReplace zeroed voxels by original image...', verbose)
    nii_smooth = Image('anat_rpi_straight_smooth_curved.nii')
    data_smooth = nii_smooth.data
    data_input = Image('anat.nii').data
    indzero = np.where(data_smooth == 0)
    data_smooth[indzero] = data_input[indzero]
    nii_smooth.data = data_smooth
    nii_smooth.save('anat_rpi_straight_smooth_curved_nonzero.nii')

    # come back
    os.chdir(curdir)

    # Generate output file
    sct.printv('\nGenerate output file...')
    sct.generate_output_file(
        os.path.join(path_tmp, "anat_rpi_straight_smooth_curved_nonzero.nii"),
        file_anat + '_smooth' + ext_anat)

    # Remove temporary files
    if remove_temp_files == 1:
        sct.printv('\nRemove temporary files...')
        sct.rmtree(path_tmp)

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

    sct.display_viewer_syntax([file_anat, file_anat + '_smooth'],
                              verbose=verbose)
示例#21
0
def notest_transfo_more_exhaustive_wrt_orientations():

    dir_tmp = "."

    print("Figuring out which orientations work without workaround")

    all_orientations = msct_image.all_refspace_strings()

    orientations_ok = []
    orientations_ng = []
    orientations_dk = []

    for orientation_src in all_orientations:
        for orientation_ref in all_orientations:
            shift = np.array([1,2,3])
            shift_wanted = shift.copy()
            shift[2] *= -1 # ANTs / ITK reference frame is LPS, ours is LPI
            # (see docs or test_transfo_figure_out_ants_frame_exhaustive())

            print(" Shifting {} in {} ref {}".format(shift_wanted, orientation_src, orientation_ref))

            path_src = "warp2-{}.nii".format(orientation_src)
            img_src = fake_3dimage_sct().change_orientation(orientation_src).save(path_src)

            path_ref = "warp2-{}.nii".format(orientation_ref)
            img_ref = fake_3dimage_sct().change_orientation(orientation_ref).save(path_ref)


            # Create warping field
            shape = tuple(list(img_src.data.shape) + [1,3])
            data = np.zeros(shape, order="F")
            data[:,:,:,0] = shift

            path_warp = "warp-{}-{}-field.nii".format(orientation_src, orientation_ref)
            img_warp = fake_image_sct_custom(data)
            img_warp.header.set_intent('vector', (), '')
            img_warp.change_orientation(orientation_ref).save(path_warp)
            #print(" Affine:\n{}".format(img_warp.header.get_best_affine()))

            path_dst = "warp-{}-{}-dst.nii".format(orientation_src, orientation_ref)
            xform = sct_apply_transfo.Transform(path_src, path_warp, path_ref, path_dst)
            xform.apply()

            img_src2 = msct_image.Image(path_src)
            img_dst = msct_image.Image(path_dst)

            assert img_ref.orientation == img_dst.orientation
            assert img_ref.data.shape == img_dst.data.shape

            dat_src = img_src.data
            dat_dst = np.array(img_dst.data)

            value = 50505
            aff_src = img_src.header.get_best_affine()
            aff_dst = img_dst.header.get_best_affine()

            pt_src = np.argwhere(dat_src == value)[0]
            try:
                pt_dst = np.argwhere(dat_dst == value)[0]
                1/0
            except:
                # Work around numerical inaccuracy, that is somehow introduced by ANTs
                min_ = np.round(np.min(np.abs(dat_dst - value)), 1)
                pt_dst = np.array(np.unravel_index(np.argmin(np.abs(dat_dst - value)), dat_dst.shape))#, order="F"))

            print(" Point %s -> %s (%s) %s" % (pt_src, pt_dst, dat_dst[tuple(pt_dst)], min_))
            if min_ != 0:
                orientations_dk.append((orientation_src, orientation_ref))
                continue

            pos_src = np.matmul(aff_src, np.hstack((pt_src, [1])).reshape((4,1)))
            pos_dst = np.matmul(aff_dst, np.hstack((pt_dst, [1])).reshape((4,1)))

            displacement = (pos_dst - pos_src).reshape((-1))[:3]
            displacement_log = pt_dst - pt_src
            #print(" Displacement (logical): %s" % (displacement_log))
            if not np.allclose(displacement, shift_wanted):
                orientations_ng.append((orientation_src, orientation_ref))
                print(" \x1B[31;1mDisplacement (physical): %s\x1B[0m" % (displacement))
            else:
                orientations_ok.append((orientation_src, orientation_ref))
                print(" Displacement (physical): %s" % (displacement))
            print("")

    def ori_str(x):
        return " ".join(["{}->{}".format(x,y) for (x,y) in x])

    print("Orientations OK: {}".format(ori_str(orientations_ok)))
    print("Orientations NG: {}".format(ori_str(orientations_ng)))
    print("Orientations DK: {}".format(ori_str(orientations_dk)))
示例#22
0
 def setUp(self):
     self.image = msct_image.Image(sct_test_path('t2', 't2.nii.gz'))
     self.overlay = msct_image.Image(self.image)
     self.params = base.AnatomicalParams()
示例#23
0
    def apply(self):
        # Initialization
        fname_src = self.input_filename  # source image (moving)
        list_warp = self.list_warp  # list of warping fields
        fname_out = self.output_filename  # output
        fname_dest = self.fname_dest  # destination image (fix)
        verbose = self.verbose
        remove_temp_files = self.remove_temp_files
        crop_reference = self.crop  # if = 1, put 0 everywhere around warping field, if = 2, real crop

        interp = sct.get_interpolation('isct_antsApplyTransforms', self.interp)

        # Parse list of warping fields
        sct.printv('\nParse list of warping fields...', verbose)
        use_inverse = []
        fname_warp_list_invert = []
        # list_warp = list_warp.replace(' ', '')  # remove spaces
        # list_warp = list_warp.split(",")  # parse with comma
        for idx_warp, path_warp in enumerate(self.list_warp):
            # Check if this transformation should be inverted
            if path_warp in self.list_warpinv:
                use_inverse.append('-i')
                # list_warp[idx_warp] = path_warp[1:]  # remove '-'
                fname_warp_list_invert += [[
                    use_inverse[idx_warp], list_warp[idx_warp]
                ]]
            else:
                use_inverse.append('')
                fname_warp_list_invert += [[path_warp]]
            path_warp = list_warp[idx_warp]
            if path_warp.endswith((".nii", ".nii.gz")) \
             and msct_image.Image(list_warp[idx_warp]).header.get_intent()[0] != 'vector':
                raise ValueError("Displacement field in {} is invalid: should be encoded" \
                 " in a 5D file with vector intent code" \
                 " (see https://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h" \
                 .format(path_warp))
        # need to check if last warping field is an affine transfo
        isLastAffine = False
        path_fname, file_fname, ext_fname = sct.extract_fname(
            fname_warp_list_invert[-1][-1])
        if ext_fname in ['.txt', '.mat']:
            isLastAffine = True

        # check if destination file is 3d
        if not sct.check_if_3d(fname_dest):
            sct.printv('ERROR: Destination data must be 3d')

        # N.B. Here we take the inverse of the warp list, because sct_WarpImageMultiTransform concatenates in the reverse order
        fname_warp_list_invert.reverse()
        fname_warp_list_invert = functools.reduce(lambda x, y: x + y,
                                                  fname_warp_list_invert)

        # Extract path, file and extension
        path_src, file_src, ext_src = sct.extract_fname(fname_src)
        path_dest, file_dest, ext_dest = sct.extract_fname(fname_dest)

        # Get output folder and file name
        if fname_out == '':
            path_out = ''  # output in user's current directory
            file_out = file_src + '_reg'
            ext_out = ext_src
            fname_out = os.path.join(path_out, file_out + ext_out)

        # Get dimensions of data
        sct.printv('\nGet dimensions of data...', verbose)
        img_src = msct_image.Image(fname_src)
        nx, ny, nz, nt, px, py, pz, pt = img_src.dim
        # nx, ny, nz, nt, px, py, pz, pt = sct.get_dimension(fname_src)
        sct.printv(
            '  ' + str(nx) + ' x ' + str(ny) + ' x ' + str(nz) + ' x ' +
            str(nt), verbose)

        # if 3d
        if nt == 1:
            # Apply transformation
            sct.printv('\nApply transformation...', verbose)
            if nz in [0, 1]:
                dim = '2'
            else:
                dim = '3'
            sct.run([
                'isct_antsApplyTransforms', '-d', dim, '-i', fname_src, '-o',
                fname_out, '-t'
            ] + fname_warp_list_invert + ['-r', fname_dest] + interp,
                    verbose=verbose,
                    is_sct_binary=True)

        # if 4d, loop across the T dimension
        else:
            path_tmp = sct.tmp_create(basename="apply_transfo",
                                      verbose=verbose)

            # convert to nifti into temp folder
            sct.printv(
                '\nCopying input data to tmp folder and convert to nii...',
                verbose)
            img_src.save(os.path.join(path_tmp, "data.nii"))
            sct.copy(fname_dest, os.path.join(path_tmp, file_dest + ext_dest))
            fname_warp_list_tmp = []
            for fname_warp in list_warp:
                path_warp, file_warp, ext_warp = sct.extract_fname(fname_warp)
                sct.copy(fname_warp,
                         os.path.join(path_tmp, file_warp + ext_warp))
                fname_warp_list_tmp.append(file_warp + ext_warp)
            fname_warp_list_invert_tmp = fname_warp_list_tmp[::-1]

            curdir = os.getcwd()
            os.chdir(path_tmp)

            # split along T dimension
            sct.printv('\nSplit along T dimension...', verbose)

            im_dat = msct_image.Image('data.nii')
            im_header = im_dat.hdr
            data_split_list = sct_image.split_data(im_dat, 3)
            for im in data_split_list:
                im.save()

            # apply transfo
            sct.printv('\nApply transformation to each 3D volume...', verbose)
            for it in range(nt):
                file_data_split = 'data_T' + str(it).zfill(4) + '.nii'
                file_data_split_reg = 'data_reg_T' + str(it).zfill(4) + '.nii'

                status, output = sct.run([
                    'isct_antsApplyTransforms',
                    '-d',
                    '3',
                    '-i',
                    file_data_split,
                    '-o',
                    file_data_split_reg,
                    '-t',
                ] + fname_warp_list_invert_tmp + [
                    '-r',
                    file_dest + ext_dest,
                ] + interp,
                                         verbose,
                                         is_sct_binary=True)

            # Merge files back
            sct.printv('\nMerge file back...', verbose)
            import glob
            path_out, name_out, ext_out = sct.extract_fname(fname_out)
            # im_list = [Image(file_name) for file_name in glob.glob('data_reg_T*.nii')]
            # concat_data use to take a list of image in input, now takes a list of file names to open the files one by one (see issue #715)
            fname_list = glob.glob('data_reg_T*.nii')
            fname_list.sort()
            im_out = sct_image.concat_data(fname_list, 3, im_header['pixdim'])
            im_out.save(name_out + ext_out)

            os.chdir(curdir)
            sct.generate_output_file(
                os.path.join(path_tmp, name_out + ext_out), fname_out)
            # Delete temporary folder if specified
            if int(remove_temp_files):
                sct.printv('\nRemove temporary files...', verbose)
                sct.rmtree(path_tmp, verbose=verbose)

        # 2. crop the resulting image using dimensions from the warping field
        warping_field = fname_warp_list_invert[-1]
        # if last warping field is an affine transfo, we need to compute the space of the concatenate warping field:
        if isLastAffine:
            sct.printv(
                'WARNING: the resulting image could have wrong apparent results. You should use an affine transformation as last transformation...',
                verbose, 'warning')
        elif crop_reference == 1:
            ImageCropper(input_file=fname_out,
                         output_file=fname_out,
                         ref=warping_field,
                         background=0).crop()
            # sct.run('sct_crop_image -i '+fname_out+' -o '+fname_out+' -ref '+warping_field+' -b 0')
        elif crop_reference == 2:
            ImageCropper(input_file=fname_out,
                         output_file=fname_out,
                         ref=warping_field).crop()
            # sct.run('sct_crop_image -i '+fname_out+' -o '+fname_out+' -ref '+warping_field)

        sct.display_viewer_syntax([fname_dest, fname_out], verbose=verbose)
示例#24
0
def main(args=None):
    import numpy as np
    import spinalcordtoolbox.image as msct_image

    # Initialization
    fname_mt0 = ''
    fname_mt1 = ''
    file_out = param.file_out
    # register = param.register
    # remove_temp_files = param.remove_temp_files
    # verbose = param.verbose

    # check user arguments
    if not args:
        args = sys.argv[1:]

    # Check input parameters
    parser = get_parser()
    arguments = parser.parse(args)

    fname_mt0 = arguments['-mt0']
    fname_mt1 = arguments['-mt1']
    remove_temp_files = int(arguments['-r'])
    verbose = int(arguments['-v'])

    # Extract path/file/extension
    path_mt0, file_mt0, ext_mt0 = sct.extract_fname(fname_mt0)
    path_out, file_out, ext_out = '', file_out, ext_mt0

    # create temporary folder
    path_tmp = sct.tmp_create()

    # Copying input data to tmp folder and convert to nii
    sct.printv('\nCopying input data to tmp folder and convert to nii...',
               verbose)
    from sct_convert import convert
    convert(fname_mt0, os.path.join(path_tmp, "mt0.nii"), dtype=np.float32)
    convert(fname_mt1, os.path.join(path_tmp, "mt1.nii"), dtype=np.float32)

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

    # compute MTR
    sct.printv('\nCompute MTR...', verbose)
    nii_mt1 = msct_image.Image('mt1.nii')
    data_mt1 = nii_mt1.data
    data_mt0 = msct_image.Image('mt0.nii').data
    data_mtr = 100 * (data_mt0 - data_mt1) / data_mt0
    # save MTR file
    nii_mtr = nii_mt1
    nii_mtr.data = data_mtr
    nii_mtr.save("mtr.nii")
    # sct.run(fsloutput+'fslmaths -dt double mt0.nii -sub mt1.nii -mul 100 -div mt0.nii -thr 0 -uthr 100 mtr.nii', verbose)

    # come back
    os.chdir(curdir)

    # Generate output files
    sct.printv('\nGenerate output files...', verbose)
    sct.generate_output_file(os.path.join(path_tmp, "mtr.nii"),
                             os.path.join(path_out, file_out + ext_out))

    # Remove temporary files
    if remove_temp_files == 1:
        sct.printv('\nRemove temporary files...')
        sct.rmtree(path_tmp)

    sct.display_viewer_syntax([fname_mt0, fname_mt1, file_out])
示例#25
0
 def setUp(self):
     self.image = msct_image.Image(
         os.path.expanduser('~/sct_example_data/t2/t2.nii.gz'))
     self.overlay = msct_image.Image(self.image)
     self.params = base.AnatomicalParams()
def extract_centerline(segmentation, verbose=0, algo_fitting='hanning', type_window='hanning',
                       window_length=5, use_phys_coord=True, file_out='centerline'):
    """
    Extract centerline from a binary or weighted segmentation by computing the center of mass slicewise.
    :param segmentation: input segmentation. Could be either an Image or a file name.
    :param verbose:
    :param algo_fitting:
    :param type_window:
    :param window_length:
    :param use_phys_coord: TODO: Explain the pros/cons of use_phys_coord.
    :param file_out:
    :return: None
    """
    # TODO: output continuous centerline (and add in unit test)
    # TODO: centerline coordinate should have the same orientation as the input image
    # TODO: no need for unecessary i/o. Everything could be done in RAM

    # Create temp folder
    path_tmp = sct.tmp_create()
    # Open segmentation volume
    im_seg = msct_image.Image(segmentation)
    # im_seg.change_orientation('RPI', generate_path=True)
    native_orientation = im_seg.orientation
    im_seg.change_orientation("RPI", generate_path=True).save(path_tmp, mutable=True)
    fname_tmp_seg = im_seg.absolutepath

    # extract centerline and smooth it
    if use_phys_coord:
        # fit centerline, smooth it and return the first derivative (in physical space)
        x_centerline_fit, y_centerline_fit, z_centerline, \
        x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
            fname_tmp_seg, algo_fitting=algo_fitting, type_window=type_window, window_length=window_length,
            nurbs_pts_number=3000, phys_coordinates=True, verbose=verbose, all_slices=False)
        centerline = Centerline(x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv,
                                y_centerline_deriv, z_centerline_deriv)

        # average centerline coordinates over slices of the image (floating point)
        x_centerline_fit_rescorr, y_centerline_fit_rescorr, z_centerline_rescorr, \
        x_centerline_deriv_rescorr, y_centerline_deriv_rescorr, z_centerline_deriv_rescorr = \
            centerline.average_coordinates_over_slices(im_seg)

        # compute z_centerline in image coordinates (discrete)
        voxel_coordinates = im_seg.transfo_phys2pix(
            [[x_centerline_fit_rescorr[i], y_centerline_fit_rescorr[i], z_centerline_rescorr[i]] for i in
             range(len(z_centerline_rescorr))])
        x_centerline_voxel = [coord[0] for coord in voxel_coordinates]
        y_centerline_voxel = [coord[1] for coord in voxel_coordinates]
        z_centerline_voxel = [coord[2] for coord in voxel_coordinates]

    else:
        # fit centerline, smooth it and return the first derivative (in voxel space but FITTED coordinates)
        x_centerline_voxel, y_centerline_voxel, z_centerline_voxel, \
        x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
            'segmentation_RPI.nii.gz', algo_fitting=algo_fitting, type_window=type_window, window_length=window_length,
            nurbs_pts_number=3000, phys_coordinates=False, verbose=verbose, all_slices=True)

    if verbose == 2:
        # TODO: code below does not work
        import matplotlib.pyplot as plt

        # Creation of a vector x that takes into account the distance between the labels
        nz_nonz = len(z_centerline_voxel)
        x_display = [0 for i in range(x_centerline_voxel.shape[0])]
        y_display = [0 for i in range(y_centerline_voxel.shape[0])]
        for i in range(0, nz_nonz, 1):
            x_display[int(z_centerline_voxel[i] - z_centerline_voxel[0])] = x_centerline[i]
            y_display[int(z_centerline_voxel[i] - z_centerline_voxel[0])] = y_centerline[i]

        plt.figure(1)
        plt.subplot(2, 1, 1)
        plt.plot(z_centerline_voxel, x_display, 'ro')
        plt.plot(z_centerline_voxel, x_centerline_voxel)
        plt.xlabel("Z")
        plt.ylabel("X")
        plt.title("x and x_fit coordinates")

        plt.subplot(2, 1, 2)
        plt.plot(z_centerline_voxel, y_display, 'ro')
        plt.plot(z_centerline_voxel, y_centerline_voxel)
        plt.xlabel("Z")
        plt.ylabel("Y")
        plt.title("y and y_fit coordinates")
        plt.show()

    # Create an image with the centerline
    # TODO: write the center of mass, not the discrete image coordinate (issue #1938)
    im_centerline = im_seg.copy()
    data_centerline = im_centerline.data * 0
    # Find z-boundaries above which and below which there is no non-null slices
    min_z_index, max_z_index = int(round(min(z_centerline_voxel))), int(round(max(z_centerline_voxel)))
    # loop across slices and set centerline pixel to value=1
    for iz in range(min_z_index, max_z_index + 1):
        data_centerline[int(round(x_centerline_voxel[iz - min_z_index])),
                        int(round(y_centerline_voxel[iz - min_z_index])),
                        int(iz)] = 1
    # assign data to centerline image
    im_centerline.data = data_centerline
    # reorient centerline to native orientation
    im_centerline.change_orientation(native_orientation)
    # save nifti volume
    fname_centerline = file_out + '.nii.gz'
    im_centerline.save(fname_centerline, dtype='uint8')
    # display stuff
    # sct.display_viewer_syntax([fname_segmentation, fname_centerline], colormaps=['gray', 'green'])

    # output csv with centerline coordinates
    fname_centerline_csv = file_out + '.csv'
    f_csv = open(fname_centerline_csv, 'w')
    f_csv.write('x,y,z\n')  # csv header
    for i in range(min_z_index, max_z_index + 1):
        f_csv.write("%d,%d,%d\n" % (int(i),
                                    x_centerline_voxel[i - min_z_index],
                                    y_centerline_voxel[i - min_z_index]))
    f_csv.close()
    # TODO: display open syntax for csv

    # create a .roi file
    fname_roi_centerline = optic.centerline2roi(fname_image=fname_centerline,
                                                folder_output='./',
                                                verbose=verbose)
示例#27
0
def compute_properties_along_centerline(fname_seg_image,
                                        property_list,
                                        fname_disks_image=None,
                                        smooth_factor=5.0,
                                        interpolation_mode=0,
                                        remove_temp_files=1,
                                        verbose=1):

    # Check list of properties
    # If diameters is in the list, compute major and minor axis length and check orientation
    compute_diameters = False
    property_list_local = list(property_list)
    if 'diameters' in property_list_local:
        compute_diameters = True
        property_list_local.remove('diameters')
        property_list_local.append('major_axis_length')
        property_list_local.append('minor_axis_length')
        property_list_local.append('orientation')

    # TODO: make sure fname_segmentation and fname_disks are in the same space
    path_tmp = sct.tmp_create(basename="compute_properties_along_centerline",
                              verbose=verbose)

    image = msct_image.Image(fname_seg_image) \
     .change_orientation("RPI", generate_path=True) \
     .save(path_tmp, mutable=True)
    fname_segmentation_orient = image.absolutepath

    if fname_disks_image is not None:
        image_disks = msct_image.Image(fname_disks_image) \
         .change_orientation("RPI", generate_path=True) \
         .save(path_tmp, mutable=True)
        fname_disks_orient = image_disks.absolutepath

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

    # Initiating some variables
    nx, ny, nz, nt, px, py, pz, pt = image.dim
    resolution = 0.5
    properties = {key: [] for key in property_list_local}
    properties['incremental_length'] = []
    properties['distance_from_C1'] = []
    properties['vertebral_level'] = []
    properties['z_slice'] = []

    # compute the spinal cord centerline based on the spinal cord segmentation
    number_of_points = 5 * nz
    x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
        fname_segmentation_orient,
        algo_fitting='nurbs',
        verbose=verbose,
        nurbs_pts_number=number_of_points,
        all_slices=False,
        phys_coordinates=True,
        remove_outliers=True)
    centerline = Centerline(x_centerline_fit, y_centerline_fit, z_centerline,
                            x_centerline_deriv, y_centerline_deriv,
                            z_centerline_deriv)

    # Compute vertebral distribution along centerline based on position of intervertebral disks
    if fname_disks_image is not None:
        coord = image_disks.getNonZeroCoordinates(sorting='z',
                                                  reverse_coord=True)
        coord_physical = []
        for c in coord:
            c_p = image_disks.transfo_pix2phys([[c.x, c.y, c.z]])[0]
            c_p.append(c.value)
            coord_physical.append(c_p)
        centerline.compute_vertebral_distribution(coord_physical)

    sct.printv('Computing spinal cord shape along the spinal cord...')
    with tqdm.tqdm(total=centerline.number_of_points) as pbar:

        # Extracting patches perpendicular to the spinal cord and computing spinal cord shape
        for index in range(centerline.number_of_points):
            # value_out = -5.0
            value_out = 0.0
            current_patch = centerline.extract_perpendicular_square(
                image,
                index,
                resolution=resolution,
                interpolation_mode=interpolation_mode,
                border='constant',
                cval=value_out)

            # check for pixels close to the spinal cord segmentation that are out of the image
            from skimage.morphology import dilation
            patch_zero = np.copy(current_patch)
            patch_zero[patch_zero == value_out] = 0.0
            patch_borders = dilation(patch_zero) - patch_zero
            """
            if np.count_nonzero(patch_borders + current_patch == value_out + 1.0) != 0:
                c = image.transfo_phys2pix([centerline.points[index]])[0]
                print('WARNING: no patch for slice', c[2])
                continue
            """

            sc_properties = properties2d(patch_zero, [resolution, resolution])
            if sc_properties is not None:
                properties['incremental_length'].append(
                    centerline.incremental_length[index])
                if fname_disks_image is not None:
                    properties['distance_from_C1'].append(
                        centerline.dist_points[index])
                    properties['vertebral_level'].append(
                        centerline.l_points[index])
                properties['z_slice'].append(
                    image.transfo_phys2pix([centerline.points[index]])[0][2])
                for property_name in property_list_local:
                    properties[property_name].append(
                        sc_properties[property_name])
            else:
                c = image.transfo_phys2pix([centerline.points[index]])[0]
                print('WARNING: no properties for slice', c[2])

            pbar.update(1)

    # Adding centerline to the properties for later use
    properties['centerline'] = centerline

    # We assume that the major axis is in the right-left direction
    # this script checks the orientation of the spinal cord and invert axis if necessary to make sure the major axis is right-left
    if compute_diameters:
        diameter_major = properties['major_axis_length']
        diameter_minor = properties['minor_axis_length']
        orientation = properties['orientation']
        for i, orientation_item in enumerate(orientation):
            if -45.0 < orientation_item < 45.0:
                continue
            else:
                temp = diameter_minor[i]
                properties['minor_axis_length'][i] = diameter_major[i]
                properties['major_axis_length'][i] = temp

        properties['RL_diameter'] = properties['major_axis_length']
        properties['AP_diameter'] = properties['minor_axis_length']
        del properties['major_axis_length']
        del properties['minor_axis_length']

    # smooth the spinal cord shape with a gaussian kernel if required
    # TODO: not all properties can be smoothed
    if smooth_factor != 0.0:  # smooth_factor is in mm
        import scipy
        window = scipy.signal.hann(smooth_factor /
                                   np.mean(centerline.progressive_length))
        for property_name in property_list_local:
            properties[property_name] = scipy.signal.convolve(
                properties[property_name], window,
                mode='same') / np.sum(window)

    if compute_diameters:
        property_list_local.remove('major_axis_length')
        property_list_local.remove('minor_axis_length')
        property_list_local.append('RL_diameter')
        property_list_local.append('AP_diameter')
        property_list = property_list_local

    # Display properties on the referential space. Requires intervertebral disks
    if verbose == 2:
        x_increment = 'distance_from_C1'
        if fname_disks_image is None:
            x_increment = 'incremental_length'

        # Display the image and plot all contours found
        fig, axes = plt.subplots(len(property_list_local),
                                 sharex=True,
                                 sharey=False)
        for k, property_name in enumerate(property_list_local):
            axes[k].plot(properties[x_increment], properties[property_name])
            axes[k].set_ylabel(property_name)

        if fname_disks_image is not None:
            properties[
                'distance_disk_from_C1'] = centerline.distance_from_C1label  # distance between each disk and C1 (or first disk)
            xlabel_disks = [
                centerline.convert_vertlabel2disklabel[label]
                for label in properties['distance_disk_from_C1']
            ]
            xtick_disks = [
                properties['distance_disk_from_C1'][label]
                for label in properties['distance_disk_from_C1']
            ]
            plt.xticks(xtick_disks, xlabel_disks, rotation=30)
        else:
            axes[-1].set_xlabel('Position along the spinal cord (in mm)')

        plt.show()

    # Removing temporary folder
    os.chdir(curdir)
    if remove_temp_files:
        sct.rmtree(path_tmp)

    return property_list, properties