示例#1
0
def label_discs(fname_seg, list_disc_z, list_disc_value, verbose=1):
    """
    Create file with single voxel label in the middle of the spinal cord for each disc.

    :param fname_seg: fname of the segmentation, no orientation expected
    :param list_disc_z: list of z that correspond to a disc
    :param list_disc_value: list of associated disc values
    :param verbose:
    :return:
    """
    seg = Image(fname_seg)
    init_orientation = seg.orientation
    seg.change_orientation("RPI")
    disc_data = np.zeros_like(seg.data)
    nx, ny, nz = seg.data.shape

    for i in range(len(list_disc_z)):
        if list_disc_z[i] < nz:
            try:
                slices = seg.data[:, :, list_disc_z[i]]
                cx, cy = [int(x) for x in np.round(center_of_mass(slices)).tolist()]
            except EmptyArrayError:
                logger.warning("During disc labeling, center of mass calculation failed due to discontinuities in "
                               "segmented spinal cord; please check the quality of your segmentation. Using "
                               "interpolated centerline as a fallback.")
                interpolated_centerline, _, _, _ = get_centerline(seg)
                slices = interpolated_centerline.data[:, :, list_disc_z[i]]
                cx, cy = [int(x) for x in np.round(center_of_mass(slices)).tolist()]

            # Disc value are offset by one due to legacy code
            disc_data[cx, cy, list_disc_z[i]] = list_disc_value[i] + 1

    seg.data = disc_data
    seg.change_orientation(init_orientation).save(add_suffix(fname_seg, '_labeled_disc'))
示例#2
0
def label_discs(fname_seg, list_disc_z, list_disc_value, verbose=1):
    """
    Create file with single voxel label in the middle of the spinal cord for each disc.

    :param fname_seg: fname of the segmentation, no orientation expected
    :param list_disc_z: list of z that correspond to a disc
    :param list_disc_value: list of associated disc values
    :param verbose:
    :return:
    """
    seg = Image(fname_seg)
    init_orientation = seg.orientation
    seg.change_orientation("RPI")
    disc_data = np.zeros_like(seg.data)
    nx, ny, nz = seg.data.shape

    for i in range(len(list_disc_z)):
        if list_disc_z[i] < nz:
            slices = seg.data[:, :, list_disc_z[i]]
            cx, cy = [
                int(x) for x in np.round(center_of_mass(slices)).tolist()
            ]
            # Disc value are offset by one due to legacy code
            disc_data[cx, cy, list_disc_z[i]] = list_disc_value[i] + 1

    seg.data = disc_data
    seg.change_orientation(init_orientation).save(
        add_suffix(fname_seg, '_labeled_disc'))
示例#3
0
def increment_z_inverse(img: Image) -> Image:
    """
    Take all non-zero values, sort them along the inverse z direction, and attributes the values 1,
    2, 3, etc.

    :param img: source image
    :returns: image with non-zero values sorted along inverse z
    """
    og_orientation = img.orientation
    if og_orientation != "RPI":
        img.change_orientation("RPI")

    out = zeros_like(img)
    coordinates_input = img.getNonZeroCoordinates(sorting='z',
                                                  reverse_coord=True)

    # for all points with non-zeros neighbors, force the neighbors to 0
    for i, (x, y, z, _) in enumerate(coordinates_input):
        out.data[int(x), int(y), int(z)] = i + 1

    if out.orientation != og_orientation:
        out.change_orientation(og_orientation)
        img.change_orientation(og_orientation)

    return out
示例#4
0
def run_crop(fname_in, fname_out, nb_slice_average=1.0):
    img = Image(fname_in).change_orientation('RPI')
    if len(list(np.where(img.data == 3)[2])) == 1:  # if label file
        x_start, x_end = str(np.where(img.data == 3)[0][0]), str(
            np.where(img.data == 3)[0][0])
        nb_slice_average_each_side = 0
        img.data[np.where(img.data != 3)] = 0
        img.data[np.where(img.data == 3)] = 1
        img.change_orientation('PIR')
        img.save(fname_out)
        del img
    else:  # if grayscale image file
        x_med = int(np.rint(img.dim[0] * 1.0 / 2))
        nb_slice_average_each_side = int(nb_slice_average / 2 / img.dim[4])
        x_start, x_end = str(x_med - nb_slice_average_each_side), str(
            x_med + nb_slice_average_each_side)
        del img
        cmd_orient = [
            'sct_image', '-i', fname_in, '-setorient', 'PIR', '-o', fname_out
        ]
        sct.run(cmd_orient)

    cmd_crop = [
        'sct_crop_image', '-i', fname_out, '-zmin', x_start, '-zmax', x_end,
        '-o', fname_out
    ]
    sct.run(cmd_crop)

    if nb_slice_average_each_side:
        cmd_mean = [
            'sct_maths', '-i', fname_out, '-mean', 'z', '-o', fname_out
        ]
        sct.run(cmd_mean)
示例#5
0
def main(fname_anat, fname_centerline, degree_poly, centerline_fitting, interp,
         remove_temp_files, verbose):

    # load input image
    im_anat = Image(fname_anat)
    nx, ny, nz, nt, px, py, pz, pt = im_anat.dim
    # re-oriente to RPI
    orientation_native = im_anat.orientation
    im_anat.change_orientation("RPI")

    # load centerline
    im_centerline = Image(fname_centerline).change_orientation("RPI")

    # smooth centerline and return fitted coordinates in voxel space
    x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
        im_centerline,
        algo_fitting=centerline_fitting,
        type_window='hanning',
        window_length=50,
        nurbs_pts_number=3000,
        phys_coordinates=False,
        verbose=verbose,
        all_slices=True)

    # compute translation for each slice, such that the flattened centerline is centered in the medial plane (R-L) and
    # avoid discontinuity in slices where there is no centerline (in which case, simply copy the translation of the
    # closest Z).
    # first, get zmin and zmax spanned by the centerline (i.e. with non-zero values)
    indz_centerline = np.where(
        [np.sum(im_centerline.data[:, :, iz]) for iz in range(nz)])[0]
    zmin, zmax = indz_centerline[0], indz_centerline[-1]
    # then, extend the centerline by copying values below zmin and above zmax
    x_centerline_extended = np.concatenate([
        np.ones(zmin) * x_centerline_fit[0], x_centerline_fit,
        np.ones(nz - zmax) * x_centerline_fit[-1]
    ])

    # loop across slices and apply translation
    im_anat_flattened = msct_image.change_type(im_anat, np.float32)
    # change type to float32 because of subsequent conversion (img_as_float). See #1790

    for iz in range(nz):
        # compute translation along x (R-L)
        translation_x = x_centerline_extended[iz] - np.round(nx / 2.0)
        # apply transformation to 2D image with linear interpolation
        # tform = tf.SimilarityTransform(scale=1, rotation=0, translation=(translation_x, 0))
        tform = transform.SimilarityTransform(translation=(0, translation_x))
        # important to force input in float to skikit image, because it will output float values
        img = img_as_float(im_anat.data[:, :, iz])
        img_reg = transform.warp(img, tform)
        im_anat_flattened.data[:, :, iz] = img_reg  # img_as_uint(img_reg)

    # change back to native orientation
    im_anat_flattened.change_orientation(orientation_native)
    # save output
    fname_out = sct.add_suffix(fname_anat, '_flatten')
    im_anat_flattened.save(fname_out)

    sct.display_viewer_syntax([fname_anat, fname_out])
示例#6
0
def detect_centerline(img, contrast):
    """Detect spinal cord centerline using OptiC.
    :param img: input Image() object.
    :param contrast: str: The type of contrast. Will define the path to Optic model.
    :returns: Image(): Output centerline
    """

    # Fetch path to Optic model based on contrast
    optic_models_path = os.path.join(sct.__sct_dir__, 'data', 'optic_models',
                                     '{}_model'.format(contrast))

    sct.log.debug('Detecting the spinal cord using OptiC')
    img_orientation = img.orientation

    temp_folder = sct.TempFolder()
    temp_folder.chdir()

    # convert image data type to int16, as required by opencv (backend in OptiC)
    img_int16 = img.copy()
    # Replace non-numeric values by zero
    img_data = img.data
    img_data[np.where(np.isnan(img_data))] = 0
    img_data[np.where(np.isinf(img_data))] = 0
    img_int16.data[np.where(np.isnan(img_int16.data))] = 0
    img_int16.data[np.where(np.isinf(img_int16.data))] = 0
    # rescale intensity
    min_out = np.iinfo('uint16').min
    max_out = np.iinfo('uint16').max
    min_in = np.nanmin(img_data)
    max_in = np.nanmax(img_data)
    data_rescaled = img_data.astype('float') * (max_out - min_out) / (max_in -
                                                                      min_in)
    img_int16.data = data_rescaled - (data_rescaled.min() - min_out)
    # change data type
    img_int16.change_type(np.uint16)
    # reorient the input image to RPI + convert to .nii
    img_int16.change_orientation('RPI')
    file_img = 'img_rpi_uint16'
    img_int16.save(file_img + '.nii')

    # call the OptiC method to generate the spinal cord centerline
    optic_input = file_img
    optic_filename = file_img + '_optic'
    os.environ["FSLOUTPUTTYPE"] = "NIFTI_PAIR"
    cmd_optic = 'isct_spine_detect -ctype=dpdt -lambda=1 "%s" "%s" "%s"' % \
                (optic_models_path, optic_input, optic_filename)
    # TODO: output coordinates, for each slice, in continuous (not discrete) values.

    sct.run(cmd_optic, verbose=0)

    # convert .img and .hdr files to .nii.gz
    img_ctl = Image(file_img + '_optic_ctr.hdr')
    img_ctl.change_orientation(img_orientation)

    # return to initial folder
    temp_folder.chdir_undo()

    return img_ctl
def dummy_centerline(size_arr=(9, 9, 9), subsampling=1, dilate_ctl=0, hasnan=False, zeroslice=[], outlier=[],
                     orientation='RPI', debug=False):
    """
    Create a dummy Image centerline of small size. Return the full and sub-sampled version along z.
    :param size_arr: tuple: (nx, ny, nz)
    :param subsampling: int >=1. Subsampling factor along z. 1: no subsampling. 2: centerline defined every other z.
    :param dilate_ctl: Dilation of centerline. E.g., if dilate_ctl=1, result will be a square of 3x3 per slice.
                         if dilate_ctl=0, result will be a single pixel per slice.
    :param hasnan: Bool: Image has non-numerical values: nan, inf. In this case, do not subsample.
    :param zeroslice: list int: zero all slices listed in this param
    :param outlier: list int: replace the current point with an outlier at the corner of the image for the slices listed
    :param orientation:
    :param debug: Bool: Write temp files
    :return:
    """
    from numpy import poly1d, polyfit
    nx, ny, nz = size_arr
    # define array based on a polynomial function, within X-Z plane, located at y=ny/4, based on the following points:
    x = np.array([round(nx/4.), round(nx/2.), round(3*nx/4.)])
    z = np.array([0, round(nz/2.), nz-1])
    p = poly1d(polyfit(z, x, deg=3))
    data = np.zeros((nx, ny, nz))
    arr_ctl = np.array([p(range(nz)).astype(np.int),
                        [round(ny / 4.)] * len(range(nz)),
                        range(nz)], dtype=np.uint16)
    # Loop across dilation of centerline. E.g., if dilate_ctl=1, result will be a square of 3x3 per slice.
    for ixiy_ctl in itertools.product(range(-dilate_ctl, dilate_ctl+1, 1), range(-dilate_ctl, dilate_ctl+1, 1)):
        data[(arr_ctl[0] + ixiy_ctl[0]).tolist(),
             (arr_ctl[1] + ixiy_ctl[1]).tolist(),
             arr_ctl[2].tolist()] = 1
    # Zero specified slices
    if zeroslice is not []:
        data[:, :, zeroslice] = 0
    # Add outlier
    if outlier is not []:
        # First, zero all the slice
        data[:, :, outlier] = 0
        # Then, add point in the corner
        data[0, 0, outlier] = 1
    # Create image with default orientation LPI
    affine = np.eye(4)
    nii = nib.nifti1.Nifti1Image(data, affine)
    img = Image(data, hdr=nii.header, dim=nii.header.get_data_shape())
    # subsample data
    img_sub = img.copy()
    img_sub.data = np.zeros((nx, ny, nz))
    for iz in range(0, nz, subsampling):
        img_sub.data[..., iz] = data[..., iz]
    # Add non-numerical values at the top corner of the image
    if hasnan:
        img.data[0, 0, 0] = np.nan
        img.data[1, 0, 0] = np.inf
    # Update orientation
    img.change_orientation(orientation)
    img_sub.change_orientation(orientation)
    if debug:
        img_sub.save('tmp_dummy_seg_'+datetime.now().strftime("%Y%m%d%H%M%S%f")+'.nii.gz')
    return img, img_sub, arr_ctl
示例#8
0
    def extract_slices(self):
        # open image and re-orient it to RPI if needed
        im, seg = Image(self.param.fname_im), Image(self.param.fname_seg)
        if self.orientation_im != self.orientation_extraction:
            im.change_orientation(self.orientation_extraction)
            seg.change_orientation(self.orientation_extraction)

        # extract axial slices in self.dct_im_seg
        self.dct_im_seg['im'], self.dct_im_seg['seg'] = [im.data[:, :, z] for z in range(im.dim[2])], [seg.data[:, :, z] for z in range(im.dim[2])]
示例#9
0
def z_brainstem(image_fold):
    brainstem_path = os.path.join(image_fold, 'label', 'brainstem_CST.nii.gz')

    brainstem_im = Image(brainstem_path)
    brainstem_im.change_orientation('RPI')
    z_brainstem_lst = list(set(np.where(brainstem_im.data)[2]))
    del brainstem_im

    return np.min(z_brainstem_lst), np.max(z_brainstem_lst)
def template_lpi(tmp_path_factory):
    """Change orientation of test data template to LPI."""
    path_out = str(tmp_path_factory.mktemp("tmp_data")/'template_lpi')  # tmp_path_factory is needed for module scope
    shutil.copytree('sct_testing_data/template', path_out)
    for file in glob.glob('sct_testing_data/template_lpi/template/*.nii.gz'):
        nii = Image(file)
        nii.change_orientation('LPI')
        nii.save(file)
    return path_out
def resample_image(fname, suffix='_resampled.nii.gz', binary=False, npx=0.3, npy=0.3, thr=0.0, interpolation='spline'):
    """
    Resampling function: add a padding, resample, crop the padding
    :param fname: name of the image file to be resampled
    :param suffix: suffix added to the original fname after resampling
    :param binary: boolean, image is binary or not
    :param npx: new pixel size in the x direction
    :param npy: new pixel size in the y direction
    :param thr: if the image is binary, it will be thresholded at thr (default=0) after the resampling
    :param interpolation: type of interpolation used for the resampling
    :return: file name after resampling (or original fname if it was already in the correct resolution)
    """
    im_in = Image(fname)
    orientation = im_in.orientation
    if orientation != 'RPI':
        im_in.change_orientation('RPI')
        fname = add_suffix(im_in.absolutepath, "_rpi")
        im_in.save(path=fname, mutable=True)

    nx, ny, nz, nt, px, py, pz, pt = im_in.dim

    if np.round(px, 2) != np.round(npx, 2) or np.round(py, 2) != np.round(npy, 2):
        name_resample = extract_fname(fname)[1] + suffix
        if binary:
            interpolation = 'nn'

        if nz == 1:
            # when data is 2d: we convert it to a 3d image in order to avoid conversion problem with 2d data
            # TODO: check if this above problem is still present (now that we are using nibabel instead of nipy)
            run_proc(['sct_image', '-i', ','.join([fname, fname]), '-concat', 'z', '-o', fname])

        run_proc(['sct_resample', '-i', fname, '-mm', str(npx) + 'x' + str(npy) + 'x' + str(pz), '-o', name_resample, '-x', interpolation])

        if nz == 1:  # when input data was 2d: re-convert data 3d-->2d
            run_proc(['sct_image', '-i', name_resample, '-split', 'z'])
            im_split = Image(name_resample.split('.nii.gz')[0] + '_Z0000.nii.gz')
            im_split.save(name_resample)

        if binary:
            img = Image(name_resample)
            img.data = binarize(img.data, thr)
            img.save()

        if orientation != 'RPI':
            img = Image(name_resample)
            img.change_orientation(orientation)
            name_resample = add_suffix(img.absolutepath, "_{}".format(orientation.lower()))
            img.save(path=name_resample, mutable=True)

        return name_resample
    else:
        if orientation != 'RPI':
            fname = add_suffix(fname, "_RPI")
            im_in = change_orientation(im_in, orientation).save(fname)

        printv('Image resolution already ' + str(npx) + 'x' + str(npy) + 'xpz')
        return fname
    def extract_slices(self):
        # open image and re-orient it to RPI if needed
        im, seg = Image(self.param.fname_im), Image(self.param.fname_seg)
        if self.orientation_im != self.orientation_extraction:
            im.change_orientation(self.orientation_extraction)
            seg.change_orientation(self.orientation_extraction)

        # extract axial slices in self.dct_im_seg
        self.dct_im_seg['im'], self.dct_im_seg['seg'] = [im.data[:, :, z] for z in range(im.dim[2])], [seg.data[:, :, z] for z in range(im.dim[2])]
示例#13
0
def create_labels_along_segmentation(
        img: Image, labels: Sequence[Tuple[int, int]]) -> Image:
    """
    Create an image with labels defined along the spinal cord segmentation (or centerline).
    Input image does **not** need to be RPI (re-orientation is done within this function).

    :param img: source segmentation
    :param labels: list of label tuples as (z_value, label_value)
    :returns: labeled segmentation (Image)
    """
    og_orientation = img.orientation

    if og_orientation != "RPI":
        img.change_orientation("RPI")

    out = zeros_like(img)

    for idx_label, label in enumerate(labels):
        z, value = label

        # update z based on native image orientation (z should represent superior-inferior axis)
        coord = Coordinate(
            [z, z, z]
        )  # since we don't know which dimension corresponds to the superior-inferior

        # axis, we put z in all dimensions (we don't care about x and y here)
        _, _, z_rpi = coord.permute(img, 'RPI')

        # if z=-1, replace with nz/2
        if z == -1:
            z_rpi = int(np.round(out.dim[2] / 2.0))

        # get center of mass of segmentation at given z
        x, y = ndimage.measurements.center_of_mass(
            np.array(img.data[:, :, z_rpi]))

        # round values to make indices
        x, y = int(np.round(x)), int(np.round(y))

        # display info
        logger.debug(f"Label # {idx_label}: {x}, {y}. {z_rpi} --> {value}")

        if len(out.data.shape) == 3:
            out.data[x, y, z_rpi] = value
        elif len(out.data.shape) == 2:
            if z != 0:
                raise ValueError(
                    f"2D coordinates should have a Z value of 0! Current value: {coord.z}"
                )

            out.data[x, y] = value

    if out.orientation != og_orientation:
        out.change_orientation(og_orientation)
        img.change_orientation(og_orientation)

    return out
示例#14
0
def label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=1):
    """
    Label segmentation image

    :param fname_seg: fname of the segmentation, no orientation expected
    :param list_disc_z: list of z that correspond to a disc
    :param list_disc_value: list of associated disc values
    :param verbose:
    :return:
    """

    # open segmentation
    seg = Image(fname_seg)
    init_orientation = seg.orientation
    seg.change_orientation("RPI")

    dim = seg.dim
    ny = dim[1]
    nz = dim[2]
    # loop across z
    for iz in range(nz):
        # get index of the disc right above iz
        try:
            ind_above_iz = max(
                [i for i in range(len(list_disc_z)) if list_disc_z[i] > iz])
        except ValueError:
            # if ind_above_iz is empty, attribute value 0
            vertebral_level = 0
        else:
            # assign vertebral level (add one because iz is BELOW the disk)
            vertebral_level = list_disc_value[ind_above_iz] + 1
            # sct.printv(vertebral_level)
        # get voxels in mask
        ind_nonzero = np.nonzero(seg.data[:, :, iz])
        seg.data[ind_nonzero[0], ind_nonzero[1], iz] = vertebral_level
        # if verbose == 2:
        #     # move to OO. No time to finish... (JCA)
        #     from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
        #     from matplotlib.figure import Figure
        #     fig = Figure()
        #     FigureCanvas(fig)
        #     ax = fig.add_subplot(111)
        #     ax.scatter(int(np.round(ny / 2)), iz, c=vertebral_level, vmin=min(list_disc_value),
        #                vmax=max(list_disc_value), cmap='prism', marker='_', s=200)
        #
        #     # TODO: the thing below crashes with the py3k move. Fix it when i have time...
        #     import matplotlib
        #     matplotlib.use('Agg')
        #     import matplotlib.pyplot as plt
        #     plt.figure(50)
        #     plt.scatter(int(np.round(ny / 2)), iz, c=vertebral_level, vmin=min(list_disc_value), vmax=max(list_disc_value), cmap='prism', marker='_', s=200)

    # write file
    seg.change_orientation(init_orientation).save(
        sct.add_suffix(fname_seg, '_labeled'))
示例#15
0
def z_slice_levels(levels_path):
    levels_im = Image(levels_path)
    levels_im.change_orientation('RPI')

    lvl_dct = {}
    for lvl in list(np.unique(levels_im.data)):
        if lvl:
            lvl_dct[lvl] = list(set(np.where(levels_im.data == lvl)[2]))
    del levels_im

    return lvl_dct
def project_labels_on_spinalcord(fname_label, fname_seg):
    """
    Project labels orthogonally on the spinal cord centerline. The algorithm works by finding the smallest distance
    between each label and the spinal cord center of mass.
    :param fname_label: file name of labels
    :param fname_seg: file name of cord segmentation (could also be of centerline)
    :return: file name of projected labels
    """
    # build output name
    fname_label_projected = sct.add_suffix(fname_label, "_projected")
    # open labels and segmentation
    im_label = Image(fname_label).change_orientation("RPI")
    im_seg = Image(fname_seg)
    native_orient = im_seg.orientation
    im_seg.change_orientation("RPI")

    # smooth centerline and return fitted coordinates in voxel space
    _, arr_ctl, _ = get_centerline(im_seg, algo_fitting='bspline')
    x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl
    # convert pixel into physical coordinates
    centerline_xyz_transposed = \
        [im_seg.transfo_pix2phys([[x_centerline_fit[i], y_centerline_fit[i], z_centerline[i]]])[0]
                                 for i in range(len(x_centerline_fit))]
    # transpose list
    centerline_phys_x = [i[0] for i in centerline_xyz_transposed]
    centerline_phys_y = [i[1] for i in centerline_xyz_transposed]
    centerline_phys_z = [i[2] for i in centerline_xyz_transposed]
    # get center of mass of label
    labels = im_label.getCoordinatesAveragedByValue()
    # initialize image of projected labels. Note that we use the space of the seg (not label).
    im_label_projected = msct_image.zeros_like(im_seg, dtype=np.uint8)

    # loop across label values
    for label in labels:
        # convert pixel into physical coordinates for the label
        label_phys_x, label_phys_y, label_phys_z = im_label.transfo_pix2phys([[label.x, label.y, label.z]])[0]
        # calculate distance between label and each point of the centerline
        distance_centerline = [np.linalg.norm([centerline_phys_x[i] - label_phys_x,
                                               centerline_phys_y[i] - label_phys_y,
                                               centerline_phys_z[i] - label_phys_z])
                               for i in range(len(x_centerline_fit))]
        # get the index corresponding to the min distance
        ind_min_distance = np.argmin(distance_centerline)
        # get centerline coordinate (in physical space)
        [min_phy_x, min_phy_y, min_phy_z] = [centerline_phys_x[ind_min_distance],
                                             centerline_phys_y[ind_min_distance],
                                             centerline_phys_z[ind_min_distance]]
        # convert coordinate to voxel space
        minx, miny, minz = im_seg.transfo_phys2pix([[min_phy_x, min_phy_y, min_phy_z]])[0]
        # use that index to assign projected label in the centerline
        im_label_projected.data[minx, miny, minz] = label.value
    # re-orient projected labels to native orientation and save
    im_label_projected.change_orientation(native_orient).save(fname_label_projected)
    return fname_label_projected
示例#17
0
def project_labels_on_spinalcord(fname_label, fname_seg, param_centerline):
    """
    Project labels orthogonally on the spinal cord centerline. The algorithm works by finding the smallest distance
    between each label and the spinal cord center of mass.
    :param fname_label: file name of labels
    :param fname_seg: file name of cord segmentation (could also be of centerline)
    :return: file name of projected labels
    """
    # build output name
    fname_label_projected = sct.add_suffix(fname_label, "_projected")
    # open labels and segmentation
    im_label = Image(fname_label).change_orientation("RPI")
    im_seg = Image(fname_seg)
    native_orient = im_seg.orientation
    im_seg.change_orientation("RPI")

    # smooth centerline and return fitted coordinates in voxel space
    _, arr_ctl, _, _ = get_centerline(im_seg, param_centerline)
    x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl
    # convert pixel into physical coordinates
    centerline_xyz_transposed = \
        [im_seg.transfo_pix2phys([[x_centerline_fit[i], y_centerline_fit[i], z_centerline[i]]])[0]
                                 for i in range(len(x_centerline_fit))]
    # transpose list
    centerline_phys_x = [i[0] for i in centerline_xyz_transposed]
    centerline_phys_y = [i[1] for i in centerline_xyz_transposed]
    centerline_phys_z = [i[2] for i in centerline_xyz_transposed]
    # get center of mass of label
    labels = im_label.getCoordinatesAveragedByValue()
    # initialize image of projected labels. Note that we use the space of the seg (not label).
    im_label_projected = msct_image.zeros_like(im_seg, dtype=np.uint8)

    # loop across label values
    for label in labels:
        # convert pixel into physical coordinates for the label
        label_phys_x, label_phys_y, label_phys_z = im_label.transfo_pix2phys([[label.x, label.y, label.z]])[0]
        # calculate distance between label and each point of the centerline
        distance_centerline = [np.linalg.norm([centerline_phys_x[i] - label_phys_x,
                                               centerline_phys_y[i] - label_phys_y,
                                               centerline_phys_z[i] - label_phys_z])
                               for i in range(len(x_centerline_fit))]
        # get the index corresponding to the min distance
        ind_min_distance = np.argmin(distance_centerline)
        # get centerline coordinate (in physical space)
        [min_phy_x, min_phy_y, min_phy_z] = [centerline_phys_x[ind_min_distance],
                                             centerline_phys_y[ind_min_distance],
                                             centerline_phys_z[ind_min_distance]]
        # convert coordinate to voxel space
        minx, miny, minz = im_seg.transfo_phys2pix([[min_phy_x, min_phy_y, min_phy_z]])[0]
        # use that index to assign projected label in the centerline
        im_label_projected.data[minx, miny, minz] = label.value
    # re-orient projected labels to native orientation and save
    im_label_projected.change_orientation(native_orient).save(fname_label_projected)
    return fname_label_projected
示例#18
0
def get_midNifti(path_im, ind):
    """
    Retrieve the input images for the network. This images are generated by
    averaging the 7 slices in the middle of the volume
    :param path_im: path to image
    :param ind: index of the middle
    :return:
    """
    a = Image(path_im)
    a.change_orientation('RPI')
    arr = np.array(a.data)
    return np.mean(arr[ind - 3:ind + 3, :, :], 0)
def _preprocess_segment(fname_t2, fname_t2_seg, contrast_test, dim_3=False):
    tmp_folder = sct.TempFolder()
    tmp_folder_path = tmp_folder.get_path()
    tmp_folder.chdir()

    img = Image(fname_t2)
    gt = Image(fname_t2_seg)

    fname_t2_RPI, fname_t2_seg_RPI = 'img_RPI.nii.gz', 'seg_RPI.nii.gz'
    img.change_orientation('RPI').save(fname_t2_RPI)
    gt.change_orientation('RPI').save(fname_t2_seg_RPI)
    input_resolution = gt.dim[4:7]
    del img, gt

    fname_res, fname_ctr = deepseg_sc.find_centerline(algo='svm',
                                                        image_fname=fname_t2_RPI,
                                                        contrast_type=contrast_test,
                                                        brain_bool=False,
                                                        folder_output=tmp_folder_path,
                                                        remove_temp_files=1,
                                                        centerline_fname=None)

    fname_t2_seg_RPI_res = 'seg_RPI_res.nii.gz'
    new_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])])
    resample_file(fname_t2_seg_RPI, fname_t2_seg_RPI_res, new_resolution, 'mm', 'linear', verbose=0)

    img, ctr, gt = Image(fname_res), Image(fname_ctr), Image(fname_t2_seg_RPI_res)
    _, _, _, img = deepseg_sc.crop_image_around_centerline(im_in=img,
                                                        ctr_in=ctr,
                                                        crop_size=64)
    _, _, _, gt = deepseg_sc.crop_image_around_centerline(im_in=gt,
                                                        ctr_in=ctr,
                                                        crop_size=64)
    del ctr

    img = deepseg_sc.apply_intensity_normalization(im_in=img)

    if dim_3:  # If 3D kernels
        fname_t2_RPI_res_crop, fname_t2_seg_RPI_res_crop = 'img_RPI_res_crop.nii.gz', 'seg_RPI_res_crop.nii.gz'
        img.save(fname_t2_RPI_res_crop)
        gt.save(fname_t2_seg_RPI_res_crop)
        del img, gt

        fname_t2_RPI_res_crop_res = 'img_RPI_res_crop_res.nii.gz'
        fname_t2_seg_RPI_res_crop_res = 'seg_RPI_res_crop_res.nii.gz'
        resample_file(fname_t2_RPI_res_crop, fname_t2_RPI_res_crop_res, new_resolution, 'mm', 'linear', verbose=0)
        resample_file(fname_t2_seg_RPI_res_crop, fname_t2_seg_RPI_res_crop_res, new_resolution, 'mm', 'linear', verbose=0)
        img, gt = Image(fname_t2_RPI_res_crop_res), Image(fname_t2_seg_RPI_res_crop_res)

    tmp_folder.chdir_undo()
    tmp_folder.cleanup()

    return img, gt
def _preprocess_segment(fname_t2, fname_t2_seg, contrast_test, dim_3=False):
    tmp_folder = sct.TempFolder()
    tmp_folder_path = tmp_folder.get_path()
    tmp_folder.chdir()

    img = Image(fname_t2)
    gt = Image(fname_t2_seg)

    fname_t2_RPI, fname_t2_seg_RPI = 'img_RPI.nii.gz', 'seg_RPI.nii.gz'
    img.change_orientation('RPI').save(fname_t2_RPI)
    gt.change_orientation('RPI').save(fname_t2_seg_RPI)
    input_resolution = gt.dim[4:7]
    del img, gt

    fname_res, fname_ctr, _ = deepseg_sc.find_centerline(algo='svm',
                                                            image_fname=fname_t2_RPI,
                                                            contrast_type=contrast_test,
                                                            brain_bool=False,
                                                            folder_output=tmp_folder_path,
                                                            remove_temp_files=1,
                                                            centerline_fname=None)

    fname_t2_seg_RPI_res = 'seg_RPI_res.nii.gz'
    new_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])])
    resample_file(fname_t2_seg_RPI, fname_t2_seg_RPI_res, new_resolution, 'mm', 'linear', verbose=0)

    img, ctr, gt = Image(fname_res), Image(fname_ctr), Image(fname_t2_seg_RPI_res)
    _, _, _, img = deepseg_sc.crop_image_around_centerline(im_in=img,
                                                        ctr_in=ctr,
                                                        crop_size=64)
    _, _, _, gt = deepseg_sc.crop_image_around_centerline(im_in=gt,
                                                        ctr_in=ctr,
                                                        crop_size=64)
    del ctr

    img = deepseg_sc.apply_intensity_normalization(im_in=img)

    if dim_3:  # If 3D kernels
        fname_t2_RPI_res_crop, fname_t2_seg_RPI_res_crop = 'img_RPI_res_crop.nii.gz', 'seg_RPI_res_crop.nii.gz'
        img.save(fname_t2_RPI_res_crop)
        gt.save(fname_t2_seg_RPI_res_crop)
        del img, gt

        fname_t2_RPI_res_crop_res = 'img_RPI_res_crop_res.nii.gz'
        fname_t2_seg_RPI_res_crop_res = 'seg_RPI_res_crop_res.nii.gz'
        resample_file(fname_t2_RPI_res_crop, fname_t2_RPI_res_crop_res, new_resolution, 'mm', 'linear', verbose=0)
        resample_file(fname_t2_seg_RPI_res_crop, fname_t2_seg_RPI_res_crop_res, new_resolution, 'mm', 'linear', verbose=0)
        img, gt = Image(fname_t2_RPI_res_crop_res), Image(fname_t2_seg_RPI_res_crop_res)

    tmp_folder.chdir_undo()
    tmp_folder.cleanup()

    return img, gt
示例#21
0
def label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=1):
    """
    Label segmentation image
    :param fname_seg: fname of the segmentation, no orientation expected
    :param list_disc_z: list of z that correspond to a disc
    :param list_disc_value: list of associated disc values
    :param verbose:
    :return:
    """

    # open segmentation
    seg = Image(fname_seg)
    init_orientation = seg.orientation
    seg.change_orientation("RPI")

    dim = seg.dim
    ny = dim[1]
    nz = dim[2]
    # loop across z
    for iz in range(nz):
        # get index of the disc right above iz
        try:
            ind_above_iz = max(
                [i for i in range(len(list_disc_z)) if list_disc_z[i] > iz])
        except ValueError:
            # if ind_above_iz is empty, attribute value 0
            vertebral_level = 0
        else:
            # assign vertebral level (add one because iz is BELOW the disk)
            vertebral_level = list_disc_value[ind_above_iz] + 1
            # sct.printv(vertebral_level)
        # get voxels in mask
        ind_nonzero = np.nonzero(seg.data[:, :, iz])
        seg.data[ind_nonzero[0], ind_nonzero[1], iz] = vertebral_level
        if verbose == 2:
            import matplotlib
            matplotlib.use('Agg')
            import matplotlib.pyplot as plt
            plt.figure(50)
            plt.scatter(int(np.round(ny / 2)),
                        iz,
                        c=vertebral_level,
                        vmin=min(list_disc_value),
                        vmax=max(list_disc_value),
                        cmap='prism',
                        marker='_',
                        s=200)
    # write file
    seg.change_orientation(init_orientation).save(
        sct.add_suffix(fname_seg, '_labeled'))
示例#22
0
    def compute_texture(self):

        offset = int(self.param_glcm.distance)
        printv('\nCompute texture metrics...', self.param.verbose, 'normal')

        # open image and re-orient it to RPI if needed
        im_tmp = Image(self.param.fname_im)
        if self.orientation_im != self.orientation_extraction:
            im_tmp.change_orientation(self.orientation_extraction)

        dct_metric = {}
        for m in self.metric_lst:
            im_2save = zeros_like(im_tmp, dtype='float64')
            dct_metric[m] = im_2save
            # dct_metric[m] = Image(self.fname_metric_lst[m])

        with sct_progress_bar() as pbar:
            for im_z, seg_z, zz in zip(self.dct_im_seg['im'], self.dct_im_seg['seg'], range(len(self.dct_im_seg['im']))):
                for xx in range(im_z.shape[0]):
                    for yy in range(im_z.shape[1]):
                        if not seg_z[xx, yy]:
                            continue
                        if xx < offset or yy < offset:
                            continue
                        if xx > (im_z.shape[0] - offset - 1) or yy > (im_z.shape[1] - offset - 1):
                            continue  # to check if the whole glcm_window is in the axial_slice
                        if False in np.unique(seg_z[xx - offset: xx + offset + 1, yy - offset: yy + offset + 1]):
                            continue  # to check if the whole glcm_window is in the mask of the axial_slice

                        glcm_window = im_z[xx - offset: xx + offset + 1, yy - offset: yy + offset + 1]
                        glcm_window = glcm_window.astype(np.uint8)

                        dct_glcm = {}
                        for a in self.param_glcm.angle.split(','):  # compute the GLCM for self.param_glcm.distance and for each self.param_glcm.angle
                            dct_glcm[a] = greycomatrix(glcm_window,
                                                       [self.param_glcm.distance], [np.radians(int(a))],
                                                       symmetric=self.param_glcm.symmetric,
                                                       normed=self.param_glcm.normed)

                        for m in self.metric_lst:  # compute the GLCM property (m.split('_')[0]) of the voxel xx,yy,zz
                            dct_metric[m].data[xx, yy, zz] = greycoprops(dct_glcm[m.split('_')[2]], m.split('_')[0])[0][0]

                        pbar.set_postfix(pos="{}/{}".format(zz, len(self.dct_im_seg["im"])))
                        pbar.update(1)

        for m in self.metric_lst:
            fname_out = add_suffix("".join(extract_fname(self.param.fname_im)[1:]), '_' + m)
            dct_metric[m].save(fname_out)
            self.fname_metric_lst[m] = fname_out
    def compute_texture(self):

        offset = int(self.param_glcm.distance)
        sct.printv('\nCompute texture metrics...', self.param.verbose, 'normal')

        # open image and re-orient it to RPI if needed
        im_tmp = Image(self.param.fname_im)
        if self.orientation_im != self.orientation_extraction:
            im_tmp.change_orientation(self.orientation_extraction)

        dct_metric = {}
        for m in self.metric_lst:
            im_2save = msct_image.zeros_like(im_tmp, dtype='float64')
            dct_metric[m] = im_2save
            # dct_metric[m] = Image(self.fname_metric_lst[m])

        with tqdm.tqdm() as pbar:
            for im_z, seg_z, zz in zip(self.dct_im_seg['im'], self.dct_im_seg['seg'], range(len(self.dct_im_seg['im']))):
                for xx in range(im_z.shape[0]):
                    for yy in range(im_z.shape[1]):
                        if not seg_z[xx, yy]:
                            continue
                        if xx < offset or yy < offset:
                            continue
                        if xx > (im_z.shape[0] - offset - 1) or yy > (im_z.shape[1] - offset - 1):
                            continue  # to check if the whole glcm_window is in the axial_slice
                        if False in np.unique(seg_z[xx - offset: xx + offset + 1, yy - offset: yy + offset + 1]):
                            continue  # to check if the whole glcm_window is in the mask of the axial_slice

                        glcm_window = im_z[xx - offset: xx + offset + 1, yy - offset: yy + offset + 1]
                        glcm_window = glcm_window.astype(np.uint8)

                        dct_glcm = {}
                        for a in self.param_glcm.angle.split(','):  # compute the GLCM for self.param_glcm.distance and for each self.param_glcm.angle
                            dct_glcm[a] = greycomatrix(glcm_window,
                                                       [self.param_glcm.distance], [np.radians(int(a))],
                                                       symmetric=self.param_glcm.symmetric,
                                                       normed=self.param_glcm.normed)

                        for m in self.metric_lst:  # compute the GLCM property (m.split('_')[0]) of the voxel xx,yy,zz
                            dct_metric[m].data[xx, yy, zz] = greycoprops(dct_glcm[m.split('_')[2]], m.split('_')[0])[0][0]

                        pbar.set_postfix(pos="{}/{}".format(zz, len(self.dct_im_seg["im"])))
                        pbar.update(1)

        for m in self.metric_lst:
            fname_out = sct.add_suffix(''.join(sct.extract_fname(self.param.fname_im)[1:]), '_' + m)
            dct_metric[m].save(fname_out)
            self.fname_metric_lst[m] = fname_out
示例#24
0
def label_discs(fname_seg_labeled, verbose=1):
    """
    Label discs from labeled_segmentation. The convention is C2/C3-->3, C3/C4-->4, etc.

    :param fname_seg_labeld: fname of the labeled segmentation
    :param verbose:
    :return:
    """
    # open labeled segmentation
    im_seg_labeled = Image(fname_seg_labeled)
    orientation_native = im_seg_labeled.orientation
    im_seg_labeled.change_orientation("RPI")
    nx, ny, nz = im_seg_labeled.dim[0], im_seg_labeled.dim[
        1], im_seg_labeled.dim[2]
    data_disc = np.zeros([nx, ny, nz])
    vertebral_level_previous = np.max(
        im_seg_labeled.data)  # initialize with the max label value
    # loop across z in the superior direction (i.e. starts with the bottom slice), and each time the i/i+1 interface
    # between two levels is found, create a label at the center of the cord with the value corresponding to the
    # vertebral level below the point. E.g., at interface C3/C4, the value would be 4.
    for iz in range(nz):
        # get 2d slice
        slice = im_seg_labeled.data[:, :, iz]
        # check if at least one voxel is non-zero
        if np.any(slice):
            slice_one = np.copy(slice)
            # set all non-zero values to 1 (to compute center of mass)
            # Note: the reason we do this is because if one slice includes part of vertebral level i and i+1, the
            # center of mass will be shifted towards the i+1 level.We don't want that here (i.e. the goal is to be at
            # the center of the cord)
            slice_one[slice.nonzero()] = 1
            # compute center of mass
            cx, cy = [
                int(x) for x in np.round(center_of_mass(slice_one)).tolist()
            ]
            # retrieve vertebral level
            vertebral_level = slice[cx, cy]
            # if smaller than previous level, then labeled as a disc
            if vertebral_level < vertebral_level_previous:
                # label disc
                data_disc[cx, cy, iz] = vertebral_level + 1
            # update variable
            vertebral_level_previous = vertebral_level
    # save disc labeled file
    im_seg_labeled.data = data_disc
    im_seg_labeled.change_orientation(orientation_native).save(
        add_suffix(fname_seg_labeled, '_disc'))
示例#25
0
def create_qc(fname_in, fname_gt, fname_out):
    img, gt = Image(fname_in), Image(fname_gt)
    img.change_orientation('RPI')
    gt.change_orientation('RPI')
    coord_c2c3 = np.where(gt.data == 1)
    y_c2c3, z_c2c3 = coord_c2c3[1][0], coord_c2c3[2][0]
    sag_slice = img.data[0, :, :]
    del img, gt

    ax = plt.gca()
    ax.imshow(sag_slice, interpolation='nearest', cmap='gray', aspect='auto')
    circ = Circle((z_c2c3, y_c2c3), 2, facecolor='chartreuse')
    ax.add_patch(circ)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    plt.savefig(fname_out)
    plt.close()
示例#26
0
def run_crop(fname_in, fname_out):
    img = Image(fname_in).change_orientation('RPI')
    if len(list(np.where(img.data == 3)[2])) == 1: # if label file
        x_med = str(np.where(img.data == 3)[0][0])
        img.data[np.where(img.data != 3)] = 0
        img.data[np.where(img.data == 3)] = 1
        img.change_orientation('PIR')
        img.save(fname_out)
        del img
    else: # if grayscale image file
        x_med = str(int(np.rint(img.dim[0] * 1.0 / 2)))
        del img
        cmd_orient = ['sct_image', '-i', fname_in, '-setorient', 'PIR', '-o', fname_out]
        sct.run(cmd_orient)

    cmd_orient = ['sct_crop_image', '-i', fname_out, '-start', x_med, '-end', x_med, '-dim', '2', '-o', fname_out]
    sct.run(cmd_orient)
示例#27
0
def launch_manual_label_gui(img: Image, input_labels_img: Image,
                            labels: Sequence[int], msg):
    # the input image is reoriented to 'SAL' when open by the GUI
    input_labels_img.change_orientation('SAL')
    mid = int(np.round(input_labels_img.data.shape[2] / 2))
    previous_points = input_labels_img.getNonZeroCoordinates()

    # boolean used to mark first element to initiate the list.
    first = True

    previous_label = None

    for i in range(len(previous_points)):
        if int(previous_points[i].value) in labels:
            pass
        else:
            labels.append(int(previous_points[i].value))
        if first:
            points = np.array([
                previous_points[i].x, previous_points[i].y,
                previous_points[i].z, previous_points[i].value
            ])
            points = np.reshape(points, (1, 4))
            previous_label = points
            first = False
        else:
            points = np.array([
                previous_points[i].x, previous_points[i].y,
                previous_points[i].z, previous_points[i].value
            ])
            points = np.reshape(points, (1, 4))
            previous_label = np.append(previous_label, points, axis=0)
        labels.sort()

    # check if variable was created which means the file was not empty and contains some points asked in labels
    if previous_label is not None:
        # project onto mid sagittal plane
        for i in range(len(previous_label)):
            previous_label[i][2] = mid

    out = launch_sagittal_viewer(img,
                                 labels,
                                 msg,
                                 previous_points=previous_label)

    return out
示例#28
0
def create_label_z(fname_seg, z, value, fname_labelz='labelz.nii.gz'):
    """
    Create a label at coordinates x_center, y_center, z
    :param fname_seg: segmentation
    :param z: int
    :param fname_labelz: string file name of output label
    :return: fname_labelz
    """
    nii = Image(fname_seg)
    orientation_origin = nii.orientation
    nii = nii.change_orientation("RPI")
    nx, ny, nz, nt, px, py, pz, pt = nii.dim  # Get dimensions
    # find x and y coordinates of the centerline at z using center of mass
    x, y = center_of_mass(np.array(nii.data[:, :, z]))
    x, y = int(np.round(x)), int(np.round(y))
    nii.data[:, :, :] = 0
    nii.data[x, y, z] = value
    # dilate label to prevent it from disappearing due to nearestneighbor interpolation
    nii.data = dilate(nii.data, 3, 'ball')
    nii.change_orientation(orientation_origin)  # put back in original orientation
    nii.save(fname_labelz)
    return fname_labelz
def resample_image(fname, suffix='_resampled.nii.gz', binary=False, npx=0.3, npy=0.3, thr=0.0, interpolation='spline'):
    """
    Resampling function: add a padding, resample, crop the padding
    :param fname: name of the image file to be resampled
    :param suffix: suffix added to the original fname after resampling
    :param binary: boolean, image is binary or not
    :param npx: new pixel size in the x direction
    :param npy: new pixel size in the y direction
    :param thr: if the image is binary, it will be thresholded at thr (default=0) after the resampling
    :param interpolation: type of interpolation used for the resampling
    :return: file name after resampling (or original fname if it was already in the correct resolution)
    """
    im_in = Image(fname)
    orientation = im_in.orientation
    if orientation != 'RPI':
        fname = im_in.change_orientation(im_in, 'RPI', generate_path=True).save().absolutepath

    nx, ny, nz, nt, px, py, pz, pt = im_in.dim

    if np.round(px, 2) != np.round(npx, 2) or np.round(py, 2) != np.round(npy, 2):
        name_resample = sct.extract_fname(fname)[1] + suffix
        if binary:
            interpolation = 'nn'

        if nz == 1:  # when data is 2d: we convert it to a 3d image in order to avoid nipy problem of conversion nifti-->nipy with 2d data
            sct.run(['sct_image', '-i', ','.join([fname, fname]), '-concat', 'z', '-o', fname])

        sct.run(['sct_resample', '-i', fname, '-mm', str(npx) + 'x' + str(npy) + 'x' + str(pz), '-o', name_resample, '-x', interpolation])

        if nz == 1:  # when input data was 2d: re-convert data 3d-->2d
            sct.run(['sct_image', '-i', name_resample, '-split', 'z'])
            im_split = Image(name_resample.split('.nii.gz')[0] + '_Z0000.nii.gz')
            im_split.save(name_resample)

        if binary:
            sct.run(['sct_maths', '-i', name_resample, '-bin', str(thr), '-o', name_resample])

        if orientation != 'RPI':
            name_resample = Image(name_resample) \
             .change_orientation(orientation, generate_path=True) \
             .save() \
             .absolutepath

        return name_resample
    else:
        if orientation != 'RPI':
            fname = sct.add_suffix(fname, "_RPI")
            im_in = msct_image.change_orientation(im_in, orientation).save(fname)

        sct.printv('Image resolution already ' + str(npx) + 'x' + str(npy) + 'xpz')
        return fname
def resample_image(fname, suffix='_resampled.nii.gz', binary=False, npx=0.3, npy=0.3, thr=0.0, interpolation='spline'):
    """
    Resampling function: add a padding, resample, crop the padding
    :param fname: name of the image file to be resampled
    :param suffix: suffix added to the original fname after resampling
    :param binary: boolean, image is binary or not
    :param npx: new pixel size in the x direction
    :param npy: new pixel size in the y direction
    :param thr: if the image is binary, it will be thresholded at thr (default=0) after the resampling
    :param interpolation: type of interpolation used for the resampling
    :return: file name after resampling (or original fname if it was already in the correct resolution)
    """
    im_in = Image(fname)
    orientation = im_in.orientation
    if orientation != 'RPI':
        fname = im_in.change_orientation(im_in, 'RPI', generate_path=True).save().absolutepath

    nx, ny, nz, nt, px, py, pz, pt = im_in.dim

    if np.round(px, 2) != np.round(npx, 2) or np.round(py, 2) != np.round(npy, 2):
        name_resample = sct.extract_fname(fname)[1] + suffix
        if binary:
            interpolation = 'nn'

        if nz == 1:  # when data is 2d: we convert it to a 3d image in order to avoid nipy problem of conversion nifti-->nipy with 2d data
            sct.run(['sct_image', '-i', ','.join([fname, fname]), '-concat', 'z', '-o', fname])

        sct.run(['sct_resample', '-i', fname, '-mm', str(npx) + 'x' + str(npy) + 'x' + str(pz), '-o', name_resample, '-x', interpolation])

        if nz == 1:  # when input data was 2d: re-convert data 3d-->2d
            sct.run(['sct_image', '-i', name_resample, '-split', 'z'])
            im_split = Image(name_resample.split('.nii.gz')[0] + '_Z0000.nii.gz')
            im_split.save(name_resample)

        if binary:
            sct.run(['sct_maths', '-i', name_resample, '-bin', str(thr), '-o', name_resample])

        if orientation != 'RPI':
            name_resample = Image(name_resample) \
             .change_orientation(orientation, generate_path=True) \
             .save() \
             .absolutepath

        return name_resample
    else:
        if orientation != 'RPI':
            fname = sct.add_suffix(fname, "_RPI")
            im_in = msct_image.change_orientation(im_in, orientation).save(fname)

        sct.printv('Image resolution already ' + str(npx) + 'x' + str(npy) + 'xpz')
        return fname
示例#31
0
def label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=1):
    """
    Label segmentation image

    :param fname_seg: fname of the segmentation, no orientation expected
    :param list_disc_z: list of z that correspond to a disc
    :param list_disc_value: list of associated disc values
    :param verbose:
    :return:
    """

    # open segmentation
    seg = Image(fname_seg)
    init_orientation = seg.orientation
    seg.change_orientation("RPI")

    dim = seg.dim
    ny = dim[1]
    nz = dim[2]
    # loop across z
    for iz in range(nz):
        # get index of the disc right above iz
        try:
            ind_above_iz = max(
                [i for i in range(len(list_disc_z)) if list_disc_z[i] > iz])
        except ValueError:
            # if ind_above_iz is empty, attribute value 0
            vertebral_level = 0
        else:
            # assign vertebral level (add one because iz is BELOW the disk)
            vertebral_level = list_disc_value[ind_above_iz] + 1
        # get voxels in mask
        ind_nonzero = np.nonzero(seg.data[:, :, iz])
        seg.data[ind_nonzero[0], ind_nonzero[1], iz] = vertebral_level

    # write file
    seg.change_orientation(init_orientation).save(
        add_suffix(fname_seg, '_labeled'))
def dummy_centerline_small(size_arr=(9, 9, 9), subsampling=1, dilate_ctl=0, hasnan=False, orientation='RPI'):
    """
    Create a dummy Image centerline of small size. Return the full and sub-sampled version along z.
    :param size_arr: tuple: (nx, ny, nz)
    :param subsampling: int >=1. Subsampling factor along z. 1: no subsampling. 2: centerline defined every other z.
    :param dilate_ctl: Dilation of centerline. E.g., if dilate_ctl=1, result will be a square of 3x3 per slice.
                         if dilate_ctl=0, result will be a single pixel per slice.
    :param hasnan: Bool: Image has non-numerical values: nan, inf. In this case, do not subsample.
    :param orientation:
    :return:
    """
    from numpy import poly1d, polyfit
    nx, ny, nz = size_arr
    # define polynomial-based centerline within X-Z plane, located at y=ny/4
    x = np.array([round(nx/4.), round(nx/2.), round(3*nx/4.)])
    z = np.array([0, round(nz/2.), nz-1])
    p = poly1d(polyfit(z, x, deg=3))
    data = np.zeros((nx, ny, nz))
    # Loop across dilation of centerline. E.g., if dilate_ctl=1, result will be a square of 3x3 per slice.
    for ixiy_ctl in itertools.product(range(-dilate_ctl, dilate_ctl+1, 1), range(-dilate_ctl, dilate_ctl+1, 1)):
        data[p(range(nz)).astype(np.int) + ixiy_ctl[0], round(ny / 4.) + ixiy_ctl[1], range(nz)] = 1
    # generate Image object with RPI orientation
    affine = np.eye(4)
    nii = nib.nifti1.Nifti1Image(data, affine)
    img = Image(data, hdr=nii.header, dim=nii.header.get_data_shape())
    # subsample data
    img_sub = img.copy()
    img_sub.data = np.zeros((nx, ny, nz))
    for iz in range(0, nz, subsampling):
        img_sub.data[..., iz] = data[..., iz]
    # Add non-numerical values at the top corner of the image
    if hasnan:
        img.data[0, 0, 0] = np.nan
        img.data[1, 0, 0] = np.inf
    # Update orientation
    img.change_orientation(orientation)
    img_sub.change_orientation(orientation)
    return img, img_sub
示例#33
0
def mask2label(path_label, aim='full'):
    """
    Convert nifti image to an array of coordinates
    :param path_label: path of nifti image
    :return:
    """
    a = Image(path_label)
    a.change_orientation('RPI')
    arr = np.array(a.data)
    print(path_label)
    list_label_image = []
    for i in range(len(arr.nonzero()[0])):
        x = arr.nonzero()[0][i]
        y = arr.nonzero()[1][i]
        z = arr.nonzero()[2][i]
        if aim == 'full':
            if arr[x, y, z] < 30 and arr[x, y, z] != 1:
                list_label_image.append([x, y, z, arr[x, y, z]])
        elif aim == 'c2':
            if arr[x, y, z] == 3:
                list_label_image.append([x, y, z, arr[x, y, z]])
    list_label_image.sort(key=lambda x: x[3])
    return (list_label_image)
示例#34
0
def label_vertebrae(img: Image,
                    vertebral_levels: Sequence[int] = None) -> Image:
    """
    Find the center of mass of vertebral levels specified by the user.

    :param img: source image
    :param vertebral_levels: list of vertebral levels
    :returns: image with labels
    """

    og_orientation = img.orientation
    if og_orientation != "RPI":
        img.change_orientation("RPI")

    # get center of mass of each vertebral level
    out = cubic_to_point(img)

    # get list of coordinates for each label
    list_coordinates = out.getNonZeroCoordinates(sorting='value')

    # if user did not specify levels, include all:
    if not vertebral_levels:
        vertebral_levels = [int(i.value) for i in list_coordinates]

    # loop across labels and remove those that are not listed by the user
    for i in range(len(list_coordinates)):
        # check if this level is NOT in vertebral_levels. if not set value to zero
        if not vertebral_levels.count(int(list_coordinates[i].value)):
            out.data[int(list_coordinates[i].x),
                     int(list_coordinates[i].y),
                     int(list_coordinates[i].z)] = 0

    if out.orientation != og_orientation:
        out.change_orientation(og_orientation)
        img.change_orientation(og_orientation)

    return out
    def extract_pmj_symmetrical_sagittal_slice(self):
        """Extract a slice that is symmetrical about the estimated PMJ location."""
        # Here, detection is used just as a way to determine the ROI for the sliding window approach
        self.extract_sagittal_slice()
        self.detect()
        self.get_max_position()
        image = Image(self.fname_im)  # img in PIR orientation
        self.rl_coord = compute_cross_corr_3d(image.change_orientation('RPI'),
                                              [
                                                  self.rl_coord,
                                                  self.pa_coord,
                                                  self.is_coord,
                                              ])  # Find R-L symmetry

        # Replace the mid-sagittal slice, to be used for the "main" PMJ detection
        run_proc([
            'sct_crop_image', '-i', self.fname_im, '-zmin',
            str(self.rl_coord), '-zmax',
            str(self.rl_coord + 1), '-o', self.slice2D_im
        ])
class ProcessLabels(object):
    def __init__(self, fname_label, fname_output=None, fname_ref=None, cross_radius=5, dilate=False,
                 coordinates=None, verbose=1, vertebral_levels=None, value=None, msg=""):
        """
        Collection of processes that deal with label creation/modification.
        :param fname_label:
        :param fname_output:
        :param fname_ref:
        :param cross_radius:
        :param dilate:  # TODO: remove dilate (does not seem to be used)
        :param coordinates:
        :param verbose:
        :param vertebral_levels:
        :param value:
        :param msg: string. message to display to the user.
        """
        self.image_input = Image(fname_label, verbose=verbose)
        self.image_ref = None
        if fname_ref is not None:
            self.image_ref = Image(fname_ref, verbose=verbose)

        if isinstance(fname_output, list):
            if len(fname_output) == 1:
                self.fname_output = fname_output[0]
            else:
                self.fname_output = fname_output
        else:
            self.fname_output = fname_output
        self.cross_radius = cross_radius
        self.vertebral_levels = vertebral_levels
        self.dilate = dilate
        self.coordinates = coordinates
        self.verbose = verbose
        self.value = value
        self.msg = msg
        self.output_image = None

    def process(self, type_process):
        # for some processes, change orientation of input image to RPI
        change_orientation = False
        if type_process in ['vert-body', 'vert-disc', 'vert-continuous']:
            # get orientation of input image
            input_orientation = self.image_input.orientation
            # change orientation
            self.image_input.change_orientation('RPI')
            change_orientation = True
        if type_process == 'add':
            self.output_image = self.add(self.value)
        if type_process == 'plan':
            self.output_image = self.plan(self.cross_radius, 100, 5)
        if type_process == 'plan_ref':
            self.output_image = self.plan_ref()
        if type_process == 'increment':
            self.output_image = self.increment_z_inverse()
        if type_process == 'disks':
            self.output_image = self.labelize_from_disks()
        if type_process == 'MSE':
            self.MSE()
            self.fname_output = None
        if type_process == 'remove-reference':
            self.output_image = self.remove_label()
        if type_process == 'remove-symm':
            self.output_image = self.remove_label(symmetry=True)
        if type_process == 'create':
            self.output_image = self.create_label()
        if type_process == 'create-add':
            self.output_image = self.create_label(add=True)
        if type_process == 'create-seg':
            self.output_image = self.create_label_along_segmentation()
        if type_process == 'display-voxel':
            self.display_voxel()
            self.fname_output = None
        if type_process == 'diff':
            self.diff()
            self.fname_output = None
        if type_process == 'dist-inter':  # second argument is in pixel distance
            self.distance_interlabels(5)
            self.fname_output = None
        if type_process == 'cubic-to-point':
            self.output_image = self.cubic_to_point()
        if type_process == 'vert-body':
            self.output_image = self.label_vertebrae(self.vertebral_levels)
        if type_process == 'vert-continuous':
            self.output_image = self.continuous_vertebral_levels()
        if type_process == 'create-viewer':
            self.output_image = self.launch_sagittal_viewer(self.value)
        if type_process in ['remove', 'keep']:
            self.output_image = self.remove_or_keep_labels(self.value, action=type_process)

        # TODO: do not save here. Create another function save() for that
        if self.fname_output is not None:
            if change_orientation:
                self.output_image.change_orientation(input_orientation)
            self.output_image.absolutepath = self.fname_output
            if type_process == 'vert-continuous':
                self.output_image.save(dtype='float32')
            elif type_process != 'plan_ref':
                self.output_image.save(dtype='minimize_int')
            else:
                self.output_image.save()
        return self.output_image

    def add(self, value):
        """
        This function add a specified value to all non-zero voxels.
        """
        image_output = self.image_input.copy()
        # image_output.data *= 0

        coordinates_input = self.image_input.getNonZeroCoordinates()

        # for all points with non-zeros neighbors, force the neighbors to 0
        for i, coord in enumerate(coordinates_input):
            image_output.data[int(coord.x), int(coord.y), int(coord.z)] = image_output.data[int(coord.x), int(coord.y), int(coord.z)] + float(value)
        return image_output

    def create_label(self, add=False):
        """
        Create an image with labels listed by the user.
        This method works only if the user inserted correct coordinates.

        self.coordinates is a list of coordinates (class in msct_types).
        a Coordinate contains x, y, z and value.
        If only one label is to be added, coordinates must be completed with '[]'
        examples:
        For one label:  object_define=ProcessLabels( fname_label, coordinates=[coordi]) where coordi is a 'Coordinate' object from msct_types
        For two labels: object_define=ProcessLabels( fname_label, coordinates=[coordi1, coordi2]) where coordi1 and coordi2 are 'Coordinate' objects from msct_types
        """
        image_output = self.image_input.copy() if add else msct_image.zeros_like(self.image_input)

        # loop across labels
        for i, coord in enumerate(self.coordinates):
            if len(image_output.data.shape) == 3:
                image_output.data[int(coord.x), int(coord.y), int(coord.z)] = coord.value
            elif len(image_output.data.shape) == 2:
                assert str(coord.z) == '0', "ERROR: 2D coordinates should have a Z value of 0. Z coordinate is :" + str(coord.z)
                image_output.data[int(coord.x), int(coord.y)] = coord.value
            else:
                sct.printv('ERROR: Data should be 2D or 3D. Current shape is: ' + str(image_output.data.shape), 1, 'error')
            # display info
            sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' +
                       str(coord.value), 1)
        return image_output

    def create_label_along_segmentation(self):
        """
        Create an image with labels defined along the spinal cord segmentation (or centerline).
        Input image does **not** need to be RPI (re-orientation is done within this function).
        Example:
        object_define=ProcessLabels(fname_segmentation, coordinates=[coord_1, coord_2, coord_i]), where coord_i='z,value'. If z=-1, then use z=nz/2 (i.e. center of FOV in superior-inferior direction)
        Returns
        """
        # reorient input image to RPI
        im_rpi = self.image_input.copy().change_orientation('RPI')
        im_output_rpi = zeros_like(im_rpi)
        # loop across labels
        for ilabel, coord in enumerate(self.coordinates):
            # split coord string
            list_coord = coord.split(',')
            # convert to int() and assign to variable
            z, value = [int(i) for i in list_coord]
            # update z based on native image orientation (z should represent superior-inferior axis)
            coord = Coordinate([z, z, z])  # since we don't know which dimension corresponds to the superior-inferior
            # axis, we put z in all dimensions (we don't care about x and y here)
            _, _, z_rpi = coord.permute(self.image_input, 'RPI')
            # if z=-1, replace with nz/2
            if z == -1:
                z_rpi = int(np.round(im_output_rpi.dim[2] / 2.0))
            # get center of mass of segmentation at given z
            x, y = ndimage.measurements.center_of_mass(np.array(im_rpi.data[:, :, z_rpi]))
            # round values to make indices
            x, y = int(np.round(x)), int(np.round(y))
            # display info
            sct.printv('Label #' + str(ilabel) + ': ' + str(x) + ',' + str(y) + ',' + str(z_rpi) + ' --> ' + str(value), 1)
            if len(im_output_rpi.data.shape) == 3:
                im_output_rpi.data[x, y, z_rpi] = value
            elif len(im_output_rpi.data.shape) == 2:
                assert str(z) == '0', "ERROR: 2D coordinates should have a Z value of 0. Z coordinate is :" + str(z)
                im_output_rpi.data[x, y] = value
        # change orientation back to native
        return im_output_rpi.change_orientation(self.image_input.orientation)

    def plan(self, width, offset=0, gap=1):
        """
        Create a plane of thickness="width" and changes its value with an offset and a gap between labels.
        """
        image_output = msct_image.zeros_like(self.image_input)

        coordinates_input = self.image_input.getNonZeroCoordinates()

        # for all points with non-zeros neighbors, force the neighbors to 0
        for coord in coordinates_input:
            image_output.data[:, :, int(coord.z) - width:int(coord.z) + width] = offset + gap * coord.value

        return image_output

    def plan_ref(self):
        """
        Generate a plane in the reference space for each label present in the input image
        """

        image_output = msct_image.zeros_like(Image(self.image_ref))

        image_input_neg = msct_image.zeros_like(Image(self.image_input))
        image_input_pos = msct_image.zeros_like(Image(self.image_input))

        X, Y, Z = (self.image_input.data < 0).nonzero()
        for i in range(len(X)):
            image_input_neg.data[X[i], Y[i], Z[i]] = -self.image_input.data[X[i], Y[i], Z[i]]  # in order to apply getNonZeroCoordinates
        X_pos, Y_pos, Z_pos = (self.image_input.data > 0).nonzero()
        for i in range(len(X_pos)):
            image_input_pos.data[X_pos[i], Y_pos[i], Z_pos[i]] = self.image_input.data[X_pos[i], Y_pos[i], Z_pos[i]]

        coordinates_input_neg = image_input_neg.getNonZeroCoordinates()
        coordinates_input_pos = image_input_pos.getNonZeroCoordinates()

        image_output.change_type('float32')
        for coord in coordinates_input_neg:
            image_output.data[:, :, int(coord.z)] = -coord.value  # PB: takes the int value of coord.value
        for coord in coordinates_input_pos:
            image_output.data[:, :, int(coord.z)] = coord.value

        return image_output

    def cubic_to_point(self):
        """
        Calculate the center of mass of each group of labels and returns a file of same size with only a
        label by group at the center of mass of this group.
        It is to be used after applying homothetic warping field to a label file as the labels will be dilated.
        Be careful: this algorithm computes the center of mass of voxels with same value, if two groups of voxels with
         the same value are present but separated in space, this algorithm will compute the center of mass of the two
         groups together.
        :return: image_output
        """

        # 0. Initialization of output image
        output_image = msct_image.zeros_like(self.image_input)

        # 1. Extraction of coordinates from all non-null voxels in the image. Coordinates are sorted by value.
        coordinates = self.image_input.getNonZeroCoordinates(sorting='value')

        # 2. Separate all coordinates into groups by value
        groups = dict()
        for coord in coordinates:
            if coord.value in groups:
                groups[coord.value].append(coord)
            else:
                groups[coord.value] = [coord]

        # 3. Compute the center of mass of each group of voxels and write them into the output image
        for value, list_coord in groups.items():
            center_of_mass = sum(list_coord) / float(len(list_coord))
            sct.printv("Value = " + str(center_of_mass.value) + " : (" + str(center_of_mass.x) + ", " + str(center_of_mass.y) + ", " + str(center_of_mass.z) + ") --> ( " + str(np.round(center_of_mass.x)) + ", " + str(np.round(center_of_mass.y)) + ", " + str(np.round(center_of_mass.z)) + ")", verbose=self.verbose)
            output_image.data[int(np.round(center_of_mass.x)), int(np.round(center_of_mass.y)), int(np.round(center_of_mass.z))] = center_of_mass.value

        return output_image

    def increment_z_inverse(self):
        """
        Take all non-zero values, sort them along the inverse z direction, and attributes the values 1,
        2, 3, etc. This function assuming RPI orientation.
        """
        image_output = msct_image.zeros_like(self.image_input)

        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z', reverse_coord=True)

        # for all points with non-zeros neighbors, force the neighbors to 0
        for i, coord in enumerate(coordinates_input):
            image_output.data[int(coord.x), int(coord.y), int(coord.z)] = i + 1

        return image_output

    def labelize_from_disks(self):
        """
        Create an image with regions labelized depending on values from reference.
        Typically, user inputs a segmentation image, and labels with disks position, and this function produces
        a segmentation image with vertebral levels labelized.
        Labels are assumed to be non-zero and incremented from top to bottom, assuming a RPI orientation
        """
        image_output = msct_image.zeros_like(self.image_input)

        coordinates_input = self.image_input.getNonZeroCoordinates()
        coordinates_ref = self.image_ref.getNonZeroCoordinates(sorting='value')

        # for all points in input, find the value that has to be set up, depending on the vertebral level
        for i, coord in enumerate(coordinates_input):
            for j in range(0, len(coordinates_ref) - 1):
                if coordinates_ref[j + 1].z < coord.z <= coordinates_ref[j].z:
                    image_output.data[int(coord.x), int(coord.y), int(coord.z)] = coordinates_ref[j].value

        return image_output

    def label_vertebrae(self, levels_user=None):
        """
        Find the center of mass of vertebral levels specified by the user.
        :return: image_output: Image with labels.
        """
        # get center of mass of each vertebral level
        image_cubic2point = self.cubic_to_point()
        # get list of coordinates for each label
        list_coordinates = image_cubic2point.getNonZeroCoordinates(sorting='value')
        # if user did not specify levels, include all:
        if levels_user[0] == 0:
            levels_user = [int(i.value) for i in list_coordinates]
        # loop across labels and remove those that are not listed by the user
        for i_label in range(len(list_coordinates)):
            # check if this level is NOT in levels_user
            if not levels_user.count(int(list_coordinates[i_label].value)):
                # if not, set value to zero
                image_cubic2point.data[int(list_coordinates[i_label].x), int(list_coordinates[i_label].y), int(list_coordinates[i_label].z)] = 0
        # list all labels
        return image_cubic2point

    def MSE(self, threshold_mse=0):
        """
        Compute the Mean Square Distance Error between two sets of labels (input and ref).
        Moreover, a warning is generated for each label mismatch.
        If the MSE is above the threshold provided (by default = 0mm), a log is reported with the filenames considered here.
        """
        coordinates_input = self.image_input.getNonZeroCoordinates()
        coordinates_ref = self.image_ref.getNonZeroCoordinates()

        # check if all the labels in both the images match
        if len(coordinates_input) != len(coordinates_ref):
            sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord in coordinates_input:
            if np.round(coord.value) not in [np.round(coord_ref.value) for coord_ref in coordinates_ref]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if np.round(coord_ref.value) not in [np.round(coord.value) for coord in coordinates_input]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')

        result = 0.0
        for coord in coordinates_input:
            for coord_ref in coordinates_ref:
                if np.round(coord_ref.value) == np.round(coord.value):
                    result += (coord_ref.z - coord.z) ** 2
                    break
        result = np.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            parent, stem, ext = sct.extract_fname(self.image_input.absolutepath)
            fname_report = os.path.join(parent, 'error_log_{}.txt'.format(stem))
            with open(fname_report, 'w') as f:
                f.write('The labels error (MSE) between {} and {} is: {}\n'.format(
                 os.path.relpath(self.image_input.absolutepath, os.path.dirname(fname_report)),
                 os.path.relpath(self.image_ref.absolutepath, os.path.dirname(fname_report)),
                 result))

        return result

    @staticmethod
    def remove_label_coord(coord_input, coord_ref, symmetry=False):
        """
        coord_input and coord_ref should be sets of CoordinateValue in order to improve speed of intersection
        :param coord_input: set of CoordinateValue
        :param coord_ref: set of CoordinateValue
        :param symmetry: boolean,
        :return: intersection of CoordinateValue: list
        """
        from msct_types import CoordinateValue
        if isinstance(coord_input[0], CoordinateValue) and isinstance(coord_ref[0], CoordinateValue) and symmetry:
            coord_intersection = list(set(coord_input).intersection(set(coord_ref)))
            result_coord_input = [coord for coord in coord_input if coord in coord_intersection]
            result_coord_ref = [coord for coord in coord_ref if coord in coord_intersection]
        else:
            result_coord_ref = coord_ref
            result_coord_input = [coord for coord in coord_input if list(filter(lambda x: x.value == coord.value, coord_ref))]
            if symmetry:
                result_coord_ref = [coord for coord in coord_ref if list(filter(lambda x: x.value == coord.value, result_coord_input))]

        return result_coord_input, result_coord_ref

    def remove_label(self, symmetry=False):
        """
        Compare two label images and remove any labels in input image that are not in reference image.
        The symmetry option enables to remove labels from reference image that are not in input image
        """
        # image_output = Image(self.image_input.dim, orientation=self.image_input.orientation, hdr=self.image_input.hdr, verbose=self.verbose)
        image_output = msct_image.zeros_like(self.image_input)

        result_coord_input, result_coord_ref = self.remove_label_coord(self.image_input.getNonZeroCoordinates(coordValue=True),
                                                                       self.image_ref.getNonZeroCoordinates(coordValue=True), symmetry)

        for coord in result_coord_input:
            image_output.data[int(coord.x), int(coord.y), int(coord.z)] = int(np.round(coord.value))

        if symmetry:
            # image_output_ref = Image(self.image_ref.dim, orientation=self.image_ref.orientation, hdr=self.image_ref.hdr, verbose=self.verbose)
            image_output_ref = Image(self.image_ref, verbose=self.verbose)
            for coord in result_coord_ref:
                image_output_ref.data[int(coord.x), int(coord.y), int(coord.z)] = int(np.round(coord.value))
            image_output_ref.absolutepath = self.fname_output[1]
            image_output_ref.save('minimize_int')

            self.fname_output = self.fname_output[0]

        return image_output

    def display_voxel(self):
        """
        Display all the labels that are contained in the input image.
        The image is suppose to be RPI to display voxels. But works also for other orientations
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='value')
        self.useful_notation = ''
        for coord in coordinates_input:
            sct.printv('Position=(' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ') -- Value= ' + str(coord.value), verbose=self.verbose)
            if self.useful_notation:
                self.useful_notation = self.useful_notation + ':'
            self.useful_notation += str(coord)
        sct.printv('All labels (useful syntax):', verbose=self.verbose)
        sct.printv(self.useful_notation, verbose=self.verbose)
        return coordinates_input

    def get_physical_coordinates(self):
        """
        This function returns the coordinates of the labels in the physical referential system.
        :return: a list of CoordinateValue, in the physical (scanner) space
        """
        coord = self.image_input.getNonZeroCoordinates(sorting='value')
        phys_coord = []
        for c in coord:
            # convert pixelar coordinates to physical coordinates
            c_p = self.image_input.transfo_pix2phys([[c.x, c.y, c.z]])[0]
            phys_coord.append(CoordinateValue([c_p[0], c_p[1], c_p[2], c.value]))
        return phys_coord

    def get_coordinates_in_destination(self, im_dest, type='discrete'):
        """
        This function calculate the position of labels in the pixelar space of a destination image
        :param im_dest: Object Image
        :param type: 'discrete' or 'continuous'
        :return: a list of CoordinateValue, in the pixelar (image) space of the destination image
        """
        phys_coord = self.get_physical_coordinates()
        dest_coord = []
        for c in phys_coord:
            if type is 'discrete':
                c_p = im_dest.transfo_phys2pix([[c.x, c.y, c.y]])[0]
            elif type is 'continuous':
                c_p = im_dest.transfo_phys2pix([[c.x, c.y, c.y]], real=False)[0]
            else:
                raise ValueError("The value of 'type' should either be 'discrete' or 'continuous'.")
            dest_coord.append(CoordinateValue([c_p[0], c_p[1], c_p[2], c.value]))
        return dest_coord

    def diff(self):
        """
        Detect any label mismatch between input image and reference image
        """
        coordinates_input = self.image_input.getNonZeroCoordinates()
        coordinates_ref = self.image_ref.getNonZeroCoordinates()

        sct.printv("Label in input image that are not in reference image:")
        for coord in coordinates_input:
            isIn = False
            for coord_ref in coordinates_ref:
                if coord.value == coord_ref.value:
                    isIn = True
                    break
            if not isIn:
                sct.printv(coord.value)

        sct.printv("Label in ref image that are not in input image:")
        for coord_ref in coordinates_ref:
            isIn = False
            for coord in coordinates_input:
                if coord.value == coord_ref.value:
                    isIn = True
                    break
            if not isIn:
                sct.printv(coord_ref.value)

    def distance_interlabels(self, max_dist):
        """
        Calculate the distances between each label in the input image.
        If a distance is larger than max_dist, a warning message is displayed.
        """
        coordinates_input = self.image_input.getNonZeroCoordinates()

        # for all points with non-zeros neighbors, force the neighbors to 0
        for i in range(0, len(coordinates_input) - 1):
            dist = np.sqrt((coordinates_input[i].x - coordinates_input[i + 1].x)**2 + (coordinates_input[i].y - coordinates_input[i + 1].y)**2 + (coordinates_input[i].z - coordinates_input[i + 1].z)**2)
            if dist < max_dist:
                sct.printv('Warning: the distance between label ' + str(i) + '[' + str(coordinates_input[i].x) + ',' + str(coordinates_input[i].y) + ',' + str(
                    coordinates_input[i].z) + ']=' + str(coordinates_input[i].value) + ' and label ' + str(i + 1) + '[' + str(
                    coordinates_input[i + 1].x) + ',' + str(coordinates_input[i + 1].y) + ',' + str(coordinates_input[i + 1].z) + ']=' + str(
                    coordinates_input[i + 1].value) + ' is larger than ' + str(max_dist) + '. Distance=' + str(dist))

    def continuous_vertebral_levels(self):
        """
        This function transforms the vertebral levels file from the template into a continuous file.
        Instead of having integer representing the vertebral level on each slice, a continuous value that represents
        the position of the slice in the vertebral level coordinate system.
        The image must be RPI
        :return:
        """
        im_input = Image(self.image_input, self.verbose)
        im_output = msct_image.zeros_like(self.image_input)

        # 1. extract vertebral levels from input image
        #   a. extract centerline
        #   b. for each slice, extract corresponding level
        nx, ny, nz, nt, px, py, pz, pt = im_input.dim
        from spinalcordtoolbox.centerline.core import get_centerline
        _, arr_ctl, _ = get_centerline(self.image_input, algo_fitting='bspline')
        x_centerline_fit, y_centerline_fit, z_centerline = arr_ctl
        value_centerline = np.array(
            [im_input.data[int(x_centerline_fit[it]), int(y_centerline_fit[it]), int(z_centerline[it])]
             for it in range(len(z_centerline))])

        # 2. compute distance for each vertebral level --> Di for i being the vertebral levels
        vertebral_levels = {}
        for slice_image, level in enumerate(value_centerline):
            if level not in vertebral_levels:
                vertebral_levels[level] = slice_image

        length_levels = {}
        for level in vertebral_levels:
            indexes_slice = np.where(value_centerline == level)
            length_levels[level] = np.sum([np.sqrt(((x_centerline_fit[indexes_slice[0][index_slice + 1]] - x_centerline_fit[indexes_slice[0][index_slice]]) * px)**2 +
                                                     ((y_centerline_fit[indexes_slice[0][index_slice + 1]] - y_centerline_fit[indexes_slice[0][index_slice]]) * py)**2 +
                                                     ((z_centerline[indexes_slice[0][index_slice + 1]] - z_centerline[indexes_slice[0][index_slice]]) * pz)**2)
                                           for index_slice in range(len(indexes_slice[0]) - 1)])

        # 2. for each slice:
        #   a. identify corresponding vertebral level --> i
        #   b. calculate distance of slice from upper vertebral level --> d
        #   c. compute relative distance in the vertebral level coordinate system --> d/Di
        continuous_values = {}
        for it, iz in enumerate(z_centerline):
            level = value_centerline[it]
            indexes_slice = np.where(value_centerline == level)
            indexes_slice = indexes_slice[0][indexes_slice[0] >= it]
            distance_from_level = np.sum([np.sqrt(((x_centerline_fit[indexes_slice[index_slice + 1]] - x_centerline_fit[indexes_slice[index_slice]]) * px * px) ** 2 +
                                                    ((y_centerline_fit[indexes_slice[index_slice + 1]] - y_centerline_fit[indexes_slice[index_slice]]) * py * py) ** 2 +
                                                    ((z_centerline[indexes_slice[index_slice + 1]] - z_centerline[indexes_slice[index_slice]]) * pz * pz) ** 2)
                                          for index_slice in range(len(indexes_slice) - 1)])
            continuous_values[iz] = level + 2.0 * distance_from_level / float(length_levels[level])

        # 3. saving data
        # for each slice, get all non-zero pixels and replace with continuous values
        coordinates_input = self.image_input.getNonZeroCoordinates()
        im_output.change_type(np.float32)
        # for all points in input, find the value that has to be set up, depending on the vertebral level
        for i, coord in enumerate(coordinates_input):
            im_output.data[int(coord.x), int(coord.y), int(coord.z)] = continuous_values[coord.z]

        return im_output

    def launch_sagittal_viewer(self, labels):
        from spinalcordtoolbox.gui import base
        from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog

        params = base.AnatomicalParams()
        params.vertebraes = labels
        params.input_file_name = self.image_input.absolutepath
        params.output_file_name = self.fname_output
        params.subtitle = self.msg
        output = msct_image.zeros_like(self.image_input)
        output.absolutepath = self.fname_output
        launch_sagittal_dialog(self.image_input, output, params)

        return output

    def remove_or_keep_labels(self, labels, action):
        """
        Create or remove labels from self.image_input
        :param list(int): Labels to keep or remove
        :param str: 'remove': remove specified labels (i.e. set to zero), 'keep': keep specified labels and remove the others
        """
        if action == 'keep':
            image_output = msct_image.zeros_like(self.image_input)
        elif action == 'remove':
            image_output = self.image_input.copy()
        coordinates_input = self.image_input.getNonZeroCoordinates()

        for labelNumber in labels:
            isInLabels = False
            for coord in coordinates_input:
                if labelNumber == coord.value:
                    new_coord = coord
                    isInLabels = True
            if isInLabels:
                if action == 'keep':
                    image_output.data[int(new_coord.x), int(new_coord.y), int(new_coord.z)] = new_coord.value
                elif action == 'remove':
                    image_output.data[int(new_coord.x), int(new_coord.y), int(new_coord.z)] = 0.0
            else:
                sct.printv("WARNING: Label " + str(float(labelNumber)) + " not found in input image.", type='warning')

        return image_output
def dummy_centerline(size_arr=(9, 9, 9),
                     pixdim=(1, 1, 1),
                     subsampling=1,
                     dilate_ctl=0,
                     hasnan=False,
                     zeroslice=[],
                     outlier=[],
                     orientation='RPI',
                     debug=False):
    """
    Create a dummy Image centerline of small size. Return the full and sub-sampled version along z. Voxel resolution
    on fully-sampled data is 1x1x1 mm (so, 2x undersampled data along z would have resolution of 1x1x2 mm).
    :param size_arr: tuple: (nx, ny, nz)
    :param pixdim: tuple: (px, py, pz)
    :param subsampling: int >=1. Subsampling factor along z. 1: no subsampling. 2: centerline defined every other z.
    :param dilate_ctl: Dilation of centerline. E.g., if dilate_ctl=1, result will be a square of 3x3 per slice.
                         if dilate_ctl=0, result will be a single pixel per slice.
    :param hasnan: Bool: Image has non-numerical values: nan, inf. In this case, do not subsample.
    :param zeroslice: list int: zero all slices listed in this param
    :param outlier: list int: replace the current point with an outlier at the corner of the image for the slices listed
    :param orientation:
    :param debug: Bool: Write temp files
    :return:
    """
    nx, ny, nz = size_arr
    # create regularized curve, within X-Z plane, located at y=ny/4, passing through the following points:
    x = np.array([round(nx / 4.), round(nx / 2.), round(3 * nx / 4.)])
    z = np.array([0, round(nz / 2.), nz - 1])
    # we use bspline (instead of poly) in order to avoid bad extrapolation at edges
    # see: https://github.com/spinalcordtoolbox/spinalcordtoolbox/pull/2754
    xfit, _ = bspline(z, x, range(nz), 10)
    # p = P.fit(z, x, 3)
    # p = np.poly1d(np.polyfit(z, x, deg=3))
    data = np.zeros((nx, ny, nz))
    arr_ctl = np.array(
        [xfit.astype(np.int), [round(ny / 4.)] * len(range(nz)),
         range(nz)],
        dtype=np.uint16)
    # Loop across dilation of centerline. E.g., if dilate_ctl=1, result will be a square of 3x3 per slice.
    for ixiy_ctl in itertools.product(range(-dilate_ctl, dilate_ctl + 1, 1),
                                      range(-dilate_ctl, dilate_ctl + 1, 1)):
        data[(arr_ctl[0] + ixiy_ctl[0]).tolist(),
             (arr_ctl[1] + ixiy_ctl[1]).tolist(), arr_ctl[2].tolist()] = 1
    # Zero specified slices
    if zeroslice is not []:
        data[:, :, zeroslice] = 0
    # Add outlier
    if outlier is not []:
        # First, zero all the slice
        data[:, :, outlier] = 0
        # Then, add point in the corner
        data[0, 0, outlier] = 1
    # Create image with default orientation LPI
    affine = np.eye(4)
    affine[0:3, 0:3] = affine[0:3, 0:3] * pixdim
    nii = nib.nifti1.Nifti1Image(data, affine)
    img = Image(data, hdr=nii.header, dim=nii.header.get_data_shape())
    # subsample data
    img_sub = img.copy()
    img_sub.data = np.zeros((nx, ny, nz))
    for iz in range(0, nz, subsampling):
        img_sub.data[..., iz] = data[..., iz]
    # Add non-numerical values at the top corner of the image
    if hasnan:
        img.data[0, 0, 0] = np.nan
        img.data[1, 0, 0] = np.inf
    # Update orientation
    img.change_orientation(orientation)
    img_sub.change_orientation(orientation)
    if debug:
        img_sub.save('tmp_dummy_seg_' +
                     datetime.now().strftime("%Y%m%d%H%M%S%f") + '.nii.gz')
    return img, img_sub, arr_ctl
示例#38
0
def deep_segmentation_MSlesion(im_image, contrast_type, ctr_algo='svm', ctr_file=None, brain_bool=True,
                               remove_temp_files=1, verbose=1):
    """
    Segment lesions from MRI data.
    :param im_image: Image() object containing the lesions to segment
    :param contrast_type: Constrast of the image. Need to use one supported by the CNN models.
    :param ctr_algo: Algo to find the centerline. See sct_get_centerline
    :param ctr_file: Centerline or segmentation (optional)
    :param brain_bool: If brain if present or not in the image.
    :param remove_temp_files:
    :return:
    """

    # create temporary folder with intermediate results
    tmp_folder = sct.TempFolder(verbose=verbose)
    tmp_folder_path = tmp_folder.get_path()
    if ctr_algo == 'file':  # if the ctr_file is provided
        tmp_folder.copy_from(ctr_file)
        file_ctr = os.path.basename(ctr_file)
    else:
        file_ctr = None
    tmp_folder.chdir()

    # orientation of the image, should be RPI
    logger.info("\nReorient the image to RPI, if necessary...")
    fname_in = im_image.absolutepath
    original_orientation = im_image.orientation
    fname_orient = 'image_in_RPI.nii'
    im_image.change_orientation('RPI').save(fname_orient)

    input_resolution = im_image.dim[4:7]

    # find the spinal cord centerline - execute OptiC binary
    logger.info("\nFinding the spinal cord centerline...")
    contrast_type_ctr = contrast_type.split('_')[0]
    fname_res, centerline_filename = find_centerline(algo=ctr_algo,
                                                    image_fname=fname_orient,
                                                    contrast_type=contrast_type_ctr,
                                                    brain_bool=brain_bool,
                                                    folder_output=tmp_folder_path,
                                                    remove_temp_files=remove_temp_files,
                                                    centerline_fname=file_ctr)
    im_nii, ctr_nii = Image(fname_res), Image(centerline_filename)

    # crop image around the spinal cord centerline
    logger.info("\nCropping the image around the spinal cord...")
    crop_size = 48
    X_CROP_LST, Y_CROP_LST, Z_CROP_LST, im_crop_nii = crop_image_around_centerline(im_in=im_nii,
                                                                                  ctr_in=ctr_nii,
                                                                                  crop_size=crop_size)
    del ctr_nii

    # normalize the intensity of the images
    logger.info("Normalizing the intensity...")
    im_norm_in = apply_intensity_normalization(img=im_crop_nii, contrast=contrast_type)
    del im_crop_nii

    # resample to 0.5mm isotropic
    fname_norm = sct.add_suffix(fname_orient, '_norm')
    im_norm_in.save(fname_norm)
    fname_res3d = sct.add_suffix(fname_norm, '_resampled3d')
    resampling.resample_file(fname_norm, fname_res3d, '0.5x0.5x0.5', 'mm', 'linear',
                             verbose=0)

    # segment data using 3D convolutions
    logger.info("\nSegmenting the MS lesions using deep learning on 3D patches...")
    segmentation_model_fname = os.path.join(sct.__sct_dir__, 'data', 'deepseg_lesion_models',
                                            '{}_lesion.h5'.format(contrast_type))
    fname_seg_crop_res = sct.add_suffix(fname_res3d, '_lesionseg')
    im_res3d = Image(fname_res3d)
    seg_im = segment_3d(model_fname=segmentation_model_fname,
                        contrast_type=contrast_type,
                        im=im_res3d.copy())
    seg_im.save(fname_seg_crop_res)
    del im_res3d, seg_im

    # resample to the initial pz resolution
    fname_seg_res2d = sct.add_suffix(fname_seg_crop_res, '_resampled2d')
    initial_2d_resolution = 'x'.join(['0.5', '0.5', str(input_resolution[2])])
    resampling.resample_file(fname_seg_crop_res, fname_seg_res2d, initial_2d_resolution,
                                                           'mm', 'linear', verbose=0)
    seg_crop = Image(fname_seg_res2d)

    # reconstruct the segmentation from the crop data
    logger.info("\nReassembling the image...")
    seg_uncrop_nii = uncrop_image(ref_in=im_nii, data_crop=seg_crop.copy().data, x_crop_lst=X_CROP_LST,
                                  y_crop_lst=Y_CROP_LST, z_crop_lst=Z_CROP_LST)
    fname_seg_res_RPI = sct.add_suffix(fname_in, '_res_RPI_seg')
    seg_uncrop_nii.save(fname_seg_res_RPI)
    del seg_crop

    # resample to initial resolution
    logger.info("Resampling the segmentation to the original image resolution...")
    initial_resolution = 'x'.join([str(input_resolution[0]), str(input_resolution[1]), str(input_resolution[2])])
    fname_seg_RPI = sct.add_suffix(fname_in, '_RPI_seg')
    resampling.resample_file(fname_seg_res_RPI, fname_seg_RPI, initial_resolution,
                                                           'mm', 'linear', verbose=0)
    seg_initres_nii = Image(fname_seg_RPI)

    if ctr_algo == 'viewer':  # resample and reorient the viewer labels
        fname_res_labels = sct.add_suffix(fname_orient, '_labels-centerline')
        resampling.resample_file(fname_res_labels, fname_res_labels, initial_resolution,
                                                           'mm', 'linear', verbose=0)
        im_image_res_labels_downsamp = Image(fname_res_labels).change_orientation(original_orientation)
    else:
        im_image_res_labels_downsamp = None

    if verbose == 2:
        fname_res_ctr = sct.add_suffix(fname_orient, '_ctr')
        resampling.resample_file(fname_res_ctr, fname_res_ctr, initial_resolution,
                                                           'mm', 'linear', verbose=0)
        im_image_res_ctr_downsamp = Image(fname_res_ctr).change_orientation(original_orientation)
    else:
        im_image_res_ctr_downsamp = None

    # binarize the resampled image to remove interpolation effects
    logger.info("\nBinarizing the segmentation to avoid interpolation effects...")
    thr = 0.1
    seg_initres_nii.data[np.where(seg_initres_nii.data >= thr)] = 1
    seg_initres_nii.data[np.where(seg_initres_nii.data < thr)] = 0

    # reorient to initial orientation
    logger.info("\nReorienting the segmentation to the original image orientation...")
    tmp_folder.chdir_undo()

    # remove temporary files
    if remove_temp_files:
        logger.info("\nRemove temporary files...")
        tmp_folder.cleanup()

    # reorient to initial orientation
    return seg_initres_nii.change_orientation(original_orientation), im_image_res_labels_downsamp, im_image_res_ctr_downsamp
def dummy_segmentation(size_arr=(256, 256, 256), pixdim=(1, 1, 1), dtype=np.float64, orientation='LPI',
                       shape='rectangle', angle_RL=0, angle_AP=0, angle_IS=0, radius_RL=5.0, radius_AP=3.0,
                       zeroslice=[], debug=False):
    """Create a dummy Image with a ellipse or ones running from top to bottom in the 3rd dimension, and rotate the image
    to make sure that compute_csa and compute_shape properly estimate the centerline angle.
    :param size_arr: tuple: (nx, ny, nz)
    :param pixdim: tuple: (px, py, pz)
    :param dtype: Numpy dtype.
    :param orientation: Orientation of the image. Default: LPI
    :param shape: {'rectangle', 'ellipse'}
    :param angle_RL: int: angle around RL axis (in deg)
    :param angle_AP: int: angle around AP axis (in deg)
    :param angle_IS: int: angle around IS axis (in deg)
    :param radius_RL: float: 1st radius. With a, b = 50.0, 30.0 (in mm), theoretical CSA of ellipse is 4712.4
    :param radius_AP: float: 2nd radius
    :param zeroslice: list int: zero all slices listed in this param
    :param debug: Write temp files for debug
    :return: img: Image object
    """
    # Initialization
    padding = 15  # Padding size (isotropic) to avoid edge effect during rotation
    # Create a 3d array, with dimensions corresponding to x: RL, y: AP, z: IS
    nx, ny, nz = [int(size_arr[i] * pixdim[i]) for i in range(3)]
    data = np.random.random((nx, ny, nz)) * 0.
    xx, yy = np.mgrid[:nx, :ny]
    # loop across slices and add object
    for iz in range(nz):
        if shape == 'rectangle':  # theoretical CSA: (a*2+1)(b*2+1)
            data[:, :, iz] = ((abs(xx - nx / 2) <= radius_RL) & (abs(yy - ny / 2) <= radius_AP)) * 1
        if shape == 'ellipse':
            data[:, :, iz] = (((xx - nx / 2) / radius_RL) ** 2 + ((yy - ny / 2) / radius_AP) ** 2 <= 1) * 1

    # Pad to avoid edge effect during rotation
    data = np.pad(data, padding, 'reflect')

    # ROTATION ABOUT IS AXIS
    # rotate (in deg), and re-grid using linear interpolation
    data_rotIS = rotate(data, angle_IS, resize=False, center=None, order=1, mode='constant', cval=0, clip=False,
                        preserve_range=False)

    # ROTATION ABOUT RL AXIS
    # Swap x-z axes (to make a rotation within y-z plane, because rotate will apply rotation on the first 2 dims)
    data_rotIS_swap = data_rotIS.swapaxes(0, 2)
    # rotate (in deg), and re-grid using linear interpolation
    data_rotIS_swap_rotRL = rotate(data_rotIS_swap, angle_RL, resize=False, center=None, order=1, mode='constant',
                                   cval=0, clip=False, preserve_range=False)
    # swap back
    data_rotIS_rotRL = data_rotIS_swap_rotRL.swapaxes(0, 2)

    # ROTATION ABOUT AP AXIS
    # Swap y-z axes (to make a rotation within x-z plane)
    data_rotIS_rotRL_swap = data_rotIS_rotRL.swapaxes(1, 2)
    # rotate (in deg), and re-grid using linear interpolation
    data_rotIS_rotRL_swap_rotAP = rotate(data_rotIS_rotRL_swap, angle_AP, resize=False, center=None, order=1,
                                         mode='constant', cval=0, clip=False, preserve_range=False)
    # swap back
    data_rot = data_rotIS_rotRL_swap_rotAP.swapaxes(1, 2)

    # Crop image (to remove padding)
    data_rot_crop = data_rot[padding:nx+padding, padding:ny+padding, padding:nz+padding]

    # Zero specified slices
    if zeroslice is not []:
        data_rot_crop[:, :, zeroslice] = 0

    # Create nibabel object
    xform = np.eye(4)
    for i in range(3):
        xform[i][i] = 1  # in [mm]
    nii = nib.nifti1.Nifti1Image(data_rot_crop.astype('float32'), xform)
    # Create nipy object and resample to desired resolution
    nii_nipy = nifti2nipy(nii)
    nii_nipy_r = resample_nipy(nii_nipy, new_size='x'.join([str(i) for i in pixdim]), new_size_type='mm',
                               interpolation='linear', dtype=dtype)
    nii_r = nipy2nifti(nii_nipy_r)
    # Create Image object. Default orientation is LPI.
    # For debugging add .save() at the end of the command below
    img = Image(nii_r.get_data(), hdr=nii_r.header, dim=nii_r.header.get_data_shape())
    # Update orientation
    img.change_orientation(orientation)
    if debug:
        img.save('tmp_dummy_seg_'+datetime.now().strftime("%Y%m%d%H%M%S%f")+'.nii.gz')
    return img