Ejemplo n.º 1
0
    def generate_mask_pmj(self):
        """Output the PMJ mask."""
        if self.pa_coord != -1:  # If PMJ has been detected
            im = Image(''.join(extract_fname(
                self.fname_im)[1:]))  # image in PIR orientation
            im_mask = im.copy()
            im_mask.data *= 0  # empty mask

            im_mask.data[self.pa_coord, self.is_coord,
                         self.rl_coord] = 50  # voxel with value = 50

            if self.quality_control:  # output QC image
                self.save_qc(im.data[:, :, self.rl_coord],
                             [self.pa_coord, self.is_coord])

            im_mask.setFileName(self.fname_out)

            im_mask = set_orientation(
                im_mask, self.orientation_im,
                fname_out=self.fname_out)  # reorient data

            x_pmj, y_pmj, z_pmj = np.where(im_mask.data == 50)
            printv('\tx_pmj = ' + str(x_pmj[0]), self.verbose, 'info')
            printv('\ty_pmj = ' + str(y_pmj[0]), self.verbose, 'info')
            printv('\tz_pmj = ' + str(z_pmj[0]), self.verbose, 'info')

            im_mask.save()
Ejemplo n.º 2
0
def segment_2d(model_fname, contrast_type, input_size, fname_in, fname_out):
    """Segment data using 2D convolutions."""
    seg_model = nn_architecture_seg(height=input_size[0],
                                    width=input_size[1],
                                    depth=2 if contrast_type != 't2' else 3,
                                    features=32,
                                    batchnorm=False,
                                    dropout=0.0)
    seg_model.load_weights(model_fname)

    image_normalized = Image(fname_in)
    seg_crop = image_normalized.copy()
    seg_crop.data *= 0
    seg_crop.changeType('uint8')

    data_norm = image_normalized.data
    x_cOm, y_cOm = None, None
    # for zz in list(reversed(range(image_normalized.dim[2]))):
    for zz in range(image_normalized.dim[2]):
        pred_seg = seg_model.predict(
            np.expand_dims(np.expand_dims(data_norm[:, :, zz], -1),
                           0))[0, :, :, 0]
        pred_seg_th = (pred_seg > 0.5).astype(int)

        pred_seg_pp = post_processing_slice_wise(pred_seg_th, x_cOm, y_cOm)
        seg_crop.data[:, :, zz] = pred_seg_pp

        if 1 in pred_seg_pp:
            x_cOm, y_cOm = center_of_mass(pred_seg_pp)
            x_cOm, y_cOm = np.round(x_cOm), np.round(y_cOm)
    seg_crop.setFileName(fname_out)
    seg_crop.save()

    return seg_crop.data
Ejemplo n.º 3
0
def multicomponent_merge(fname_list):
    from numpy import zeros, reshape
    # WARNING: output multicomponent is not optimal yet, some issues may be related to the use of this function

    im_0 = Image(fname_list[0])
    new_shape = list(im_0.data.shape)
    if len(new_shape) == 3:
        new_shape.append(1)
    new_shape.append(len(fname_list))
    new_shape = tuple(new_shape)

    data_out = zeros(new_shape)
    for i, fname in enumerate(fname_list):
        im = Image(fname)
        dat = im.data
        if len(dat.shape) == 2:
            data_out[:, :, 0, 0, i] = dat.astype('float32')
        elif len(dat.shape) == 3:
            data_out[:, :, :, 0, i] = dat.astype('float32')
        elif len(dat.shape) == 4:
            data_out[:, :, :, :, i] = dat.astype('float32')
        del im
        del dat
    im_out = im_0.copy()
    im_out.data = data_out.astype('float32')
    im_out.hdr.set_intent('vector', (), '')
    im_out.setFileName(im_out.file_name+'_multicomponent'+im_out.ext)
    return im_out
Ejemplo n.º 4
0
def multicomponent_merge(fname_list):
    from numpy import zeros
    # WARNING: output multicomponent is not optimal yet, some issues may be related to the use of this function

    im_0 = Image(fname_list[0])
    new_shape = list(im_0.data.shape)
    if len(new_shape) == 3:
        new_shape.append(1)
    new_shape.append(len(fname_list))
    new_shape = tuple(new_shape)

    data_out = zeros(new_shape)
    for i, fname in enumerate(fname_list):
        im = Image(fname)
        dat = im.data
        if len(dat.shape) == 2:
            data_out[:, :, 0, 0, i] = dat.astype('float32')
        elif len(dat.shape) == 3:
            data_out[:, :, :, 0, i] = dat.astype('float32')
        elif len(dat.shape) == 4:
            data_out[:, :, :, :, i] = dat.astype('float32')
        del im
        del dat
    im_out = im_0.copy()
    im_out.data = data_out.astype('float32')
    im_out.hdr.set_intent('vector', (), '')
    im_out.setFileName(im_out.file_name + '_multicomponent' + im_out.ext)
    return im_out
    def measure(self):
        im_lesion = Image(self.fname_label)
        im_lesion_data = im_lesion.data
        p_lst = im_lesion.dim[3:6]

        label_lst = [l for l in np.unique(im_lesion_data) if l]  # lesion label IDs list

        if self.path_template is not None:
            if os.path.isfile(self.path_levels):
                img_vert = Image(self.path_levels)
                im_vert_data = img_vert.data
                self.vert_lst = [v for v in np.unique(im_vert_data) if v]  # list of vertebral levels available in the input image

            else:
                im_vert_data = None
                printv('ERROR: the file ' + self.path_levels + ' does not exist. Please make sure the template was correctly registered and warped (sct_register_to_template or sct_register_multimodal and sct_warp_template)', type='error')

            # In order to open atlas images only one time
            atlas_data_dct = {}  # dict containing the np.array of the registrated atlas
            for fname_atlas_roi in self.atlas_roi_lst:
                tract_id = int(fname_atlas_roi.split('_')[-1].split('.nii.gz')[0])
                img_cur = Image(fname_atlas_roi)
                img_cur_copy = img_cur.copy()
                atlas_data_dct[tract_id] = img_cur_copy.data
                del img_cur

        self.volumes = np.zeros((im_lesion.dim[2], len(label_lst)))

        # iteration across each lesion to measure statistics
        for lesion_label in label_lst:
            im_lesion_data_cur = np.copy(im_lesion_data == lesion_label)
            printv('\nMeasures on lesion #' + str(lesion_label) + '...', self.verbose, 'normal')

            label_idx = self.measure_pd[self.measure_pd.label == lesion_label].index
            self._measure_volume(im_lesion_data_cur, p_lst, label_idx)
            self._measure_length(im_lesion_data_cur, p_lst, label_idx)
            self._measure_diameter(im_lesion_data_cur, p_lst, label_idx)

            # compute lesion distribution for each lesion
            if self.path_template is not None:
                self._measure_eachLesion_distribution(lesion_id=lesion_label,
                                                      atlas_data=atlas_data_dct,
                                                      im_vert=im_vert_data,
                                                      im_lesion=im_lesion_data_cur,
                                                      p_lst=p_lst)

        if self.path_template is not None:
            # compute total lesion distribution
            self._measure_totLesion_distribution(im_lesion=np.copy(im_lesion_data > 0),
                                                 atlas_data=atlas_data_dct,
                                                 im_vert=im_vert_data,
                                                 p_lst=p_lst)

        if self.fname_ref is not None:
            # Compute mean and std value in each labeled lesion
            self._measure_within_im(im_lesion=im_lesion_data, im_ref=Image(self.fname_ref).data, label_lst=label_lst)
    def label_lesion(self):
        printv('\nLabel connected regions of the masked image...', self.verbose, 'normal')
        im = Image(self.fname_mask)
        im_2save = im.copy()
        im_2save.data = label(im.data, connectivity=2)
        im_2save.setFileName(self.fname_label)
        im_2save.save()

        self.measure_pd['label'] = [l for l in np.unique(im_2save.data) if l]
        printv('Lesion count = ' + str(len(self.measure_pd['label'])), self.verbose, 'info')
    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 = set_orientation(im_tmp, self.orientation_extraction)

        dct_metric = {}
        for m in self.metric_lst:
            im_2save = im_tmp.copy()
            im_2save.changeType(type='float64')
            im_2save.data *= 0
            dct_metric[m] = im_2save
            # dct_metric[m] = Image(self.fname_metric_lst[m])

        timer = Timer(number_of_iteration=len(self.dct_im_seg['im']))
        timer.start()

        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], [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]

            timer.add_iteration()

        timer.stop()

        for m in self.metric_lst:
            fname_out = add_suffix(''.join(extract_fname(self.param.fname_im)[1:]), '_' + m)
            dct_metric[m].setFileName(fname_out)
            dct_metric[m].save()
            self.fname_metric_lst[m] = fname_out
Ejemplo n.º 8
0
def apply_intensity_normalization(img_path, fname_out, params=None):
    """Standardize the intensity range."""
    img = Image(img_path)
    img_normalized = img.copy()
    data2norm = img.data.astype(np.float32)

    img_normalized.data = scale_intensity(data2norm)

    img_normalized.changeType('float32')
    img_normalized.setFileName(fname_out)
    img_normalized.save()
Ejemplo n.º 9
0
def test_integrity(param_test):
    """
    Test integrity of function
    """
    # initializations
    mse_detection = float('nan')

    # extract name of output centerline: data_centerline_optic.nii.gz
    file_ctr = os.path.join(
        param_test.path_output,
        sct.add_suffix(param_test.file_input, '_centerline_optic'))

    # open output segmentation
    im_ctr = Image(file_ctr)

    # open ground truth
    im_seg_manual = Image(param_test.fname_gt)
    im_ctr_manual = im_seg_manual.copy()  # Create Ctr GT from SC seg GT

    if im_ctr_manual.orientation != 'RPI':
        im_ctr_manual.change_orientation('RPI')

    im_ctr_manua_data = im_ctr_manual.data

    # Compute center of mass of the SC seg on each axial slice.
    center_of_mass_x_y_z_lst = [[
        int(center_of_mass(im_ctr_manua_data[:, :, zz])[0]),
        int(center_of_mass(im_ctr_manua_data[:, :, zz])[1]), zz
    ] for zz in range(im_ctr_manual.dim[2])]

    im_ctr_manual.data *= 0
    for x_y_z in center_of_mass_x_y_z_lst:
        im_ctr_manual.data[x_y_z[0], x_y_z[1], x_y_z[2]] = 1

    # compute MSE between generated ctr and ctr from database
    mse_detection = compute_mse(im_ctr, im_ctr_manual)

    param_test.output += 'Computed MSE: ' + str(mse_detection)
    param_test.output += 'MSE threshold (if computed MSE higher: fail): ' + str(
        param_test.mse_threshold)

    if mse_detection > param_test.mse_threshold:
        param_test.status = 99
        param_test.output += '--> FAILED'
    else:
        param_test.output += '--> PASSED'

    # update Panda structure
    param_test.results['mse_detection'] = mse_detection

    return param_test
Ejemplo n.º 10
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.change_orientation('RPI')

    # load centerline
    im_centerline = Image(fname_centerline)
    im_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 padding 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-1) * x_centerline_fit[-1]])

    # loop across slices and apply translation
    im_anat_flattened = im_anat.copy()
    # change type to float32 because of subsequent conversion (img_as_float). See #1790
    im_anat_flattened.changeType('float32')
    for iz in range(nz):
        # compute translation along x (R-L)
        translation_x = x_centerline_extended[iz] - 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.setFileName(fname_out)
    im_anat_flattened.save()

    sct.display_viewer_syntax([fname_anat, fname_out])
Ejemplo n.º 11
0
def uncrop_image(fname_ref, fname_out, data_crop, x_crop_lst, y_crop_lst):
    """Reconstruc the data from the crop segmentation."""
    im = Image(fname_ref)
    seg_unCrop = im.copy()
    seg_unCrop.data *= 0
    seg_unCrop.changeType('uint8')

    crop_size_x, crop_size_y = data_crop.shape[:2]

    for zz in range(len(x_crop_lst)):
        pred_seg = data_crop[:, :, zz]
        x_start, y_start = int(x_crop_lst[zz]), int(y_crop_lst[zz])
        x_end = x_start + crop_size_x if x_start + crop_size_x < seg_unCrop.dim[
            0] else seg_unCrop.dim[0]
        y_end = y_start + crop_size_y if y_start + crop_size_y < seg_unCrop.dim[
            1] else seg_unCrop.dim[1]
        seg_unCrop.data[x_start:x_end, y_start:y_end,
                        zz] = pred_seg[0:x_end - x_start, 0:y_end - y_start]

    seg_unCrop.setFileName(fname_out)
    seg_unCrop.save()
Ejemplo n.º 12
0
def apply_intensity_standardization_model(img_path,
                                          landmarks_pd,
                                          fname_out,
                                          max_interp='exp',
                                          per_slice=0):
    """Description: apply the learned intensity landmarks to the input image."""
    img = Image(img_path)
    img_data = np.asarray(img.data)

    if per_slice:
        output = np.zeros(img_data.shape)
        for zz in range(img_data.shape[2]):
            output[:, :,
                   zz] = intensity_standardization(img_data[:, :, zz],
                                                   landmarks_pd, max_interp)
    else:
        output = intensity_standardization(img_data, landmarks_pd, max_interp)

    # save resulting image
    img_normalized = img.copy()
    img_normalized.data = output
    img_normalized.setFileName(fname_out)
    img_normalized.save()
Ejemplo n.º 13
0
def crop_image_around_centerline(filename_in, filename_ctr, filename_out,
                                 crop_size):
    """Crop the input image around the input centerline file."""
    im_in, data_ctr = Image(filename_in), Image(filename_ctr).data

    im_new = im_in.copy()
    im_new.dim = tuple([crop_size, crop_size, im_in.dim[2]] +
                       list(im_in.dim[3:]))

    x_lst, y_lst = [], []
    data_im_new = np.zeros((crop_size, crop_size, im_in.dim[2]))
    for zz in range(im_in.dim[2]):
        if 1 in np.array(data_ctr[:, :, zz]):
            x_ctr, y_ctr = center_of_mass(np.array(data_ctr[:, :, zz]))

            x_start, x_end = _find_crop_start_end(x_ctr, crop_size,
                                                  im_in.dim[0])
            y_start, y_end = _find_crop_start_end(y_ctr, crop_size,
                                                  im_in.dim[1])

            crop_im = np.zeros((crop_size, crop_size))
            x_shape, y_shape = im_in.data[x_start:x_end, y_start:y_end,
                                          zz].shape
            crop_im[:x_shape, :y_shape] = im_in.data[x_start:x_end,
                                                     y_start:y_end, zz]

            data_im_new[:, :, zz] = crop_im

            x_lst.append(str(x_start))
            y_lst.append(str(y_start))

    im_new.data = data_im_new
    im_new.setFileName(filename_out)
    im_new.save()
    del im_in, im_new

    return x_lst, y_lst
Ejemplo n.º 14
0
def interpolate_im_to_ref(im_input,
                          im_input_sc,
                          new_res=0.3,
                          sq_size_size_mm=22.5,
                          interpolation_mode=3):
    nx, ny, nz, nt, px, py, pz, pt = im_input.dim

    im_input_sc = im_input_sc.copy()
    im_input = im_input.copy()

    # keep only spacing and origin in qform to avoid rotation issues
    input_qform = im_input.hdr.get_qform()
    for i in range(4):
        for j in range(4):
            if i != j and j != 3:
                input_qform[i, j] = 0

    im_input.hdr.set_qform(input_qform)
    im_input.hdr.set_sform(input_qform)
    im_input_sc.hdr = im_input.hdr

    sq_size = int(sq_size_size_mm / new_res)
    # create a reference image : square of ones
    im_ref = Image(np.ones((sq_size, sq_size, 1), dtype=np.int),
                   dim=(sq_size, sq_size, 1, 0, new_res, new_res, pz, 0),
                   orientation='RPI')

    # copy input qform matrix to reference image
    im_ref.hdr.set_qform(im_input.hdr.get_qform())
    im_ref.hdr.set_sform(im_input.hdr.get_sform())

    # set correct header to reference image
    im_ref.hdr.set_data_shape((sq_size, sq_size, 1))
    im_ref.hdr.set_zooms((new_res, new_res, pz))

    # save image to set orientation to RPI (not properly done at the creation of the image)
    fname_ref = 'im_ref.nii.gz'
    im_ref.setFileName(fname_ref)
    im_ref.save()
    im_ref = set_orientation(im_ref, 'RPI', fname_out=fname_ref)

    # set header origin to zero to get physical coordinates of the center of the square
    im_ref.hdr.as_analyze_map()['qoffset_x'] = 0
    im_ref.hdr.as_analyze_map()['qoffset_y'] = 0
    im_ref.hdr.as_analyze_map()['qoffset_z'] = 0
    im_ref.hdr.set_sform(im_ref.hdr.get_qform())
    im_ref.hdr.set_qform(im_ref.hdr.get_qform())
    [[x_square_center_phys, y_square_center_phys,
      z_square_center_phys]] = im_ref.transfo_pix2phys(
          coordi=[[int(sq_size / 2), int(sq_size / 2), 0]])

    list_interpolate_images = []
    # iterate on z dimension of input image
    for iz in range(nz):
        # copy reference image: one reference image per slice
        im_ref_slice_iz = im_ref.copy()

        # get center of mass of SC for slice iz
        x_seg, y_seg = (im_input_sc.data[:, :, iz] > 0).nonzero()
        x_center, y_center = np.mean(x_seg), np.mean(y_seg)
        [[x_center_phys, y_center_phys, z_center_phys]
         ] = im_input_sc.transfo_pix2phys(coordi=[[x_center, y_center, iz]])

        # center reference image on SC for slice iz
        im_ref_slice_iz.hdr.as_analyze_map(
        )['qoffset_x'] = x_center_phys - x_square_center_phys
        im_ref_slice_iz.hdr.as_analyze_map(
        )['qoffset_y'] = y_center_phys - y_square_center_phys
        im_ref_slice_iz.hdr.as_analyze_map()['qoffset_z'] = z_center_phys
        im_ref_slice_iz.hdr.set_sform(im_ref_slice_iz.hdr.get_qform())
        im_ref_slice_iz.hdr.set_qform(im_ref_slice_iz.hdr.get_qform())

        # interpolate input image to reference image
        im_input_interpolate_iz = im_input.interpolate_from_image(
            im_ref_slice_iz,
            interpolation_mode=interpolation_mode,
            border='nearest')
        # reshape data to 2D if needed
        if len(im_input_interpolate_iz.data.shape) == 3:
            im_input_interpolate_iz.data = im_input_interpolate_iz.data.reshape(
                im_input_interpolate_iz.data.shape[:-1])
        # add slice to list
        list_interpolate_images.append(im_input_interpolate_iz)

    return list_interpolate_images
class MultiLabelRegistration:
    def __init__(
        self,
        fname_gm,
        fname_wm,
        path_template,
        fname_warp_template2target,
        param=None,
        fname_warp_target2template=None,
        apply_warp_template=0,
    ):
        if param is None:
            self.param = Param()
        else:
            self.param = param
        self.im_gm = Image(fname_gm)
        self.im_wm = Image(fname_wm)
        self.path_template = sct.slash_at_the_end(path_template, 1)
        if "MNI-Poly-AMU_GM.nii.gz" in os.listdir(self.path_template + "template/"):
            self.im_template_gm = Image(self.path_template + "template/MNI-Poly-AMU_GM.nii.gz")
            self.im_template_wm = Image(self.path_template + "template/MNI-Poly-AMU_WM.nii.gz")
            self.template = "MNI-Poly-AMU"
        else:
            self.im_template_gm = Image(self.path_template + "template/PAM50_gm.nii.gz")
            self.im_template_wm = Image(self.path_template + "template/PAM50_wm.nii.gz")
            self.template = "PAM50"

        # Previous warping fields:
        self.fname_warp_template2target = fname_warp_template2target
        self.fname_warp_target2template = fname_warp_target2template

        # new warping fields:
        self.fname_warp_template2gm = ""
        self.fname_wwarp_gm2template = ""

        # temporary fix - related to issue #871
        self.apply_warp_template = apply_warp_template

    def register(self):
        # accentuate separation WM/GM
        self.im_gm = thr_im(self.im_gm, 0.01, self.param.thr)
        self.im_wm = thr_im(self.im_wm, 0.01, self.param.thr)
        self.im_template_gm = thr_im(self.im_template_gm, 0.01, self.param.thr)
        self.im_template_wm = thr_im(self.im_template_wm, 0.01, self.param.thr)

        ## create multilabel images:
        # copy GM images to keep header information
        im_automatic_ml = self.im_gm.copy()
        im_template_ml = self.im_template_gm.copy()

        # create multi-label segmentation with GM*200 + WM*100 (100 and 200 encoded in self.param.gap)
        im_automatic_ml.data = self.param.gap[1] * self.im_gm.data + self.param.gap[0] * self.im_wm.data
        im_template_ml.data = (
            self.param.gap[1] * self.im_template_gm.data + self.param.gap[0] * self.im_template_wm.data
        )

        # set new names
        fname_automatic_ml = "multilabel_automatic_seg.nii.gz"
        fname_template_ml = "multilabel_template_seg.nii.gz"
        im_automatic_ml.setFileName(fname_automatic_ml)
        im_template_ml.setFileName(fname_template_ml)

        # Create temporary folder and put files in it
        tmp_dir = sct.tmp_create()

        path_gm, file_gm, ext_gm = sct.extract_fname(fname_gm)
        path_warp_template2target, file_warp_template2target, ext_warp_template2target = sct.extract_fname(
            self.fname_warp_template2target
        )

        convert(fname_gm, tmp_dir + file_gm + ext_gm)
        convert(fname_warp_template, tmp_dir + file_warp_template2target + ext_warp_template2target, squeeze_data=0)
        if self.fname_warp_target2template is not None:
            path_warp_target2template, file_warp_target2template, ext_warp_target2template = sct.extract_fname(
                self.fname_warp_target2template
            )
            convert(
                self.fname_warp_target2template,
                tmp_dir + file_warp_target2template + ext_warp_target2template,
                squeeze_data=0,
            )

        os.chdir(tmp_dir)
        # save images
        im_automatic_ml.save()
        im_template_ml.save()

        # apply template2image warping field
        if self.apply_warp_template == 1:
            fname_template_ml_new = sct.add_suffix(fname_template_ml, "_r")
            sct.run(
                "sct_apply_transfo -i "
                + fname_template_ml
                + " -d "
                + fname_automatic_ml
                + " -w "
                + file_warp_template2target
                + ext_warp_template2target
                + " -o "
                + fname_template_ml_new
            )
            fname_template_ml = fname_template_ml_new

        nx, ny, nz, nt, px, py, pz, pt = im_automatic_ml.dim
        size_mask = int(22.5 / px)
        fname_mask = "square_mask.nii.gz"
        sct.run(
            "sct_create_mask -i "
            + fname_automatic_ml
            + " -p centerline,"
            + fname_automatic_ml
            + " -f box -size "
            + str(size_mask)
            + " -o "
            + fname_mask
        )

        fname_automatic_ml, xi, xf, yi, yf, zi, zf = crop_im(fname_automatic_ml, fname_mask)
        fname_template_ml, xi, xf, yi, yf, zi, zf = crop_im(fname_template_ml, fname_mask)

        #        fname_automatic_ml_smooth = sct.add_suffix(fname_automatic_ml, '_smooth')
        #        sct.run('sct_maths -i '+fname_automatic_ml+' -smooth '+str(self.param.smooth)+','+str(self.param.smooth)+',0 -o '+fname_automatic_ml_smooth)
        #        fname_automatic_ml = fname_automatic_ml_smooth

        path_automatic_ml, file_automatic_ml, ext_automatic_ml = sct.extract_fname(fname_automatic_ml)
        path_template_ml, file_template_ml, ext_template_ml = sct.extract_fname(fname_template_ml)

        # Register multilabel images together
        cmd_reg = (
            "sct_register_multimodal -i "
            + fname_template_ml
            + " -d "
            + fname_automatic_ml
            + " -param "
            + self.param.param_reg
        )
        if "centermass" in self.param.param_reg:
            fname_template_ml_seg = sct.add_suffix(fname_template_ml, "_bin")
            sct.run("sct_maths -i " + fname_template_ml + " -bin 0 -o " + fname_template_ml_seg)

            fname_automatic_ml_seg = sct.add_suffix(fname_automatic_ml, "_bin")
            # sct.run('sct_maths -i '+fname_automatic_ml+' -thr 50 -o '+fname_automatic_ml_seg)
            sct.run("sct_maths -i " + fname_automatic_ml + " -bin 50 -o " + fname_automatic_ml_seg)

            cmd_reg += " -iseg " + fname_template_ml_seg + " -dseg " + fname_automatic_ml_seg

        sct.run(cmd_reg)
        fname_warp_multilabel_template2auto = "warp_" + file_template_ml + "2" + file_automatic_ml + ".nii.gz"
        fname_warp_multilabel_auto2template = "warp_" + file_automatic_ml + "2" + file_template_ml + ".nii.gz"

        self.fname_warp_template2gm = "warp_template2" + file_gm + ".nii.gz"
        # fname_warp_multilabel_template2auto = pad_im(fname_warp_multilabel_template2auto, nx, ny, nz, xi, xf, yi, yf, zi, zf)
        # fname_warp_multilabel_auto2template = pad_im(fname_warp_multilabel_auto2template, nx, ny, nz, xi, xf, yi, yf, zi, zf)

        sct.run(
            "sct_concat_transfo -w "
            + file_warp_template2target
            + ext_warp_template2target
            + ","
            + fname_warp_multilabel_template2auto
            + " -d "
            + file_gm
            + ext_gm
            + " -o "
            + self.fname_warp_template2gm
        )

        if self.fname_warp_target2template is not None:
            path_script = os.path.dirname(__file__)
            path_sct = os.path.dirname(path_script)
            if self.template == "MNI-Poly-AMU":
                fname_dest = path_sct + "/data/MNI-Poly-AMU/template/MNI-Poly-AMU_T2.nii.gz"
            elif self.template == "PAM50":
                fname_dest = path_sct + "/data/PAM50/template/PAM50_t2.nii.gz"

            self.fname_warp_gm2template = "warp_" + file_gm + "_gm2template.nii.gz"
            sct.run(
                "sct_concat_transfo -w "
                + fname_warp_multilabel_auto2template
                + ","
                + file_warp_target2template
                + ext_warp_target2template
                + " -d "
                + fname_dest
                + " -o "
                + self.fname_warp_gm2template
            )

        os.chdir("..")

        # sct.generate_output_file(tmp_dir+fname_warp_multilabel_template2auto, self.param.output_folder+'warp_template_multilabel2automatic_seg_multilabel.nii.gz')
        # sct.generate_output_file(tmp_dir+fname_warp_multilabel_auto2template, self.param.output_folder+'warp_automatic_seg_multilabel2template_multilabel.nii.gz')

        sct.generate_output_file(
            tmp_dir + self.fname_warp_template2gm, self.param.output_folder + self.fname_warp_template2gm
        )
        if self.fname_warp_target2template is not None:
            sct.generate_output_file(
                tmp_dir + self.fname_warp_gm2template, self.param.output_folder + self.fname_warp_gm2template
            )

        if self.param.qc:
            fname_grid_warped = visualize_warp(
                tmp_dir + fname_warp_multilabel_template2auto, rm_tmp=self.param.remove_tmp
            )
            path_grid_warped, file_grid_warped, ext_grid_warped = sct.extract_fname(fname_grid_warped)
            sct.generate_output_file(fname_grid_warped, self.param.output_folder + file_grid_warped + ext_grid_warped)

        if self.param.remove_tmp:
            sct.run("rm -rf " + tmp_dir, error_exit="warning")

    def validation(self, fname_manual_gmseg, fname_sc_seg):
        path_manual_gmseg, file_manual_gmseg, ext_manual_gmseg = sct.extract_fname(fname_manual_gmseg)
        path_sc_seg, file_sc_seg, ext_sc_seg = sct.extract_fname(fname_sc_seg)

        # Create tmp folder and copy files in it
        tmp_dir = sct.tmp_create()
        sct.run("cp " + fname_manual_gmseg + " " + tmp_dir + file_manual_gmseg + ext_manual_gmseg)
        sct.run("cp " + fname_sc_seg + " " + tmp_dir + file_sc_seg + ext_sc_seg)
        sct.run(
            "cp " + self.param.output_folder + self.fname_warp_template2gm + " " + tmp_dir + self.fname_warp_template2gm
        )
        os.chdir(tmp_dir)

        sct.run("sct_warp_template -d " + fname_manual_gmseg + " -w " + self.fname_warp_template2gm + " -qc 0 -a 0")
        if "MNI-Poly-AMU_GM.nii.gz" in os.listdir("label/template/"):
            im_new_template_gm = Image("label/template/MNI-Poly-AMU_GM.nii.gz")
            im_new_template_wm = Image("label/template/MNI-Poly-AMU_WM.nii.gz")
        else:
            im_new_template_gm = Image("label/template/PAM50_gm.nii.gz")
            im_new_template_wm = Image("label/template/PAM50_wm.nii.gz")

        im_new_template_gm = thr_im(im_new_template_gm, self.param.thr, self.param.thr)
        im_new_template_wm = thr_im(im_new_template_wm, self.param.thr, self.param.thr)

        self.im_template_gm = thr_im(self.im_template_gm, self.param.thr, self.param.thr)
        self.im_template_wm = thr_im(self.im_template_wm, self.param.thr, self.param.thr)

        fname_new_template_gm = "new_template_gm.nii.gz"
        im_new_template_gm.setFileName(fname_new_template_gm)
        im_new_template_gm.save()

        fname_new_template_wm = "new_template_wm.nii.gz"
        im_new_template_wm.setFileName(fname_new_template_wm)
        im_new_template_wm.save()

        fname_old_template_wm = "old_template_wm.nii.gz"
        self.im_template_wm.setFileName(fname_old_template_wm)
        self.im_template_wm.save()

        fname_old_template_gm = "old_template_gm.nii.gz"
        self.im_template_gm.setFileName(fname_old_template_gm)
        self.im_template_gm.save()

        fname_manual_wmseg = "target_manual_wmseg.nii.gz"
        sct.run(
            "sct_maths -i "
            + file_sc_seg
            + ext_sc_seg
            + " -sub "
            + file_manual_gmseg
            + ext_manual_gmseg
            + " -o "
            + fname_manual_wmseg
        )

        # Compute Hausdorff distance
        status, output_old_hd = sct.run(
            "sct_compute_hausdorff_distance -i "
            + fname_old_template_gm
            + " -r "
            + file_manual_gmseg
            + ext_manual_gmseg
            + " -t 1  -v 1"
        )
        status, output_new_hd = sct.run(
            "sct_compute_hausdorff_distance -i "
            + fname_new_template_gm
            + " -r "
            + file_manual_gmseg
            + ext_manual_gmseg
            + " -t 1  -v 1"
        )

        hd_name = "hd_md_multilabel_reg.txt"
        hd_fic = open(hd_name, "w")
        hd_fic.write(
            'The "diff" columns are comparisons between regular template registration and corrected template registration according to SC internal structure\n'
            "Diff = metric_regular_reg - metric_corrected_reg\n"
        )
        hd_fic.write("#Slice, HD, HD diff, MD, MD diff\n")

        no_ref_slices = []

        init_hd = "Hausdorff's distance  -  First relative Hausdorff's distance median - Second relative Hausdorff's distance median(all in mm)\n"
        old_gm_hd = output_old_hd[output_old_hd.find(init_hd) + len(init_hd) :].split("\n")
        new_gm_hd = output_new_hd[output_new_hd.find(init_hd) + len(init_hd) :].split("\n")

        for i in range(len(old_gm_hd) - 3):  # last two lines are informations
            i_old, val_old = old_gm_hd[i].split(":")
            i_new, val_new = new_gm_hd[i].split(":")
            i_old = int(i_old[-2:])
            i_new = int(i_new[-2:])

            assert i == i_old == i_new, "ERROR: when comparing Hausdorff distances, slice numbers differs."
            hd_old, med1_old, med2_old = val_old.split("-")
            hd_new, med1_new, med2_new = val_new.split("-")

            if float(hd_old) == 0.0:
                no_ref_slices.append(i)
                hd_fic.write(str(i) + ", NO MANUAL SEGMENTATION\n")
            else:
                md_new = max(float(med1_new), float(med2_new))
                md_old = max(float(med1_old), float(med2_old))

                hd_fic.write(
                    str(i)
                    + ", "
                    + hd_new
                    + ", "
                    + str(float(hd_old) - float(hd_new))
                    + ", "
                    + str(md_new)
                    + ", "
                    + str(md_old - md_new)
                    + "\n"
                )
        hd_fic.close()

        # Compute Dice coefficient
        # --- DC old template
        try:
            status_old_gm, output_old_gm = sct.run(
                "sct_dice_coefficient -i "
                + file_manual_gmseg
                + ext_manual_gmseg
                + " -d "
                + fname_old_template_gm
                + " -2d-slices 2",
                error_exit="warning",
                raise_exception=True,
            )
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            corrected_manual_gmseg = file_manual_gmseg + "_in_old_template_space" + ext_manual_gmseg
            sct.run(
                "isct_antsRegistration -d 3 -t Translation[0] -m MI["
                + fname_old_template_gm
                + ","
                + file_manual_gmseg
                + ext_manual_gmseg
                + ",1,16] -o [reg_ref_to_res,"
                + corrected_manual_gmseg
                + "] -n BSpline[3] -c 0 -f 1 -s 0"
            )
            # sct.run('sct_maths -i '+corrected_manual_gmseg+' -thr 0.1 -o '+corrected_manual_gmseg)
            sct.run("sct_maths -i " + corrected_manual_gmseg + " -bin 0.1 -o " + corrected_manual_gmseg)
            status_old_gm, output_old_gm = sct.run(
                "sct_dice_coefficient -i " + corrected_manual_gmseg + " -d " + fname_old_template_gm + "  -2d-slices 2",
                error_exit="warning",
            )

        try:
            status_old_wm, output_old_wm = sct.run(
                "sct_dice_coefficient -i " + fname_manual_wmseg + " -d " + fname_old_template_wm + " -2d-slices 2",
                error_exit="warning",
                raise_exception=True,
            )
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            path_manual_wmseg, file_manual_wmseg, ext_manual_wmseg = sct.extract_fname(fname_manual_wmseg)
            corrected_manual_wmseg = file_manual_wmseg + "_in_old_template_space" + ext_manual_wmseg
            sct.run(
                "isct_antsRegistration -d 3 -t Translation[0] -m MI["
                + fname_old_template_wm
                + ","
                + fname_manual_wmseg
                + ",1,16] -o [reg_ref_to_res,"
                + corrected_manual_wmseg
                + "] -n BSpline[3] -c 0 -f 1 -s 0"
            )
            # sct.run('sct_maths -i '+corrected_manual_wmseg+' -thr 0.1 -o '+corrected_manual_wmseg)
            sct.run("sct_maths -i " + corrected_manual_wmseg + " -bin 0.1 -o " + corrected_manual_wmseg)
            status_old_wm, output_old_wm = sct.run(
                "sct_dice_coefficient -i " + corrected_manual_wmseg + " -d " + fname_old_template_wm + "  -2d-slices 2",
                error_exit="warning",
            )

        # --- DC new template
        try:
            status_new_gm, output_new_gm = sct.run(
                "sct_dice_coefficient -i "
                + file_manual_gmseg
                + ext_manual_gmseg
                + " -d "
                + fname_new_template_gm
                + " -2d-slices 2",
                error_exit="warning",
                raise_exception=True,
            )
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            corrected_manual_gmseg = file_manual_gmseg + "_in_new_template_space" + ext_manual_gmseg
            sct.run(
                "isct_antsRegistration -d 3 -t Translation[0] -m MI["
                + fname_new_template_gm
                + ","
                + file_manual_gmseg
                + ext_manual_gmseg
                + ",1,16] -o [reg_ref_to_res,"
                + corrected_manual_gmseg
                + "] -n BSpline[3] -c 0 -f 1 -s 0"
            )
            # sct.run('sct_maths -i '+corrected_manual_gmseg+' -thr 0.1 -o '+corrected_manual_gmseg)
            sct.run("sct_maths -i " + corrected_manual_gmseg + " -bin 0.1 -o " + corrected_manual_gmseg)
            status_new_gm, output_new_gm = sct.run(
                "sct_dice_coefficient -i " + corrected_manual_gmseg + " -d " + fname_new_template_gm + "  -2d-slices 2",
                error_exit="warning",
            )

        try:
            status_new_wm, output_new_wm = sct.run(
                "sct_dice_coefficient -i " + fname_manual_wmseg + " -d " + fname_new_template_wm + " -2d-slices 2",
                error_exit="warning",
                raise_exception=True,
            )
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            path_manual_wmseg, file_manual_wmseg, ext_manual_wmseg = sct.extract_fname(fname_manual_wmseg)
            corrected_manual_wmseg = file_manual_wmseg + "_in_new_template_space" + ext_manual_wmseg
            sct.run(
                "isct_antsRegistration -d 3 -t Translation[0] -m MI["
                + fname_new_template_wm
                + ","
                + fname_manual_wmseg
                + ",1,16] -o [reg_ref_to_res,"
                + corrected_manual_wmseg
                + "] -n BSpline[3] -c 0 -f 1 -s 0"
            )
            # sct.run('sct_maths -i '+corrected_manual_wmseg+' -thr 0.1 -o '+corrected_manual_wmseg)
            sct.run("sct_maths -i " + corrected_manual_wmseg + " -bin 0.1 -o " + corrected_manual_wmseg)
            status_new_wm, output_new_wm = sct.run(
                "sct_dice_coefficient -i " + corrected_manual_wmseg + " -d " + fname_new_template_wm + "  -2d-slices 2",
                error_exit="warning",
            )

        dice_name = "dice_multilabel_reg.txt"
        dice_fic = open(dice_name, "w")
        dice_fic.write(
            'The "diff" columns are comparisons between regular template registration and corrected template registration according to SC internal structure\n'
            "Diff = metric_corrected_reg - metric_regular_reg\n"
        )
        dice_fic.write("#Slice, WM DC, WM diff, GM DC, GM diff\n")

        init_dc = "2D Dice coefficient by slice:\n"

        old_gm_dc = output_old_gm[output_old_gm.find(init_dc) + len(init_dc) :].split("\n")
        old_wm_dc = output_old_wm[output_old_wm.find(init_dc) + len(init_dc) :].split("\n")
        new_gm_dc = output_new_gm[output_new_gm.find(init_dc) + len(init_dc) :].split("\n")
        new_wm_dc = output_new_wm[output_new_wm.find(init_dc) + len(init_dc) :].split("\n")

        for i in range(len(old_gm_dc)):
            if i not in no_ref_slices:
                i_new_gm, val_new_gm = new_gm_dc[i].split(" ")
                i_new_wm, val_new_wm = new_wm_dc[i].split(" ")
                i_old_gm, val_old_gm = old_gm_dc[i].split(" ")
                i_old_wm, val_old_wm = old_wm_dc[i].split(" ")

                assert (
                    i == int(i_new_gm) == int(i_new_wm) == int(i_old_gm) == int(i_old_wm)
                ), "ERROR: when comparing Dice coefficients, slice numbers differs."

                dice_fic.write(
                    str(i)
                    + ", "
                    + val_new_wm
                    + ", "
                    + str(float(val_new_wm) - float(val_old_wm))
                    + ", "
                    + val_new_gm
                    + ", "
                    + str(float(val_new_gm) - float(val_old_gm))
                    + "\n"
                )
            else:
                dice_fic.write(str(i) + ", NO MANUAL SEGMENTATION\n")
        dice_fic.close()
        os.chdir("..")

        sct.generate_output_file(tmp_dir + hd_name, self.param.output_folder + hd_name)
        sct.generate_output_file(tmp_dir + dice_name, self.param.output_folder + dice_name)

        if self.param.remove_tmp:
            sct.run("rm -rf " + tmp_dir, error_exit="warning")
Ejemplo n.º 16
0
def vertebral_detection(fname, fname_seg, contrast, param, init_disc=[], verbose=1, path_template='', initc2='auto', path_output='../'):
    """
    Find intervertebral discs in straightened image using template matching
    :param fname:
    :param fname_seg:
    :param contrast:
    :param param:  advanced parameters
    :param init_disc:
    :param verbose:
    :param path_template:
    :param path_output: output path for verbose=2 pictures
    :return:
    """
    printv('\nLook for template...', verbose)
    # if path_template == '':
    #     # get path of SCT
    #     from os import path
    #     path_script = path.dirname(__file__)
    #     path_sct = slash_at_the_end(path.dirname(path_script), 1)
    #     folder_template = 'data/template/'
    #     path_template = path_sct+folder_template
    printv('Path template: '+path_template, verbose)

    # adjust file names if MNI-Poly-AMU template is used
    fname_level = get_file_label(path_template+'template/', 'vertebral', output='filewithpath')
    fname_template = get_file_label(path_template+'template/', contrast.upper()+'-weighted', output='filewithpath')

    # if not len(glob(path_template+'MNI-Poly-AMU*.*')) == 0:
    #     contrast = contrast.upper()
    #     file_level = '*_level.nii.gz'
    # else:
    #     file_level = '*_levels.nii.gz'
    #
    # # retrieve file_template based on contrast
    # try:
    #     fname_template_list = glob(path_template + '*' + contrast + '.nii.gz')
    #     fname_template = fname_template_list[0]
    # except IndexError:
    #     printv('\nERROR: No template found. Please check the provided path.', 1, 'error')
    # retrieve disc level from template
    # try:
    #     fname_level_list = glob(path_template+file_level)
    #     fname_level = fname_level_list[0]
    # except IndexError:
    #     printv('\nERROR: File *_levels.nii.gz not found.', 1, 'error')

    # Open template and vertebral levels
    printv('\nOpen template and vertebral levels...', verbose)
    data_template = Image(fname_template).data
    data_disc_template = Image(fname_level).data

    # open anatomical volume
    im_input = Image(fname)
    data = im_input.data

    # smooth data
    from scipy.ndimage.filters import gaussian_filter
    data = gaussian_filter(data, param.smooth_factor, output=None, mode="reflect")

    # get dimension of src
    nx, ny, nz = data.shape
    # define xc and yc (centered in the field of view)
    xc = int(round(nx/2))  # direction RL
    yc = int(round(ny/2))  # direction AP
    # get dimension of template
    nxt, nyt, nzt = data_template.shape
    # define xc and yc (centered in the field of view)
    xct = int(round(nxt/2))  # direction RL
    yct = int(round(nyt/2))  # direction AP

    # define mean distance (in voxel) between adjacent discs: [C1/C2 -> C2/C3], [C2/C3 -> C4/C5], ..., [L1/L2 -> L2/L3]
    centerline_level = data_disc_template[xct, yct, :]
    # attribute value to each disc. Starts from max level, then decrease.
    min_level = centerline_level[centerline_level.nonzero()].min()
    max_level = centerline_level[centerline_level.nonzero()].max()
    list_disc_value_template = range(min_level, max_level)
    # add disc above top one
    list_disc_value_template.insert(int(0), min_level - 1)
    printv('\nDisc values from template: ' + str(list_disc_value_template), verbose)
    # get diff to find transitions (i.e., discs)
    diff_centerline_level = np.diff(centerline_level)
    # get disc z-values
    list_disc_z_template = diff_centerline_level.nonzero()[0].tolist()
    list_disc_z_template.reverse()
    printv('Z-values for each disc: ' + str(list_disc_z_template), verbose)
    list_distance_template = (
        np.diff(list_disc_z_template) * (-1)).tolist()  # multiplies by -1 to get positive distances
    printv('Distances between discs (in voxel): ' + str(list_distance_template), verbose)

    # if automatic mode, find C2/C3 disc
    if init_disc == [] and initc2 == 'auto':
        printv('\nDetect C2/C3 disk...', verbose)
        zrange = range(0, nz)
        ind_c2 = list_disc_value_template.index(2)
        z_peak = compute_corr_3d(src=data, target=data_template, x=xc, xshift=0, xsize=param.size_RL_initc2, y=yc, yshift=param.shift_AP_initc2, ysize=param.size_AP_initc2, z=0, zshift=param.shift_IS_initc2, zsize=param.size_IS_initc2, xtarget=xct, ytarget=yct, ztarget=list_disc_z_template[ind_c2], zrange=zrange, verbose=verbose, save_suffix='_initC2', gaussian_weighting=True, path_output=path_output)
        init_disc = [z_peak, 2]

    # if manual mode, open viewer for user to click on C2/C3 disc
    if init_disc == [] and initc2 == 'manual':
        from sct_viewer import ClickViewer
        # reorient image to SAL to be compatible with viewer
        im_input_SAL = im_input.copy()
        im_input_SAL.change_orientation('SAL')
        viewer = ClickViewer(im_input_SAL, orientation_subplot=['sag', 'ax'])
        viewer.number_of_slices = 1
        pz = 1
        viewer.gap_inter_slice = int(10 / pz)
        viewer.calculate_list_slices()
        viewer.help_url = 'https://sourceforge.net/p/spinalcordtoolbox/wiki/sct_label_vertebrae/attachment/label_vertebrae_viewer.png'
        # start the viewer that ask the user to enter a few points along the spinal cord
        mask_points = viewer.start()
        if mask_points:
            # create the mask containing either the three-points or centerline mask for initialization
            mask_filename = sct.add_suffix(fname, "_mask_viewer")
            sct.run("sct_label_utils -i " + fname + " -create " + mask_points + " -o " + mask_filename, verbose=False)
        else:
            sct.printv('\nERROR: the viewer has been closed before entering all manual points. Please try again.', verbose, type='error')
        # assign new init_disc_z value, which corresponds to the first vector of mask_points. Note, we need to substract from nz due to SAL orientation: in the viewer, orientation is S-I while in this code, it is I-S.
        init_disc = [nz-int(mask_points.split(',')[0]), 2]

    # display init disc
    if verbose == 2:
        import matplotlib
        matplotlib.use('Agg')
        import matplotlib.pyplot as plt
        plt.matshow(np.mean(data[xc-param.size_RL:xc+param.size_RL, :, :], axis=0).transpose(), fignum=50, cmap=plt.cm.gray, clim=[0, 800], origin='lower')
        plt.title('Anatomical image')
        plt.autoscale(enable=False)  # to prevent autoscale of axis when displaying plot
        plt.figure(50), plt.scatter(yc + param.shift_AP_visu, init_disc[0], c='yellow', s=50)
        plt.text(yc + param.shift_AP_visu + 4, init_disc[0], str(init_disc[1]) + '/' + str(init_disc[1] + 1),
                 verticalalignment='center', horizontalalignment='left', color='pink', fontsize=15), plt.draw()
        # plt.ion()  # enables interactive mode

    # FIND DISCS
    # ===========================================================================
    printv('\nDetect intervertebral discs...', verbose)
    # assign initial z and disc
    current_z = init_disc[0]
    current_disc = init_disc[1]
    # mean_distance = mean_distance * pz
    # mean_distance_real = np.zeros(len(mean_distance))
    # create list for z and disc
    list_disc_z = []
    list_disc_value = []
    zrange = range(-10, 10)
    direction = 'superior'
    search_next_disc = True
    while search_next_disc:
        printv('Current disc: '+str(current_disc)+' (z='+str(current_z)+'). Direction: '+direction, verbose)
        try:
            # get z corresponding to current disc on template
            current_z_template = list_disc_z_template[current_disc]
        except:
            # in case reached the bottom (see issue #849)
            printv('WARNING: Reached the bottom of the template. Stop searching.', verbose, 'warning')
            break
        # find next disc
        # N.B. Do not search for C1/C2 disc (because poorly visible), use template distance instead
        if not current_disc in [1]:
            current_z = compute_corr_3d(src=data, target=data_template, x=xc, xshift=0, xsize=param.size_RL, y=yc, yshift=param.shift_AP, ysize=param.size_AP, z=current_z, zshift=0, zsize=param.size_IS, xtarget=xct, ytarget=yct, ztarget=current_z_template, zrange=zrange, verbose=verbose, save_suffix='_disc'+str(current_disc), gaussian_weighting=False, path_output=path_output)

        # display new disc
        if verbose == 2:
            plt.figure(50), plt.scatter(yc+param.shift_AP_visu, current_z, c='yellow', s=50)
            plt.text(yc + param.shift_AP_visu + 4, current_z, str(current_disc)+'/'+str(current_disc+1), verticalalignment='center', horizontalalignment='left', color='yellow', fontsize=15), plt.draw()

        # append to main list
        if direction == 'superior':
            # append at the beginning
            list_disc_z.insert(0, current_z)
            list_disc_value.insert(0, current_disc)
        elif direction == 'inferior':
            # append at the end
            list_disc_z.append(current_z)
            list_disc_value.append(current_disc)

        # adjust correcting factor based on already-identified discs
        if len(list_disc_z) > 1:
            # compute distance between already-identified discs
            list_distance_current = (np.diff(list_disc_z) * (-1)).tolist()
            # retrieve the template distance corresponding to the already-identified discs
            index_disc_identified = [i for i, j in enumerate(list_disc_value_template) if j in list_disc_value[:-1]]
            list_distance_template_identified = [list_distance_template[i] for i in index_disc_identified]
            # divide subject and template distances for the identified discs
            list_subject_to_template_distance = [float(list_distance_current[i]) / list_distance_template_identified[i] for i in range(len(list_distance_current))]
            # average across identified discs to obtain an average correcting factor
            correcting_factor = np.mean(list_subject_to_template_distance)
            printv('.. correcting factor: '+str(correcting_factor), verbose)
        else:
            correcting_factor = 1
        # update list_distance specific for the subject
        list_distance = [int(round(list_distance_template[i] * correcting_factor)) for i in range(len(list_distance_template))]
        # updated average_disc_distance (in case it is needed)
        # average_disc_distance = int(round(np.mean(list_distance)))

        # assign new current_z and disc value
        if direction == 'superior':
            try:
                approx_distance_to_next_disc = list_distance[list_disc_value_template.index(current_disc-1)]
            except ValueError:
                printv('WARNING: Disc value not included in template. Using previously-calculated distance: '+str(approx_distance_to_next_disc))
            # assign new current_z and disc value
            current_z = current_z + approx_distance_to_next_disc
            current_disc = current_disc - 1
        elif direction == 'inferior':
            try:
                approx_distance_to_next_disc = list_distance[list_disc_value_template.index(current_disc)]
            except:
                printv('WARNING: Disc value not included in template. Using previously-calculated distance: '+str(approx_distance_to_next_disc))
            # assign new current_z and disc value
            current_z = current_z - approx_distance_to_next_disc
            current_disc = current_disc + 1

        # if current_z is larger than searching zone, switch direction (and start from initial z minus approximate distance from updated template distance)
        if current_z >= nz or current_disc == 0:
            printv('.. Switching to inferior direction.', verbose)
            direction = 'inferior'
            current_disc = init_disc[1] + 1
            current_z = init_disc[0] - list_distance[list_disc_value_template.index(current_disc)]
        # if current_z is lower than searching zone, stop searching
        if current_z <= 0:
            search_next_disc = False

        # if verbose == 2:
        #     # close figures
        #     plt.figure(fig_corr), plt.close()
        #     plt.figure(fig_pattern), plt.close()

    # if upper disc is not 1, add disc above top disc based on mean_distance_adjusted
    upper_disc = min(list_disc_value)
    # if not upper_disc == 1:
    printv('Adding top disc based on adjusted template distance: #'+str(upper_disc-1), verbose)
    approx_distance_to_next_disc = list_distance[list_disc_value_template.index(upper_disc-1)]
    next_z = max(list_disc_z) + approx_distance_to_next_disc
    printv('.. approximate distance: '+str(approx_distance_to_next_disc), verbose)
    # make sure next disc does not go beyond FOV in superior direction
    if next_z > nz:
        list_disc_z.insert(0, nz)
    else:
        list_disc_z.insert(0, next_z)
    # assign disc value
    list_disc_value.insert(0, upper_disc-1)

    # Label segmentation
    label_segmentation(fname_seg, list_disc_z, list_disc_value, verbose=verbose)

    # save figure
    if verbose == 2:
        plt.figure(50), plt.savefig(path_output + 'fig_anat_straight_with_labels.png')
def vertebral_detection(fname, fname_seg, contrast):

    shift_AP = 14  # shift the centerline on the spine in mm default : 17 mm
    size_AP = 3  # mean around the centerline in the anterior-posterior direction in mm
    size_RL = 3  # mean around the centerline in the right-left direction in mm
    verbose = param.verbose

    if verbose:
        import matplotlib.pyplot as plt

    # open anatomical volume
    img = Image(fname)
    # orient to RPI
    img.change_orientation()
    # get dimension
    nx, ny, nz, nt, px, py, pz, pt = img.dim


    #==================================================
    # Compute intensity profile across vertebrae
    #==================================================

    shift_AP = shift_AP * py
    size_AP = size_AP * py
    size_RL = size_RL * px

    # orient segmentation to RPI
    run('sct_orientation -i ' + fname_seg + ' -s RPI')
    # smooth segmentation/centerline
    path_centerline, file_centerline, ext_centerline = extract_fname(fname_seg)
    x, y, z, Tx, Ty, Tz = smooth_centerline(path_centerline + file_centerline + '_RPI' + ext_centerline)

    # build intensity profile along the centerline
    I = np.zeros((len(y), 1))

    #  mask where intensity profile will be taken
    if verbose == 2:
        mat = img.copy()
        mat.data = np.zeros(mat.dim)

    for iz in range(len(z)):
        # define vector orthogonal to the cord in RL direction
        P1 = np.array([1, 0, -Tx[iz]/Tz[iz]])
        P1 = P1/np.linalg.norm(P1)
        # define vector orthogonal to the cord in AP direction
        P2 = np.array([0, 1, -Ty[iz]/Tz[iz]])
        P2 = P2/np.linalg.norm(P2)
        # define X and Y coordinates of the voxels to extract intensity profile from
        indexRL = range(-np.int(round(size_RL)), np.int(round(size_RL)))
        indexAP = range(0, np.int(round(size_AP)))+np.array(shift_AP)
        # loop over coordinates of perpendicular plane
        for i_RL in indexRL:
            for i_AP in indexAP:
                i_vect = np.round(np.array([x[iz], y[iz], z[iz]])+P1*i_RL+P2*i_AP)
                i_vect = np.minimum(np.maximum(i_vect, 0), np.array([nx, ny, nz])-1)  # check if index stays in image dimension
                I[iz] = I[iz] + img.data[i_vect[0], i_vect[1], i_vect[2]]

                # create a mask with this perpendicular plane
                if verbose == 2:
                    mat.data[i_vect[0], i_vect[1], i_vect[2]] = 1

    if verbose == 2:
        mat.file_name = 'mask'
        mat.save()

    # Detrending Intensity
    start_centerline_y = y[0]
    X = np.where(I == 0)
    mask2 = np.ones((len(y), 1), dtype=bool)
    mask2[X, 0] = False

    # low pass filtering
    import scipy.signal
    frequency = 2/pz
    Wn = 0.1/frequency
    N = 2              #Order of the filter
    #    b, a = scipy.signal.butter(N, Wn, btype='low', analog=False, output='ba')
    b, a = scipy.signal.iirfilter(N, Wn, rp=None, rs=None, btype='high', analog=False, ftype='bessel', output='ba')
    I_detrend = scipy.signal.filtfilt(b, a, I[:, 0], axis=-1, padtype='constant', padlen=None)
    I_detrend = I_detrend/(np.amax(I_detrend))


    #==================================================
    # step 1 : Find the First Peak
    #==================================================
    if contrast == 't1':
        I_detrend2 = np.diff(I_detrend)
    elif contrast == 't2':
        space = np.linspace(-10/pz, 10/pz, round(21/pz), endpoint=True)
        pattern = (np.sinc((space*pz)/20)) ** 20
        I_corr = scipy.signal.correlate(-I_detrend.squeeze().squeeze()+1,pattern,'same')
        b, a = scipy.signal.iirfilter(N, Wn, rp=None, rs=None, btype='high', analog=False, ftype='bessel', output='ba')
        I_detrend2 = scipy.signal.filtfilt(b, a, I_corr, axis=-1, padtype='constant', padlen=None)

    I_detrend2[I_detrend2 < 0.2] = 0
    ind_locs = np.squeeze(scipy.signal.argrelextrema(I_detrend2, np.greater))

    # remove peaks that are too closed
    locsdiff = np.diff(z[ind_locs])
    ind = locsdiff > 10
    ind_locs = np.hstack((ind_locs[ind], ind_locs[-1]))
    locs = z[ind_locs]

    if verbose == 2:
        # x=0: most caudal, x=max: most rostral
        plt.figure()
        plt.plot(I_detrend2)
        plt.plot(ind_locs, I_detrend2[ind_locs], '+')
        plt.show()


    #=====================================================================================
    # step 2 : Cross correlation between the adjusted template and the intensity profile.
    #          Local moving of template's peak from the first peak already found
    #=====================================================================================

    #For each loop, a peak is located at the most likely position and then local adjustment is done.
    #The position of the next peak is calculated from previous positions

    # TODO: use mean distance
    mean_distance = [12.1600, 20.8300, 18.0000, 16.0000, 15.1667, 15.3333, 15.8333,   18.1667,   18.6667,   18.6667,
    19.8333,   20.6667,   21.6667,   22.3333,   23.8333,   24.1667,   26.0000,   28.6667,   30.5000,   33.5000,
    33.0000,   31.3330]
    #
    # #Creating pattern
    printv('\nFinding Cross correlation between the adjusted template and the intensity profile...', verbose)
    space = np.linspace(-10/pz, 10/pz, round(21/pz), endpoint=True)
    pattern = (np.sinc((space*pz)/20))**20
    I_corr = scipy.signal.correlate(I_detrend2.squeeze().squeeze()+1, pattern, 'same')
    #
    # level_start=1
    # if contrast == 'T1':
    #     mean_distance = mean_distance[level_start-1:len(mean_distance)]
    #     xmax_pattern = np.argmax(pattern)
    # else:
    #     mean_distance = mean_distance[level_start+1:len(mean_distance)]
    #     xmax_pattern = np.argmin(pattern)          # position of the peak in the pattern
    # pixend = len(pattern) - xmax_pattern       #number of pixel after the peaks in the pattern
    #
    #
    # mean_distance_new = mean_distance
    # mean_ratio = np.zeros(len(mean_distance))
    #
    # L = np.round(1.2*max(mean_distance)) - np.round(0.8*min(mean_distance))
    # corr_peak  = np.zeros((L,len(mean_distance)))          # corr_peak  = np.nan #for T2
    #
    # #loop on each peak
    # for i_peak in range(len(mean_distance)):
    #     scale_min = np.round(0.80*mean_distance_new[i_peak]) - xmax_pattern - pixend
    #     if scale_min<0:
    #         scale_min = 0
    #
    #     scale_max = np.round(1.2*mean_distance_new[i_peak]) - xmax_pattern - pixend
    #     scale_peak = np.arange(scale_min,scale_max+1)
    #
    #     for i_scale in range(len(scale_peak)):
    #         template_resize_peak = np.concatenate([template_truncated,np.zeros(scale_peak[i_scale]),pattern])
    #         if len(I_detrend[:,0])>len(template_resize_peak):
    #             template_resize_peak1 = np.concatenate((template_resize_peak,np.zeros(len(I_detrend[:,0])-len(template_resize_peak))))
    #
    #         #cross correlation
    #         corr_template = scipy.signal.correlate(I_detrend[:,0],template_resize_peak)
    #
    #         if len(I_detrend[:,0])>len(template_resize_peak):
    #             val = np.dot(I_detrend[:,0],template_resize_peak1.T)
    #         else:
    #             I_detrend_2 = np.concatenate((I_detrend[:,0],np.zeros(len(template_resize_peak)-len(I_detrend[:,0]))))
    #             val = np.dot(I_detrend_2,template_resize_peak.T)
    #         corr_peak[i_scale,i_peak] = val
    #
    #         if verbose:
    #             plt.xlim(0,len(I_detrend[:,0]))
    #             plt.plot(I_detrend[:,0])
    #             plt.plot(template_resize_peak)
    #             plt.show(block=False)
    #
    #             plt.plot(corr_peak[:,i_peak],marker='+',linestyle='None',color='r')
    #             plt.title('correlation value against the displacement of the peak (px)')
    #             plt.show(block=False)
    #
    #     max_peak = np.amax(corr_peak[:,i_peak])
    #     index_scale_peak = np.where(corr_peak[:,i_peak]==max_peak)
    #     good_scale_peak = scale_peak[index_scale_peak][0]
    #     Mcorr = Mcorr1
    #     Mcorr = np.resize(Mcorr,i_peak+2)
    #     Mcorr[i_peak+1] = np.amax(corr_peak[:,0:(i_peak+1)])
    #     flag = 0
    #
    #     #If the correlation coefficient is too low, put the peak at the mean position
    #     if i_peak>0:
    #         if (Mcorr[i_peak+1]-Mcorr[i_peak])<0.4*np.mean(Mcorr[1:i_peak+2]-Mcorr[0:i_peak+1]):
    #             test = i_peak
    #             template_resize_peak = np.concatenate((template_truncated,np.zeros(round(mean_distance[i_peak])-xmax_pattern-pixend),pattern))
    #             good_scale_peak = np.round(mean_distance[i_peak]) - xmax_pattern - pixend
    #             flag = 1
    #     if i_peak==0:
    #         if (Mcorr[i_peak+1] - Mcorr[i_peak])<0.4*Mcorr[0]:
    #             template_resize_peak = np.concatenate((template_truncated,np.zeros(round(mean_distance[i_peak])-xmax_pattern-pixend),pattern))
    #             good_scale_peak = round(mean_distance[i_peak]) - xmax_pattern - pixend
    #             flag = 1
    #     if flag==0:
    #         template_resize_peak=np.concatenate((template_truncated,np.zeros(good_scale_peak),pattern))
    #
    #     #update mean-distance by a adjustement ratio
    #     mean_distance_new[i_peak] = good_scale_peak + xmax_pattern + pixend
    #     mean_ratio[i_peak] = np.mean(mean_distance_new[:,0:i_peak]/mean_distance[:,0:i_peak])
    #
    #     template_truncated = template_resize_peak
    #
    #     if verbose:
    #         plt.plot(I_detrend[:,0])
    #         plt.plot(template_truncated)
    #         plt.xlim(0,(len(I_detrend[:,0])-1))
    #         plt.show()
    #
    # #finding the maxima of the adjusted template
    # minpeakvalue = 0.5
    # loc_disk = np.arange(len(template_truncated))
    # index_disk = []
    # for i in range(len(template_truncated)):
    #     if template_truncated[i]>=minpeakvalue:
    #         if i==0:
    #             if template_truncated[i]<template_truncated[i+1]:
    #                 index_disk.append(i)
    #         elif i==(len(template_truncated)-1):
    #             if template_truncated[i]<template_truncated[i-1]:
    #                 index_disk.append(i)
    #         else:
    #             if template_truncated[i]<template_truncated[i+1]:
    #                 index_disk.append(i)
    #             elif template_truncated[i]<template_truncated[i-1]:
    #                 index_disk.append(i)
    #     else:
    #         index_disk.append(i)
    #
    # mask_disk = np.ones(len(template_truncated), dtype=bool)
    # mask_disk[index_disk] = False
    # loc_disk = loc_disk[mask_disk]
    # X1 = np.where(loc_disk > I_detrend.shape[0])
    # mask_disk1 = np.ones(len(loc_disk), dtype=bool)
    # mask_disk1[X1] = False
    # loc_disk = loc_disk[mask_disk1]
    # loc_disk = loc_disk + start_centerline_y - 1


    #=====================================================================
    # Step 3: Label segmentation
    #=====================================================================

    # # Project vertebral levels back to the centerline
    # centerline = Image(fname_seg)
    # raw_orientation = centerline.change_orientation()
    # centerline.data[:, :, :] = 0
    # for iz in range(locs[0]):
    #         centerline.data[np.round(x[iz]), np.round(y[iz]), iz] = 1
    # for i in range(len(locs)-1):
    #     for iz in range(locs[i], min(locs[i+1], len(z))):
    #         centerline.data[np.round(x[iz]), np.round(y[iz]), iz] = i+2
    # for iz in range(locs[-1], len(z)):
    #         centerline.data[np.round(x[iz]), np.round(y[iz]), iz] = i+3
    #
    # #centerline.change_orientation(raw_orientation)
    # centerline.file_name += '_labeled'
    # centerline.save()

    # Label segmentation with vertebral number
    # Method: loop across all voxels of the segmentation, project each voxel to the line passing through the vertebrae
    # (using minimum distance) and assign vertebral level.
    printv('\nLabel segmentation...', verbose)
    seg = Image(fname_seg)
    seg_raw_orientation = seg.change_orientation()
    # find all voxels belonging to segmentation
    x_seg, y_seg, z_seg = np.where(seg.data)
    # loop across voxels in segmentation
    for ivox in range(len(x_seg)):
        # get voxel coordinate
        vox_coord = np.array([x_seg[ivox], y_seg[ivox], z_seg[ivox]])
        # find closest point to the curved line passing through the vertebrae

        for iplane in range(len(locs)):
            ind = np.where(z == locs[iplane])
            vox_vector = vox_coord - np.hstack((x[ind], y[ind], z[ind]))
            normal2plane_vector = np.hstack((Tx[ind], Ty[ind], Tz[ind]))  # Tx, Ty and Tz are the derivatives of the centerline

            # if voxel is above the plane --> give the number of the plane
            if np.dot(vox_vector, normal2plane_vector) > 0:
                seg.data[vox_coord[0], vox_coord[1], vox_coord[2]] = iplane+2
            else:  # if the voxel gets below the plane --> next voxel
                break
    seg.change_orientation(seg_raw_orientation)
    seg.file_name += '_labeled'
    seg.save()

    # # color the segmentation with vertebral number
    # printv('\nLabel input segmentation...', verbose)
    # # if fname_segmentation:
    # seg = Image(fname_seg)
    # seg_raw_orientation = seg.change_orientation()
    # x_seg, y_seg, z_seg = np.where(seg.data)  # find all voxels belonging to segmentation
    # for ivox in range(len(x_seg)):  # loop across voxels in segmentation
    #     vox_coord = np.array([x_seg[ivox], y_seg[ivox], z_seg[ivox]])  # get voxel coordinate
    #     for iplane in range(len(locs)):
    #         ind = np.where(z == locs[iplane])
    #         vox_vector = vox_coord - np.hstack((x[ind], y[ind], z[ind]))
    #         normal2plane_vector = np.hstack((Tx[ind], Ty[ind], Tz[ind]))  # Tx, Ty and Tz are the derivatives of the centerline
    #
    #         # if voxel is above the plane --> give the number of the plane
    #         if np.dot(vox_vector, normal2plane_vector) > 0:
    #             seg.data[vox_coord[0], vox_coord[1], vox_coord[2]] = iplane+2
    #         else:  # if the voxel gets below the plane --> next voxel
    #             break
    # seg.change_orientation(seg_raw_orientation)
    # seg.file_name += '_labeled'
    # seg.save()

    return locs
Ejemplo n.º 18
0
def generate_initial_template_space(dataset_info, points_average_centerline, position_template_disks):
    """
    This function generates the initial template space, on which all images will be registered.
    :param points_average_centerline: list of points (x, y, z) of the average spinal cord and brainstem centerline
    :param position_template_disks: index of intervertebral disks along the template centerline
    :return:
    """

    # initializing variables
    path_template = dataset_info['path_template']
    x_size_of_template_space, y_size_of_template_space = 201, 201
    spacing = 0.5

    # creating template space
    size_template_z = int(abs(points_average_centerline[0][2] - points_average_centerline[-1][2]) / spacing) + 15
    template_space = Image([x_size_of_template_space, y_size_of_template_space, size_template_z])
    template_space.data = np.zeros((x_size_of_template_space, y_size_of_template_space, size_template_z))
    template_space.hdr.set_data_dtype('float32')
    origin = [points_average_centerline[-1][0] + x_size_of_template_space * spacing / 2.0,
              points_average_centerline[-1][1] - y_size_of_template_space * spacing / 2.0,
              (points_average_centerline[-1][2] - spacing)]
    template_space.hdr.as_analyze_map()['dim'] = [3.0, x_size_of_template_space, y_size_of_template_space, size_template_z, 1.0, 1.0, 1.0, 1.0]
    template_space.hdr.as_analyze_map()['qoffset_x'] = origin[0]
    template_space.hdr.as_analyze_map()['qoffset_y'] = origin[1]
    template_space.hdr.as_analyze_map()['qoffset_z'] = origin[2]
    template_space.hdr.as_analyze_map()['srow_x'][-1] = origin[0]
    template_space.hdr.as_analyze_map()['srow_y'][-1] = origin[1]
    template_space.hdr.as_analyze_map()['srow_z'][-1] = origin[2]
    template_space.hdr.as_analyze_map()['srow_x'][0] = -spacing
    template_space.hdr.as_analyze_map()['srow_y'][1] = spacing
    template_space.hdr.as_analyze_map()['srow_z'][2] = spacing
    template_space.hdr.set_sform(template_space.hdr.get_sform())
    template_space.hdr.set_qform(template_space.hdr.get_sform())
    template_space.setFileName(path_template + 'template_space.nii.gz')
    template_space.save(type='uint8')

    # generate template centerline as an image
    image_centerline = template_space.copy()
    for coord in points_average_centerline:
        coord_pix = image_centerline.transfo_phys2pix([coord])[0]
        if 0 <= coord_pix[0] < image_centerline.data.shape[0] and 0 <= coord_pix[1] < image_centerline.data.shape[1] and 0 <= coord_pix[2] < image_centerline.data.shape[2]:
            image_centerline.data[int(coord_pix[0]), int(coord_pix[1]), int(coord_pix[2])] = 1
    image_centerline.setFileName(path_template + 'template_centerline.nii.gz')
    image_centerline.save(type='float32')

    # generate template disks position
    coord_physical = []
    image_disks = template_space.copy()
    for disk in position_template_disks:
        label = labels_regions[disk]
        coord = position_template_disks[disk]
        coord_pix = image_disks.transfo_phys2pix([coord])[0]

        coord = coord.tolist()
        coord.append(label)
        coord_physical.append(coord)
        if 0 <= coord_pix[0] < image_disks.data.shape[0] and 0 <= coord_pix[1] < image_disks.data.shape[1] and 0 <= coord_pix[2] < image_disks.data.shape[2]:
            image_disks.data[int(coord_pix[0]), int(coord_pix[1]), int(coord_pix[2])] = label
        else:
            sct.printv(str(coord_pix))
            sct.printv('ERROR: the disk label ' + str(disk) + ' is not in the template image.')
    image_disks.setFileName(path_template + 'template_disks.nii.gz')
    image_disks.save(type='uint8')

    # generate template centerline as a npz file
    x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
        path_template + 'template_centerline.nii.gz', algo_fitting='nurbs', verbose=0, nurbs_pts_number=4000,
        all_slices=False, phys_coordinates=True, remove_outliers=True)
    centerline_template = Centerline(x_centerline_fit, y_centerline_fit, z_centerline,
                                     x_centerline_deriv, y_centerline_deriv, z_centerline_deriv)
    centerline_template.compute_vertebral_distribution(coord_physical)
    centerline_template.save_centerline(fname_output=path_template + 'template_centerline')
Ejemplo n.º 19
0
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):
        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

    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 == 'cross':
            self.output_image = self.cross()
        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':
            self.output_image = self.remove_label()
        if type_process == 'remove-symm':
            self.output_image = self.remove_label(symmetry=True)
        if type_process == 'centerline':
            self.extract_centerline()
        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 == '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-disc':
        #     self.output_image = self.label_disc(self.vertebral_levels)
        # if type_process == 'label-vertebrae-from-disks':
        #     self.output_image = self.label_vertebrae_from_disks(self.vertebral_levels)
        if type_process == 'vert-continuous':
            self.output_image = self.continuous_vertebral_levels()

        # save the output image as minimized integers
        if self.fname_output is not None:
            self.output_image.setFileName(self.fname_output)
            if change_orientation:
                self.output_image.change_orientation(input_orientation)
            if type_process == 'vert-continuous':
                self.output_image.save('float32')
            elif type_process != 'plan_ref':
                self.output_image.save('minimize_int')
            else:
                self.output_image.save()

    def add(self, value):
        """
        This function add a specified value to all non-zero voxels.
        """
        image_output = Image(self.image_input, self.verbose)
        # 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 not add:
            image_output.data *= 0

        # loop across labels
        for i, coord in enumerate(self.coordinates):
            # display info
            sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' +
                       str(coord.value), 1)
            image_output.data[int(coord.x), int(coord.y), int(coord.z)] = coord.value

        return image_output


    def cross(self):
        """
        create a cross.
        :return:
        """
        output_image = Image(self.image_input, self.verbose)
        nx, ny, nz, nt, px, py, pz, pt = Image(self.image_input.absolutepath).dim

        coordinates_input = self.image_input.getNonZeroCoordinates()
        d = self.cross_radius  # cross radius in pixel
        dx = d / px  # cross radius in mm
        dy = d / py

        # clean output_image
        output_image.data *= 0

        cross_coordinates = self.get_crosses_coordinates(coordinates_input, dx, self.image_ref, self.dilate)

        for coord in cross_coordinates:
            output_image.data[int(round(coord.x)), int(round(coord.y)), int(round(coord.z))] = coord.value

        return output_image


    @staticmethod
    def get_crosses_coordinates(coordinates_input, gapxy=15, image_ref=None, dilate=False):
        from msct_types import Coordinate

        # if reference image is provided (segmentation), we draw the cross perpendicular to the centerline
        if image_ref is not None:
            # smooth centerline
            from sct_straighten_spinalcord import smooth_centerline
            x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(self.image_ref, verbose=self.verbose)

        # compute crosses
        cross_coordinates = []
        for coord in coordinates_input:
            if image_ref is None:
                from sct_straighten_spinalcord import compute_cross
                cross_coordinates_temp = compute_cross(coord, gapxy)
            else:
                from sct_straighten_spinalcord import compute_cross_centerline
                from numpy import where
                index_z = where(z_centerline == coord.z)
                deriv = Coordinate([x_centerline_deriv[index_z][0], y_centerline_deriv[index_z][0], z_centerline_deriv[index_z][0], 0.0])
                cross_coordinates_temp = compute_cross_centerline(coord, deriv, gapxy)

            for i, coord_cross in enumerate(cross_coordinates_temp):
                coord_cross.value = coord.value * 10 + i + 1

            # dilate cross to 3x3x3
            if dilate:
                additional_coordinates = []
                for coord_temp in cross_coordinates_temp:
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y+1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y+1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y+1.0, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y-1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y-1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y-1.0, coord_temp.z-1.0, coord_temp.value]))

                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y+1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y+1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y+1.0, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y-1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y-1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y-1.0, coord_temp.z-1.0, coord_temp.value]))

                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y+1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y+1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y+1.0, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y-1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y-1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y-1.0, coord_temp.z-1.0, coord_temp.value]))

                cross_coordinates_temp.extend(additional_coordinates)

            cross_coordinates.extend(cross_coordinates_temp)

        cross_coordinates = sorted(cross_coordinates, key=lambda obj: obj.value)
        return cross_coordinates


    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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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 = Image(self.image_ref, self.verbose)
        image_output.data *= 0

        image_input_neg = Image(self.image_input, self.verbose).copy()
        image_input_pos = Image(self.image_input, self.verbose).copy()
        image_input_neg.data *=0
        image_input_pos.data *=0
        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.changeType('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 = self.image_input.copy()
        output_image.data *= 0

        # 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.iteritems():
            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(round(center_of_mass.x)) + ", " + str(round(center_of_mass.y)) + ", " + str(round(center_of_mass.z)) + ")", verbose=self.verbose)
            output_image.data[int(round(center_of_mass.x)), int(round(center_of_mass.y)), int(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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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


    # FUNCTION BELOW REMOVED BY JULIEN ON 2016-07-04 BECAUSE SEEMS NOT TO BE USED (AND DUPLICATION WITH ABOVE)
    # def label_vertebrae_from_disks(self, levels_user):
    #     """
    #     Find the center of mass of vertebral levels specified by the user.
    #     :param levels_user:
    #     :return:
    #     """
    #     image_cubic2point = self.cubic_to_point()
    #     # get list of coordinates for each label
    #     list_coordinates_disks = image_cubic2point.getNonZeroCoordinates(sorting='value')
    #     image_cubic2point.data *= 0
    #     # compute vertebral labels from disk labels
    #     list_coordinates_vertebrae = []
    #     for i_label in range(len(list_coordinates_disks)-1):
    #         list_coordinates_vertebrae.append((list_coordinates_disks[i_label] + list_coordinates_disks[i_label+1]) / 2.0)
    #     # loop across labels and remove those that are not listed by the user
    #     for i_label in range(len(list_coordinates_vertebrae)):
    #         # check if this level is NOT in levels_user
    #         if levels_user.count(int(list_coordinates_vertebrae[i_label].value)):
    #             image_cubic2point.data[int(list_coordinates_vertebrae[i_label].x), int(list_coordinates_vertebrae[i_label].y), int(list_coordinates_vertebrae[i_label].z)] = list_coordinates_vertebrae[i_label].value
    #
    #     return image_cubic2point


    # BELOW: UNFINISHED BUSINESS (JULIEN)
    # def label_disc(self, levels_user=None):
    #     """
    #     Find the edge of vertebral labeling file and assign value corresponding to middle coordinate between two levels.
    #     Assumes RPI orientation.
    #     :return: image_output: Image with labels.
    #     """
    #     from msct_types import Coordinate
    #     # get dim
    #     nx, ny, nz, nt, px, py, pz, pt = self.image_input.dim
    #     # initialize disc as a coordinate variable
    #     disc = []
    #     # get center of mass of each vertebral level
    #     image_cubic2point = self.cubic_to_point()
    #     # get list of coordinates for each label
    #     list_centermass = 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_centermass]
    #     # get list of all coordinates
    #     list_coordinates = self.display_voxel()
    #     # loop across labels and remove those that are not listed by the user
    #     # for i_label in range(len(list_centermass)):
    #
    #     # TOP DISC
    #     # get coordinates for value i_level
    #     list_i_level = [list_coordinates[i] for i in xrange(len(list_coordinates)) if int(list_coordinates[i].value) == levels_user[0]]
    #     # get max z-value
    #     zmax = max([list_i_level[i].z for i in xrange(len(list_i_level))])
    #     # get coordinates corresponding to bottom voxels
    #     list_i_level_top = [list_i_level[i] for i in xrange(len(list_i_level)) if list_i_level[i].z == zmax]
    #     # get center of mass of the top and bottom voxels
    #     arr_voxels_around_disc = np.array([[list_i_level_top[i].x, list_i_level_top[i].y, list_i_level_top[i].z] for i in range(len(list_i_level_top))])
    #     centermass = list(np.mean(arr_voxels_around_disc, 0))
    #     centermass.append(levels_user[0]-1)
    #     disc.append(Coordinate(centermass))
    #     # if minimum level corresponds to z=nz, then remove it (likely corresponds to top edge of the FOV)
    #     if disc[0].z == nz:
    #         sct.printv('WARNING: Maximum level corresponds to z=0. Removing it (likely corresponds to edge of the FOV)', 1, 'warning')
    #         # remove last element of the list
    #         disc.pop()
    #
    #     # ALL DISCS
    #     # loop across values
    #     for i_level in levels_user:
    #         # get coordinates for value i_level
    #         list_i_level = [list_coordinates[i] for i in xrange(len(list_coordinates)) if int(list_coordinates[i].value) == i_level]
    #         # get min z-value
    #         zmin = min([list_i_level[i].z for i in xrange(len(list_i_level))])
    #         # get coordinates corresponding to bottom voxels
    #         list_i_level_bottom = [list_i_level[i] for i in xrange(len(list_i_level)) if list_i_level[i].z == zmin]
    #         # get center of mass
    #         # arr_i_level_bottom = np.array([[list_i_level_bottom[i].x, list_i_level_bottom[i].y] for i in range(len(list_i_level_bottom))])
    #         # centermass_i_level = ndimage.measurements.center_of_mass()
    #         try:
    #             # get coordinates for value i_level+1
    #             list_i_level_plus_one = [list_coordinates[i] for i in xrange(len(list_coordinates)) if int(list_coordinates[i].value) == i_level+1]
    #             # get max z-value
    #             zmax = max([list_i_level_plus_one[i].z for i in xrange(len(list_i_level_plus_one))])
    #             # get coordinates corresponding to top voxels
    #             list_i_level_plus_one_top = [list_i_level_plus_one[i] for i in xrange(len(list_i_level_plus_one)) if list_i_level_plus_one[i].z == zmax]
    #         except:
    #             # if maximum level was reached, ignore it and disc will be located at the centermass of the bottom z.
    #             list_i_level_plus_one_top = []
    #         # stack bottom and top voxels
    #         list_voxels_around_disc = list_i_level_bottom + list_i_level_plus_one_top
    #         # get center of mass of the top and bottom voxels
    #         arr_voxels_around_disc = np.array([[list_voxels_around_disc[i].x, list_voxels_around_disc[i].y, list_voxels_around_disc[i].z] for i in range(len(list_voxels_around_disc))])
    #         centermass = list(np.mean(arr_voxels_around_disc, 0))
    #         centermass.append(i_level)
    #         disc.append(Coordinate(centermass))
    #     # if maximum level corresponds to z=0, then remove it (likely corresponds to edge of the FOV)
    #     if disc[-1].z == 0.0:
    #         sct.printv('WARNING: Maximum level corresponds to z=0. Removing it (likely corresponds to edge of the FOV)', 1, 'warning')
    #         # remove last element of the list
    #         disc.pop()
    #
    #     # loop across labels and assign voxels in image
    #     image_cubic2point.data[:, :, :] = 0
    #     for i_label in range(len(disc)):
    #         image_cubic2point.data[int(round(disc[i_label].x)),
    #                                int(round(disc[i_label].y)),
    #                                int(round(disc[i_label].z))] = disc[i_label].value
    #
    #     # return image of 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 round(coord.value) not in [round(coord_ref.value) for coord_ref in coordinates_ref]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if round(coord_ref.value) not in [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 round(coord_ref.value) == round(coord.value):
                    result += (coord_ref.z - coord.z) ** 2
                    break
        result = math.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            f = open(self.image_input.path + 'error_log_' + self.image_input.file_name + '.txt', 'w')
            f.write(
                'The labels error (MSE) between ' + self.image_input.file_name + ' and ' + self.image_ref.file_name + ' is: ' + str(
                    result))
            f.close()

        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 filter(lambda x: x.value == coord.value, coord_ref)]
            if symmetry:
                result_coord_ref = [coord for coord in coord_ref if 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 = Image(self.image_input, verbose=self.verbose)
        image_output.data *= 0  # put all voxels to 0

        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(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(round(coord.value))
            image_output_ref.setFileName(self.fname_output[1])
            image_output_ref.save('minimize_int')

            self.fname_output = self.fname_output[0]

        return image_output


    def extract_centerline(self):
        """
        Write a text file with the coordinates of the centerline.
        The image is suppose to be RPI
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z')

        fo = open(self.fname_output, "wb")
        for coord in coordinates_input:
            line = (coord.x,coord.y, coord.z)
            fo.write("%i %i %i\n" % line)
        fo.close()


    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='z')
        useful_notation = ''
        for coord in coordinates_input:
            print 'Position=(' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ') -- Value= ' + str(coord.value)
            if useful_notation:
                useful_notation = useful_notation + ':'
            useful_notation = useful_notation + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ',' + str(coord.value)
        print 'All labels (useful syntax):'
        print useful_notation
        return coordinates_input


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

        print "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:
                print coord.value

        print "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:
                print 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 = math.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:
                print '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 = Image(self.image_input, self.verbose)
        im_output.data *= 0

        # 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 sct_straighten_spinalcord import smooth_centerline
        x_centerline_fit, y_centerline_fit, z_centerline_fit, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(self.image_input, algo_fitting='nurbs', verbose=0)
        value_centerline = np.array([im_input.data[int(x_centerline_fit[it]), int(y_centerline_fit[it]), int(z_centerline_fit[it])] for it in range(len(z_centerline_fit))])

        # 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([math.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_fit[indexes_slice[0][index_slice + 1]] - z_centerline_fit[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_fit):
            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([math.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_fit[indexes_slice[index_slice + 1]] - z_centerline_fit[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.changeType('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 vertebral_detection(fname,
                        fname_seg,
                        contrast,
                        param,
                        init_disc,
                        verbose=1,
                        path_template='',
                        initc2='auto',
                        path_output='../'):
    """
    Find intervertebral discs in straightened image using template matching
    :param fname:
    :param fname_seg:
    :param contrast:
    :param param:  advanced parameters
    :param init_disc:
    :param verbose:
    :param path_template:
    :param path_output: output path for verbose=2 pictures
    :return:
    """
    sct.printv('\nLook for template...', verbose)
    # if path_template == '':
    #     # get path of SCT
    #     from os import path
    #     path_script = path.dirname(__file__)
    #     path_sct = slash_at_the_end(path.dirname(path_script), 1)
    #     folder_template = 'data/template/'
    #     path_template = path_sct+folder_template
    sct.printv('Path template: ' + path_template, verbose)

    # adjust file names if MNI-Poly-AMU template is used
    fname_level = get_file_label(path_template + 'template/',
                                 'vertebral',
                                 output='filewithpath')
    fname_template = get_file_label(path_template + 'template/',
                                    contrast.upper() + '-weighted',
                                    output='filewithpath')

    # if not len(glob(path_template+'MNI-Poly-AMU*.*')) == 0:
    #     contrast = contrast.upper()
    #     file_level = '*_level.nii.gz'
    # else:
    #     file_level = '*_levels.nii.gz'
    #
    # # retrieve file_template based on contrast
    # try:
    #     fname_template_list = glob(path_template + '*' + contrast + '.nii.gz')
    #     fname_template = fname_template_list[0]
    # except IndexError:
    #     sct.printv('\nERROR: No template found. Please check the provided path.', 1, 'error')
    # retrieve disc level from template
    # try:
    #     fname_level_list = glob(path_template+file_level)
    #     fname_level = fname_level_list[0]
    # except IndexError:
    #     sct.printv('\nERROR: File *_levels.nii.gz not found.', 1, 'error')

    # Open template and vertebral levels
    sct.printv('\nOpen template and vertebral levels...', verbose)
    data_template = Image(fname_template).data
    data_disc_template = Image(fname_level).data

    # open anatomical volume
    im_input = Image(fname)
    data = im_input.data

    # smooth data
    from scipy.ndimage.filters import gaussian_filter
    data = gaussian_filter(data,
                           param.smooth_factor,
                           output=None,
                           mode="reflect")

    # get dimension of src
    nx, ny, nz = data.shape
    # define xc and yc (centered in the field of view)
    xc = int(round(nx / 2))  # direction RL
    yc = int(round(ny / 2))  # direction AP
    # get dimension of template
    nxt, nyt, nzt = data_template.shape
    # define xc and yc (centered in the field of view)
    xct = int(round(nxt / 2))  # direction RL
    yct = int(round(nyt / 2))  # direction AP

    # define mean distance (in voxel) between adjacent discs: [C1/C2 -> C2/C3], [C2/C3 -> C4/C5], ..., [L1/L2 -> L2/L3]
    centerline_level = data_disc_template[xct, yct, :]
    # attribute value to each disc. Starts from max level, then decrease.
    min_level = centerline_level[centerline_level.nonzero()].min()
    max_level = centerline_level[centerline_level.nonzero()].max()
    list_disc_value_template = range(min_level, max_level)
    # add disc above top one
    list_disc_value_template.insert(int(0), min_level - 1)
    sct.printv('\nDisc values from template: ' + str(list_disc_value_template),
               verbose)
    # get diff to find transitions (i.e., discs)
    diff_centerline_level = np.diff(centerline_level)
    # get disc z-values
    list_disc_z_template = diff_centerline_level.nonzero()[0].tolist()
    list_disc_z_template.reverse()
    sct.printv('Z-values for each disc: ' + str(list_disc_z_template), verbose)
    list_distance_template = (
        np.diff(list_disc_z_template) *
        (-1)).tolist()  # multiplies by -1 to get positive distances
    sct.printv(
        'Distances between discs (in voxel): ' + str(list_distance_template),
        verbose)

    # if automatic mode, find C2/C3 disc
    if init_disc == [] and initc2 == 'auto':
        sct.printv('\nDetect C2/C3 disk...', verbose)
        zrange = range(0, nz)
        ind_c2 = list_disc_value_template.index(2)
        z_peak = compute_corr_3d(data,
                                 data_template,
                                 x=xc,
                                 xshift=0,
                                 xsize=param.size_RL_initc2,
                                 y=yc,
                                 yshift=param.shift_AP_initc2,
                                 ysize=param.size_AP_initc2,
                                 z=0,
                                 zshift=param.shift_IS_initc2,
                                 zsize=param.size_IS_initc2,
                                 xtarget=xct,
                                 ytarget=yct,
                                 ztarget=list_disc_z_template[ind_c2],
                                 zrange=zrange,
                                 verbose=verbose,
                                 save_suffix='_initC2',
                                 gaussian_std=param.gaussian_std,
                                 path_output=path_output)
        init_disc = [z_peak, 2]

    # if manual mode, open viewer for user to click on C2/C3 disc
    if init_disc == [] and initc2 == 'manual':
        from spinalcordtoolbox.gui.base import AnatomicalParams
        from spinalcordtoolbox.gui.sagittal import launch_sagittal_dialog

        params = AnatomicalParams()
        params.num_points = 1
        params.vertebraes = [
            3,
        ]
        input_file = Image(fname)
        output_file = input_file.copy()
        output_file.data *= 0
        output_file.setFileName(os.path.join(path_output, 'labels.nii.gz'))
        controller = launch_sagittal_dialog(input_file, output_file, params)
        mask_points = controller.as_string()

        # assign new init_disc_z value, which corresponds to the first vector of mask_points. Note, we need to substract from nz due to SAL orientation: in the viewer, orientation is S-I while in this code, it is I-S.
        init_disc = [nz - int(mask_points.split(',')[0]), 2]

    # display init disc
    if verbose == 2:
        import matplotlib
        matplotlib.use('Agg')
        import matplotlib.pyplot as plt
        # get percentile for automatic contrast adjustment
        data_display = np.mean(data[xc - param.size_RL:xc +
                                    param.size_RL, :, :],
                               axis=0).transpose()
        percmin = np.percentile(data_display, 10)
        percmax = np.percentile(data_display, 90)
        # display image
        plt.matshow(data_display,
                    fignum=50,
                    cmap=plt.cm.gray,
                    clim=[percmin, percmax],
                    origin='lower')
        plt.title('Anatomical image')
        plt.autoscale(
            enable=False)  # to prevent autoscale of axis when displaying plot
        plt.figure(50), plt.scatter(yc + param.shift_AP_visu,
                                    init_disc[0],
                                    c='yellow',
                                    s=50)
        plt.text(yc + param.shift_AP_visu + 4,
                 init_disc[0],
                 str(init_disc[1]) + '/' + str(init_disc[1] + 1),
                 verticalalignment='center',
                 horizontalalignment='left',
                 color='pink',
                 fontsize=15), plt.draw()
        # plt.ion()  # enables interactive mode

    # FIND DISCS
    # ===========================================================================
    sct.printv('\nDetect intervertebral discs...', verbose)
    # assign initial z and disc
    current_z = init_disc[0]
    current_disc = init_disc[1]
    # mean_distance = mean_distance * pz
    # mean_distance_real = np.zeros(len(mean_distance))
    # create list for z and disc
    list_disc_z = []
    list_disc_value = []
    zrange = range(-10, 10)
    direction = 'superior'
    search_next_disc = True
    while search_next_disc:
        sct.printv(
            'Current disc: ' + str(current_disc) + ' (z=' + str(current_z) +
            '). Direction: ' + direction, verbose)
        try:
            # get z corresponding to current disc on template
            current_z_template = list_disc_z_template[current_disc]
        except:
            # in case reached the bottom (see issue #849)
            sct.printv(
                'WARNING: Reached the bottom of the template. Stop searching.',
                verbose, 'warning')
            break
        # find next disc
        # N.B. Do not search for C1/C2 disc (because poorly visible), use template distance instead
        if current_disc != 1:
            current_z = compute_corr_3d(data,
                                        data_template,
                                        x=xc,
                                        xshift=0,
                                        xsize=param.size_RL,
                                        y=yc,
                                        yshift=param.shift_AP,
                                        ysize=param.size_AP,
                                        z=current_z,
                                        zshift=0,
                                        zsize=param.size_IS,
                                        xtarget=xct,
                                        ytarget=yct,
                                        ztarget=current_z_template,
                                        zrange=zrange,
                                        verbose=verbose,
                                        save_suffix='_disc' +
                                        str(current_disc),
                                        gaussian_std=999,
                                        path_output=path_output)

        # display new disc
        if verbose == 2:
            plt.figure(50), plt.scatter(yc + param.shift_AP_visu,
                                        current_z,
                                        c='yellow',
                                        s=50)
            plt.text(yc + param.shift_AP_visu + 4,
                     current_z,
                     str(current_disc) + '/' + str(current_disc + 1),
                     verticalalignment='center',
                     horizontalalignment='left',
                     color='yellow',
                     fontsize=15), plt.draw()

        # append to main list
        if direction == 'superior':
            # append at the beginning
            list_disc_z.insert(0, current_z)
            list_disc_value.insert(0, current_disc)
        elif direction == 'inferior':
            # append at the end
            list_disc_z.append(current_z)
            list_disc_value.append(current_disc)

        # adjust correcting factor based on already-identified discs
        if len(list_disc_z) > 1:
            # compute distance between already-identified discs
            list_distance_current = (np.diff(list_disc_z) * (-1)).tolist()
            # retrieve the template distance corresponding to the already-identified discs
            index_disc_identified = [
                i for i, j in enumerate(list_disc_value_template)
                if j in list_disc_value[:-1]
            ]
            list_distance_template_identified = [
                list_distance_template[i] for i in index_disc_identified
            ]
            # divide subject and template distances for the identified discs
            list_subject_to_template_distance = [
                float(list_distance_current[i]) /
                list_distance_template_identified[i]
                for i in range(len(list_distance_current))
            ]
            # average across identified discs to obtain an average correcting factor
            correcting_factor = np.mean(list_subject_to_template_distance)
            sct.printv('.. correcting factor: ' + str(correcting_factor),
                       verbose)
        else:
            correcting_factor = 1
        # update list_distance specific for the subject
        list_distance = [
            int(round(list_distance_template[i] * correcting_factor))
            for i in range(len(list_distance_template))
        ]
        # updated average_disc_distance (in case it is needed)
        # average_disc_distance = int(round(np.mean(list_distance)))

        # assign new current_z and disc value
        if direction == 'superior':
            try:
                approx_distance_to_next_disc = list_distance[
                    list_disc_value_template.index(current_disc - 1)]
            except ValueError:
                sct.printv(
                    'WARNING: Disc value not included in template. Using previously-calculated distance: '
                    + str(approx_distance_to_next_disc))
            # assign new current_z and disc value
            current_z = current_z + approx_distance_to_next_disc
            current_disc = current_disc - 1
        elif direction == 'inferior':
            try:
                approx_distance_to_next_disc = list_distance[
                    list_disc_value_template.index(current_disc)]
            except:
                sct.printv(
                    'WARNING: Disc value not included in template. Using previously-calculated distance: '
                    + str(approx_distance_to_next_disc))
            # assign new current_z and disc value
            current_z = current_z - approx_distance_to_next_disc
            current_disc = current_disc + 1

        # if current_z is larger than searching zone, switch direction (and start from initial z minus approximate distance from updated template distance)
        if current_z >= nz or current_disc == 0:
            sct.printv('.. Switching to inferior direction.', verbose)
            direction = 'inferior'
            current_disc = init_disc[1] + 1
            current_z = init_disc[0] - list_distance[
                list_disc_value_template.index(current_disc)]
        # if current_z is lower than searching zone, stop searching
        if current_z <= 0:
            search_next_disc = False

        # if verbose == 2:
        #     # close figures
        #     plt.figure(fig_corr), plt.close()
        #     plt.figure(fig_pattern), plt.close()

    # if upper disc is not 1, add disc above top disc based on mean_distance_adjusted
    upper_disc = min(list_disc_value)
    # if not upper_disc == 1:
    sct.printv(
        'Adding top disc based on adjusted template distance: #' +
        str(upper_disc - 1), verbose)
    approx_distance_to_next_disc = list_distance[
        list_disc_value_template.index(upper_disc - 1)]
    next_z = max(list_disc_z) + approx_distance_to_next_disc
    sct.printv('.. approximate distance: ' + str(approx_distance_to_next_disc),
               verbose)
    # make sure next disc does not go beyond FOV in superior direction
    if next_z > nz:
        list_disc_z.insert(0, nz)
    else:
        list_disc_z.insert(0, next_z)
    # assign disc value
    list_disc_value.insert(0, upper_disc - 1)

    # Label segmentation
    label_segmentation(fname_seg,
                       list_disc_z,
                       list_disc_value,
                       verbose=verbose)

    # save figure
    if verbose == 2:
        plt.figure(50), plt.savefig(path_output +
                                    'fig_anat_straight_with_labels.png')
Ejemplo n.º 21
0
def heatmap(filename_in,
            filename_out,
            model,
            patch_shape,
            mean_train,
            std_train,
            brain_bool=True):
    """Compute the heatmap with CNN_1 representing the SC localization."""
    im = Image(filename_in)
    data_im = im.data.astype(np.float32)
    im_out = im.copy()
    im_out.changeType('uint8')
    del im
    data = np.zeros(im_out.data.shape)

    x_shape, y_shape = data_im.shape[:2]
    x_shape_block, y_shape_block = np.ceil(
        x_shape * 1.0 / patch_shape[0]).astype(np.int), np.int(y_shape * 1.0 /
                                                               patch_shape[1])
    x_pad = int(x_shape_block * patch_shape[0] - x_shape)
    if y_shape > patch_shape[1]:
        y_crop = y_shape - y_shape_block * patch_shape[1]
        # slightly crop the input data in the P-A direction so that data_im.shape[1] % patch_shape[1] == 0
        data_im = data_im[:, :y_shape - y_crop, :]
        # coordinates of the blocks to scan during the detection, in the cross-sectional plane
        coord_lst = [[
            x_dim * patch_shape[0], y_dim * patch_shape[1],
            (x_dim + 1) * patch_shape[0], (y_dim + 1) * patch_shape[1]
        ] for y_dim in range(y_shape_block) for x_dim in range(x_shape_block)]
    else:
        data_im = np.pad(data_im,
                         ((0, 0), (0, patch_shape[1] - y_shape), (0, 0)),
                         'constant')
        coord_lst = [[
            x_dim * patch_shape[0], 0, (x_dim + 1) * patch_shape[0],
            patch_shape[1]
        ] for x_dim in range(x_shape_block)]
    # pad the input data in the R-L direction
    data_im = np.pad(data_im, ((0, x_pad), (0, 0), (0, 0)), 'constant')
    # scale intensities between 0 and 255
    data_im = scale_intensity(data_im)

    x_CoM, y_CoM = None, None
    z_sc_notDetected_cmpt = 0
    for zz in range(data_im.shape[2]):
        # if SC was detected at zz-1, we will start doing the detection on the block centered around the previously conputed center of mass (CoM)
        if x_CoM is not None:
            z_sc_notDetected_cmpt = 0  # SC detected, cmpt set to zero
            x_0, x_1 = _find_crop_start_end(x_CoM, patch_shape[0],
                                            data_im.shape[0])
            y_0, y_1 = _find_crop_start_end(y_CoM, patch_shape[1],
                                            data_im.shape[1])
            block = data_im[x_0:x_1, y_0:y_1, zz]
            block_nn = np.expand_dims(np.expand_dims(block, 0), -1)
            block_nn_norm = _normalize_data(block_nn, mean_train, std_train)
            block_pred = model.predict(block_nn_norm)

            # coordinates manipulation due to the above padding and cropping
            if x_1 > data.shape[0]:
                x_end = data.shape[0]
                x_1 = data.shape[0]
                x_0 = data.shape[0] - patch_shape[0] if data.shape[
                    0] > patch_shape[0] else 0
            else:
                x_end = patch_shape[0]
            if y_1 > data.shape[1]:
                y_end = data.shape[1]
                y_1 = data.shape[1]
                y_0 = data.shape[1] - patch_shape[1] if data.shape[
                    1] > patch_shape[1] else 0
            else:
                y_end = patch_shape[1]

            data[x_0:x_1, y_0:y_1, zz] = block_pred[0, :x_end, :y_end, 0]

            # computation of the new center of mass
            if np.max(data[:, :, zz]) > 0.5:
                z_slice_out_bin = data[:, :,
                                       zz] > 0.5  # if the SC was detection
                x_CoM, y_CoM = center_of_mass(z_slice_out_bin)
                x_CoM, y_CoM = int(x_CoM), int(y_CoM)
            else:
                x_CoM, y_CoM = None, None

        # if the SC was not detected at zz-1 or on the patch centered around CoM in slice zz, the entire cross-sectional slice is scaned
        if x_CoM is None:
            z_slice, x_CoM, y_CoM, coord_lst = scan_slice(
                data_im[:, :, zz], model, mean_train, std_train, coord_lst,
                patch_shape, data.shape[:2])
            data[:, :, zz] = z_slice

            z_sc_notDetected_cmpt += 1
            # if the SC has not been detected on 10 consecutive z_slices, we stop the SC investigation
            if z_sc_notDetected_cmpt > 10 and brain_bool:
                sct.printv('Brain section detected.')
                break

        # distance transform to deal with the harsh edges of the prediction boundaries (Dice)
        data[:, :, zz][np.where(data[:, :, zz] < 0.5)] = 0
        data[:, :, zz] = distance_transform_edt(data[:, :, zz])

    im_out.data = data
    im_out.setFileName(filename_out)
    im_out.save()
    del im_out

    # z_max is used to reject brain sections
    z_max = np.max(list(set(np.where(data)[2])))
    if z_max == data.shape[2] - 1:
        return None
    else:
        return z_max
Ejemplo n.º 22
0
def detect_centerline(image_fname,
                      contrast_type,
                      optic_models_path,
                      folder_output,
                      remove_temp_files=False,
                      init_option=None,
                      output_roi=False,
                      verbose=0):
    """This method will use the OptiC to detect the centerline.

    :param image_fname: The input image filename.
    :param init_option: Axial slice where the propagation starts.
    :param contrast_type: The contrast type.
    :param optic_models_path: The path with the Optic model files.
    :param folder_output: The OptiC output folder.
    :param remove_temp_files: Remove the temporary created files.
    :param verbose: Adjusts the verbosity of the logging.

    :returns: The OptiC output filename.
    """

    image_input = Image(image_fname)
    path_data, file_data, ext_data = sct.extract_fname(image_fname)

    sct.printv('Detecting the spinal cord using OptiC', verbose=verbose)
    image_input_orientation = orientation(image_input, get=True, verbose=False)

    temp_folder = sct.TempFolder()
    temp_folder.copy_from(image_fname)
    curdir = os.getcwd()
    temp_folder.chdir()

    # convert image data type to int16, as required by opencv (backend in OptiC)
    image_int_filename = sct.add_suffix(file_data + ext_data, "_int16")
    img = Image(image_fname)
    img_int16 = img.copy()

    # 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 * (max_out - min_out) / (max_in - min_in)
    img_int16.data = data_rescaled - (data_rescaled.min() - min_out)

    # change data type
    img_int16.changeType('uint16')
    img_int16.setFileName(image_int_filename)
    img_int16.save()
    del img, img_int16

    # reorient the input image to RPI + convert to .nii
    reoriented_image_filename = sct.add_suffix(image_int_filename, "_RPI")
    img_filename = ''.join(sct.extract_fname(reoriented_image_filename)[:2])
    reoriented_image_filename_nii = img_filename + '.nii'
    cmd_reorient = 'sct_image -i "%s" -o "%s" -setorient RPI -v 0' % \
                (image_int_filename, reoriented_image_filename_nii)
    sct.run(cmd_reorient, verbose=0)

    image_rpi_init = Image(reoriented_image_filename_nii)
    nxr, nyr, nzr, ntr, pxr, pyr, pzr, ptr = image_rpi_init.dim
    if init_option is not None:
        if init_option > 1:
            init_option /= (nzr - 1)

    # call the OptiC method to generate the spinal cord centerline
    optic_input = img_filename
    optic_filename = img_filename + '_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)
    sct.run(cmd_optic, verbose=0)

    # convert .img and .hdr files to .nii.gz
    optic_hdr_filename = img_filename + '_optic_ctr.hdr'
    centerline_optic_RPI_filename = sct.add_suffix(file_data + ext_data,
                                                   "_centerline_optic_RPI")
    img = nib.load(optic_hdr_filename)
    nib.save(img, centerline_optic_RPI_filename)

    # reorient the output image to initial orientation
    centerline_optic_filename = sct.add_suffix(file_data + ext_data,
                                               "_centerline_optic")
    cmd_reorient = 'sct_image -i "%s" -o "%s" -setorient "%s" -v 0' % \
                   (centerline_optic_RPI_filename,
                    centerline_optic_filename,
                    image_input_orientation)
    sct.run(cmd_reorient, verbose=0)

    # copy centerline to parent folder
    folder_output_from_temp = folder_output
    if not os.path.isabs(folder_output):
        folder_output_from_temp = os.path.join(curdir, folder_output)

    sct.printv('Copy output to ' + folder_output, verbose=0)
    sct.copy(centerline_optic_filename, folder_output_from_temp)

    if output_roi:
        fname_roi_centerline = centerline2roi(
            fname_image=centerline_optic_RPI_filename,
            folder_output=folder_output_from_temp,
            verbose=verbose)

        # Note: the .roi file is defined in RPI orientation. To be used, it must be applied on the original image with
        # a RPI orientation. For this reason, this script also outputs the input image in RPI orientation
        sct.copy(reoriented_image_filename_nii, folder_output_from_temp)

    # return to initial folder
    temp_folder.chdir_undo()

    # delete temporary folder
    if remove_temp_files:
        temp_folder.cleanup()

    return init_option, os.path.join(folder_output, centerline_optic_filename)
Ejemplo n.º 23
0
class ProcessLabels(object):
    def __init__(self, fname_label, fname_output=None, fname_ref=None, cross_radius=5, dilate=False,
                 coordinates=None, verbose=1):
        self.image_input = Image(fname_label, verbose=verbose)

        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.dilate = dilate
        self.coordinates = coordinates
        self.verbose = verbose

    def process(self, type_process):
        if type_process == 'cross':
            self.output_image = self.cross()
        elif type_process == 'plan':
            self.output_image = self.plan(self.cross_radius, 100, 5)
        elif type_process == 'plan_ref':
            self.output_image = self.plan_ref()
        elif type_process == 'increment':
            self.output_image = self.increment_z_inverse()
        elif type_process == 'disks':
            self.output_image = self.labelize_from_disks()
        elif type_process == 'MSE':
            self.MSE()
            self.fname_output = None
        elif type_process == 'remove':
            self.output_image = self.remove_label()
        elif type_process == 'remove-symm':
            self.output_image = self.remove_label(symmetry=True)
        elif type_process == 'centerline':
            self.extract_centerline()
        elif type_process == 'display-voxel':
            self.display_voxel()
            self.fname_output = None
        elif type_process == 'create':
            self.output_image = self.create_label()
        elif type_process == 'add':
            self.output_image = self.create_label(add=True)
        elif type_process == 'diff':
            self.diff()
            self.fname_output = None
        elif type_process == 'dist-inter':  # second argument is in pixel distance
            self.distance_interlabels(5)
            self.fname_output = None
        elif type_process == 'cubic-to-point':
            self.output_image = self.cubic_to_point()
        else:
            sct.printv('Error: The chosen process is not available.',1,'error')

        # save the output image as minimized integers
        if self.fname_output is not None:
            self.output_image.setFileName(self.fname_output)
            if type_process != 'plan_ref':
                self.output_image.save('minimize_int')
            else:
                self.output_image.save()


    def cross(self):
        image_output = Image(self.image_input, self.verbose)
        nx, ny, nz, nt, px, py, pz, pt = sct.get_dimension(self.image_input.absolutepath)

        coordinates_input = self.image_input.getNonZeroCoordinates()
        d = self.cross_radius  # cross radius in pixel
        dx = d / px  # cross radius in mm
        dy = d / py

        # for all points with non-zeros neighbors, force the neighbors to 0
        for coord in coordinates_input:
            image_output.data[coord.x][coord.y][coord.z] = 0  # remove point on the center of the spinal cord
            image_output.data[coord.x][coord.y + dy][
                coord.z] = coord.value * 10 + 1  # add point at distance from center of spinal cord
            image_output.data[coord.x + dx][coord.y][coord.z] = coord.value * 10 + 2
            image_output.data[coord.x][coord.y - dy][coord.z] = coord.value * 10 + 3
            image_output.data[coord.x - dx][coord.y][coord.z] = coord.value * 10 + 4

            # dilate cross to 3x3
            if self.dilate:
                image_output.data[coord.x - 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x][coord.y + dy - 1][coord.z] = \
                    image_output.data[coord.x + 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y + dy][coord.z] = \
                    image_output.data[coord.x + 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x][coord.y + dy + 1][coord.z] = \
                    image_output.data[coord.x - 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y + dy][coord.z] = \
                    image_output.data[coord.x][coord.y + dy][coord.z]
                image_output.data[coord.x + dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx][coord.y - 1][coord.z] = \
                    image_output.data[coord.x + dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx + 1][coord.y][coord.z] = \
                    image_output.data[coord.x + dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx][coord.y + 1][coord.z] = \
                    image_output.data[coord.x + dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx - 1][coord.y][coord.z] = \
                    image_output.data[coord.x + dx][coord.y][coord.z]
                image_output.data[coord.x - 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x][coord.y - dy - 1][coord.z] = \
                    image_output.data[coord.x + 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y - dy][coord.z] = \
                    image_output.data[coord.x + 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x][coord.y - dy + 1][coord.z] = \
                    image_output.data[coord.x - 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y - dy][coord.z] = \
                    image_output.data[coord.x][coord.y - dy][coord.z]
                image_output.data[coord.x - dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx][coord.y - 1][coord.z] = \
                    image_output.data[coord.x - dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx + 1][coord.y][coord.z] = \
                    image_output.data[coord.x - dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx][coord.y + 1][coord.z] = \
                    image_output.data[coord.x - dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx - 1][coord.y][coord.z] = \
                    image_output.data[coord.x - dx][coord.y][coord.z]

        return image_output

    def plan(self, width, offset=0, gap=1):
        """
        This function creates a plan of thickness="width" and changes its value with an offset and a gap between labels.
        """
        image_output = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[:,:,coord.z-width:coord.z+width] = offset + gap * coord.value

        return image_output

    def plan_ref(self):
        """
        This function generate a plan in the reference space for each label present in the input image
        """

        image_output = Image(self.image_ref, self.verbose)
        image_output.data *= 0

        image_input_neg = Image(self.image_input, self.verbose).copy()
        image_input_pos = Image(self.image_input, self.verbose).copy()
        image_input_neg.data *=0
        image_input_pos.data *=0
        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.changeType('float32')
        for coord in coordinates_input_neg:
            image_output.data[:, :, coord.z] = -coord.value #PB: takes the int value of coord.value
        for coord in coordinates_input_pos:
            image_output.data[:, :, coord.z] = coord.value

        return image_output
    def cubic_to_point(self):
        """
        This function calculates 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.
        It is to be used after applying homothetic warping field to a label file as the labels will be dilated.
        :return:
        """
        from scipy import ndimage
        from numpy import array,mean
        data = self.image_input.data


        # pb: doesn't work if several groups have same value
        image_output = self.image_input.copy()
        data_output = image_output.data
        data_output *= 0
        coordinates = self.image_input.getNonZeroCoordinates(sorting='value')
        #list of present values
        list_values = []
        for i,coord in enumerate(coordinates):
            if i == 0 or coord.value != coordinates[i-1].value:
                list_values.append(coord.value)

        # make list of group of labels coordinates per value
        list_group_labels = []
        list_barycenter = []
        for i in range(0, len(list_values)):
            #mean_coord = mean(array([[coord.x, coord.y, coord.z] for coord in coordinates if coord.value==i]))
            list_group_labels.append([])
            list_group_labels[i] = [coordinates[j] for j in range(len(coordinates)) if coordinates[j].value == list_values[i]]
            # find barycenter: first define each case as a coordinate instance then calculate the value
            list_barycenter.append([0,0,0,0])
            sum_x = 0
            sum_y = 0
            sum_z = 0
            for j in range(len(list_group_labels[i])):
                sum_x += list_group_labels[i][j].x
                sum_y += list_group_labels[i][j].y
                sum_z += list_group_labels[i][j].z
            list_barycenter[i][0] = int(round(sum_x/len(list_group_labels[i])))
            list_barycenter[i][1] = int(round(sum_y/len(list_group_labels[i])))
            list_barycenter[i][2] = int(round(sum_z/len(list_group_labels[i])))
            list_barycenter[i][3] = list_group_labels[i][0].value

        # put value of group at each center of mass
        for i in range(len(list_values)):
            data_output[list_barycenter[i][0],list_barycenter[i][1], list_barycenter[i][2]] = list_barycenter[i][3]

        return image_output

        # Process to use if one wants to calculate the center of mass of a group of labels ordered by volume (and not by value)
        # image_output = self.image_input.copy()
        # data_output = image_output.data
        # data_output *= 0
        # nx = image_output.data.shape[0]
        # ny = image_output.data.shape[1]
        # nz = image_output.data.shape[2]
        # print '.. matrix size: '+str(nx)+' x '+str(ny)+' x '+str(nz)
        #
        # z_centerline = [iz for iz in range(0, nz, 1) if data[:,:,iz].any() ]
        # nz_nonz = len(z_centerline)
        # if nz_nonz==0 :
        #     print '\nERROR: Label file is empty'
        #     sys.exit()
        # x_centerline = [0 for iz in range(0, nz_nonz, 1)]
        # y_centerline = [0 for iz in range(0, nz_nonz, 1)]
        # print '\nGet center of mass for each slice of the label file ...'
        # for iz in xrange(len(z_centerline)):
        #     x_centerline[iz], y_centerline[iz] = ndimage.measurements.center_of_mass(array(data[:,:,z_centerline[iz]]))
        #
        # ## Calculate mean coordinate according to z for each cube of labels:
        # list_cube_labels_x = [[]]
        # list_cube_labels_y = [[]]
        # list_cube_labels_z = [[]]
        # count = 0
        # for i in range(nz_nonz-1):
        #     # Make a list of group of slices that contains a non zero value
        #     # check if the group is only one slice of height (at first slice)
        #     if i==0 and z_centerline[i] - z_centerline[i+1] != -1:
        #         list_cube_labels_z[count].append(z_centerline[i])
        #         list_cube_labels_x[count].append(x_centerline[i])
        #         list_cube_labels_y[count].append(y_centerline[i])
        #         list_cube_labels_z.append([])
        #         list_cube_labels_x.append([])
        #         list_cube_labels_y.append([])
        #         count += 1
        #     # check if the group is only one slice of height (in the middle)
        #     if i>0 and z_centerline[i-1] - z_centerline[i] != -1 and z_centerline[i] - z_centerline[i+1] != -1:
        #         list_cube_labels_z[count].append(z_centerline[i])
        #         list_cube_labels_x[count].append(x_centerline[i])
        #         list_cube_labels_y[count].append(y_centerline[i])
        #         list_cube_labels_z.append([])
        #         list_cube_labels_x.append([])
        #         list_cube_labels_y.append([])
        #         count += 1
        #     if z_centerline[i] - z_centerline[i+1] == -1:
        #         # Verify if the value has already been recovered and add if not
        #         #If the group is empty add first value do not if it is not empty as it will copy it for a second time
        #         if len(list_cube_labels_z[count]) == 0 :#or list_cube_labels[count][-1] != z_centerline[i]:
        #             list_cube_labels_z[count].append(z_centerline[i])
        #             list_cube_labels_x[count].append(x_centerline[i])
        #             list_cube_labels_y[count].append(y_centerline[i])
        #         list_cube_labels_z[count].append(z_centerline[i+1])
        #         list_cube_labels_x[count].append(x_centerline[i+1])
        #         list_cube_labels_y[count].append(y_centerline[i+1])
        #         if i+2 < nz_nonz-1 and z_centerline[i+1] - z_centerline[i+2] != -1:
        #             list_cube_labels_z.append([])
        #             list_cube_labels_x.append([])
        #             list_cube_labels_y.append([])
        #             count += 1
        #
        # z_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # x_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # y_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # v_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # for i in range(len(list_cube_labels_z)):
        #     for j in range(len(list_cube_labels_z[i])):
        #         z_label_mean[i] += list_cube_labels_z[i][j]
        #         x_label_mean[i] += list_cube_labels_x[i][j]
        #         y_label_mean[i] += list_cube_labels_y[i][j]
        #     z_label_mean[i] = int(round(z_label_mean[i]/len(list_cube_labels_z[i])))
        #     x_label_mean[i] = int(round(x_label_mean[i]/len(list_cube_labels_x[i])))
        #     y_label_mean[i] = int(round(y_label_mean[i]/len(list_cube_labels_y[i])))
        #     # We suppose that the labels' value of the group is the value of the barycentre
        #     v_label_mean[i] = data[x_label_mean[i],y_label_mean[i], z_label_mean[i]]
        #
        # ## Put labels of value one into mean coordinates
        # for i in range(len(z_label_mean)):
        #     data_output[x_label_mean[i],y_label_mean[i], z_label_mean[i]] = v_label_mean[i]
        #
        # return image_output

    def increment_z_inverse(self):
        """
        This function increments all the labels present in the input image, inversely ordered by Z.
        Therefore, labels are incremented from top to bottom, assuming a RPI orientation
        Labels are assumed to be non-zero.
        """
        image_output = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = i + 1

        return image_output

    def labelize_from_disks(self):
        """
        This function creates an image with regions labelized depending on values from reference.
        Typically, user inputs an 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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = coordinates_ref[j].value

        return image_output

    def symmetrizer(self, side='left'):
        """
        This function symmetrize the input image. One side of the image will be copied on the other side. We assume a
        RPI orientation.
        :param side: string 'left' or 'right'. Side that will be copied on the other side.
        :return:
        """
        image_output = Image(self.image_input, self.verbose)

        image_output[0:]

        """inspiration: (from atlas creation matlab script)
        temp_sum = temp_g + temp_d;
        temp_sum_flip = temp_sum(end:-1:1,:);
        temp_sym = (temp_sum + temp_sum_flip) / 2;

        temp_g(1:end / 2,:) = 0;
        temp_g(1 + end / 2:end,:) = temp_sym(1 + end / 2:end,:);
        temp_d(1:end / 2,:) = temp_sym(1:end / 2,:);
        temp_d(1 + end / 2:end,:) = 0;

        tractsHR
        {label_l}(:,:, num_slice_ref) = temp_g;
        tractsHR
        {label_r}(:,:, num_slice_ref) = temp_d;
        """

        return image_output

    def MSE(self, threshold_mse=0):
        """
        This function computes 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 round(coord.value) not in [round(coord_ref.value) for coord_ref in coordinates_ref]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if round(coord_ref.value) not in [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 round(coord_ref.value) == round(coord.value):
                    result += (coord_ref.z - coord.z) ** 2
                    break
        result = math.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            f = open(self.image_input.path + 'error_log_' + self.image_input.file_name + '.txt', 'w')
            f.write(
                'The labels error (MSE) between ' + self.image_input.file_name + ' and ' + self.image_ref.file_name + ' is: ' + str(
                    result))
            f.close()

        return result

    def create_label(self, add=False):
        """
        This function 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 not add:
            image_output.data *= 0

        # loop across labels
        for i, coord in enumerate(self.coordinates):
            # display info
            sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' +
                       str(coord.value), 1)
            image_output.data[coord.x, coord.y, coord.z] = coord.value

        return image_output

    @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 filter(lambda x: x.value == coord.value, coord_ref)]
            if symmetry:
                result_coord_ref = [coord for coord in coord_ref if filter(lambda x: x.value == coord.value, result_coord_input)]

        return result_coord_input, result_coord_ref

    def remove_label(self, symmetry=False):
        """
        This function compares 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)

        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[coord.x, coord.y, coord.z] = int(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)
            for coord in result_coord_ref:
                image_output_ref.data[coord.x, coord.y, coord.z] = int(round(coord.value))
            image_output_ref.setFileName(self.fname_output[1])
            image_output_ref.save('minimize_int')

            self.fname_output = self.fname_output[0]

        return image_output

    def extract_centerline(self):
        """
        This function write a text file with the coordinates of the centerline.
        The image is suppose to be RPI
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z')

        fo = open(self.fname_output, "wb")
        for coord in coordinates_input:
            line = (coord.x,coord.y, coord.z)
            fo.write("%i %i %i\n" % line)
        fo.close()

    def display_voxel(self):
        """
        This function displays 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='z')
        useful_notation = ''
        for coord in coordinates_input:
            print 'Position=(' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ') -- Value= ' + str(coord.value)
            if useful_notation != '':
                useful_notation = useful_notation + ':'
            useful_notation = useful_notation + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ',' + str(coord.value)
        print 'Useful notation:'
        print useful_notation
        return coordinates_input

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

        print "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:
                print coord.value

        print "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:
                print coord_ref.value

    def distance_interlabels(self, max_dist):
        """
        This function calculates 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 = math.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:
                print '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)
Ejemplo n.º 24
0
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:
        :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

    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 == 'cross':
            self.output_image = self.cross()
        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':
            self.output_image = self.remove_label()
        if type_process == 'remove-symm':
            self.output_image = self.remove_label(symmetry=True)
        if type_process == 'centerline':
            self.extract_centerline()
        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)

        # save the output image as minimized integers
        if self.fname_output is not None:
            self.output_image.setFileName(self.fname_output)
            if change_orientation:
                self.output_image.change_orientation(input_orientation)
            if type_process == 'vert-continuous':
                self.output_image.save('float32')
            elif type_process != 'plan_ref':
                self.output_image.save('minimize_int')
            else:
                self.output_image.save()

    def add(self, value):
        """
        This function add a specified value to all non-zero voxels.
        """
        image_output = Image(self.image_input, self.verbose)
        # 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 not add:
            image_output.data *= 0

        # 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)
        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
        -------
        image_output: Image object with labels.
        """
        # copy input Image object (will use the same header)
        image_output = self.image_input.copy()
        # set all voxels to 0
        image_output.data *= 0
        # loop across labels
        for i, 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]
            # if z=-1, replace with nz/2
            if z == -1:
                z = int(round(image_output.dim[2] / 2.0))
            # get center of mass of segmentation at given z
            x, y = ndimage.measurements.center_of_mass(
                np.array(self.image_input.data[:, :, z]))
            # round values to make indices
            x, y = int(round(x)), int(round(y))
            # display info
            sct.printv(
                'Label #' + str(i) + ': ' + str(x) + ',' + str(y) + ',' +
                str(z) + ' --> ' + str(value), 1)
            if len(image_output.data.shape) == 3:
                image_output.data[x, y, z] = value
            elif len(image_output.data.shape) == 2:
                assert str(
                    z
                ) == '0', "ERROR: 2D coordinates should have a Z value of 0. Z coordinate is :" + str(
                    z)
                image_output.data[x, y] = value
        return image_output

    def cross(self):
        """
        create a cross.
        :return:
        """
        output_image = Image(self.image_input, self.verbose)
        nx, ny, nz, nt, px, py, pz, pt = Image(
            self.image_input.absolutepath).dim

        coordinates_input = self.image_input.getNonZeroCoordinates()
        d = self.cross_radius  # cross radius in pixel
        dx = d / px  # cross radius in mm
        dy = d / py

        # clean output_image
        output_image.data *= 0

        cross_coordinates = self.get_crosses_coordinates(
            coordinates_input, dx, self.image_ref, self.dilate)

        for coord in cross_coordinates:
            output_image.data[int(round(coord.x)),
                              int(round(coord.y)),
                              int(round(coord.z))] = coord.value

        return output_image

    @staticmethod
    def get_crosses_coordinates(coordinates_input,
                                gapxy=15,
                                image_ref=None,
                                dilate=False):
        from msct_types import Coordinate

        # if reference image is provided (segmentation), we draw the cross perpendicular to the centerline
        if image_ref is not None:
            # smooth centerline
            from sct_straighten_spinalcord import smooth_centerline
            x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
                self.image_ref, verbose=self.verbose)

        # compute crosses
        cross_coordinates = []
        for coord in coordinates_input:
            if image_ref is None:
                from sct_straighten_spinalcord import compute_cross
                cross_coordinates_temp = compute_cross(coord, gapxy)
            else:
                from sct_straighten_spinalcord import compute_cross_centerline
                from numpy import where
                index_z = where(z_centerline == coord.z)
                deriv = Coordinate([
                    x_centerline_deriv[index_z][0],
                    y_centerline_deriv[index_z][0],
                    z_centerline_deriv[index_z][0], 0.0
                ])
                cross_coordinates_temp = compute_cross_centerline(
                    coord, deriv, gapxy)

            for i, coord_cross in enumerate(cross_coordinates_temp):
                coord_cross.value = coord.value * 10 + i + 1

            # dilate cross to 3x3x3
            if dilate:
                additional_coordinates = []
                for coord_temp in cross_coordinates_temp:
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y, coord_temp.z + 1.0,
                            coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y, coord_temp.z - 1.0,
                            coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y + 1.0, coord_temp.z,
                            coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y + 1.0,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y + 1.0,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y - 1.0, coord_temp.z,
                            coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y - 1.0,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x, coord_temp.y - 1.0,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))

                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y, coord_temp.z,
                            coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y + 1.0,
                            coord_temp.z, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y + 1.0,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y + 1.0,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y - 1.0,
                            coord_temp.z, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y - 1.0,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x + 1.0, coord_temp.y - 1.0,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))

                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y, coord_temp.z,
                            coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y + 1.0,
                            coord_temp.z, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y + 1.0,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y + 1.0,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y - 1.0,
                            coord_temp.z, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y - 1.0,
                            coord_temp.z + 1.0, coord_temp.value
                        ]))
                    additional_coordinates.append(
                        Coordinate([
                            coord_temp.x - 1.0, coord_temp.y - 1.0,
                            coord_temp.z - 1.0, coord_temp.value
                        ]))

                cross_coordinates_temp.extend(additional_coordinates)

            cross_coordinates.extend(cross_coordinates_temp)

        cross_coordinates = sorted(cross_coordinates,
                                   key=lambda obj: obj.value)
        return cross_coordinates

    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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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 = Image(self.image_ref, self.verbose)
        image_output.data *= 0

        image_input_neg = Image(self.image_input, self.verbose).copy()
        image_input_pos = Image(self.image_input, self.verbose).copy()
        image_input_neg.data *= 0
        image_input_pos.data *= 0
        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.changeType('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 = self.image_input.copy()
        output_image.data *= 0

        # 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(round(center_of_mass.x)) + ", " +
                       str(round(center_of_mass.y)) + ", " +
                       str(round(center_of_mass.z)) + ")",
                       verbose=self.verbose)
            output_image.data[
                int(round(center_of_mass.x)),
                int(round(center_of_mass.y)),
                int(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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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 round(coord.value) not in [
                    round(coord_ref.value) for coord_ref in coordinates_ref
            ]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if round(coord_ref.value) not in [
                    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 round(coord_ref.value) == round(coord.value):
                    result += (coord_ref.z - coord.z)**2
                    break
        result = math.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            f = open(
                self.image_input.path + 'error_log_' +
                self.image_input.file_name + '.txt', 'w')
            f.write('The labels error (MSE) between ' +
                    self.image_input.file_name + ' and ' +
                    self.image_ref.file_name + ' is: ' + str(result))
            f.close()

        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 = Image(self.image_input, verbose=self.verbose)
        image_output.data *= 0  # put all voxels to 0

        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(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(round(coord.value))
            image_output_ref.setFileName(self.fname_output[1])
            image_output_ref.save('minimize_int')

            self.fname_output = self.fname_output[0]

        return image_output

    def extract_centerline(self):
        """
        Write a text file with the coordinates of the centerline.
        The image is suppose to be RPI
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z')

        fo = open(self.fname_output, "wb")
        for coord in coordinates_input:
            line = (coord.x, coord.y, coord.z)
            fo.write("%i %i %i\n" % line)
        fo.close()

    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_phys2continuouspix([[c.x, c.y, c.y]])[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 = math.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 = Image(self.image_input, self.verbose)
        im_output.data *= 0

        # 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 sct_straighten_spinalcord import smooth_centerline
        x_centerline_fit, y_centerline_fit, z_centerline_fit, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
            self.image_input, algo_fitting='nurbs', verbose=0)
        value_centerline = np.array([
            im_input.data[int(x_centerline_fit[it]),
                          int(y_centerline_fit[it]),
                          int(z_centerline_fit[it])]
            for it in range(len(z_centerline_fit))
        ])

        # 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([
                math.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_fit[indexes_slice[0][index_slice + 1]] -
                      z_centerline_fit[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_fit):
            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([
                math.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_fit[indexes_slice[index_slice + 1]] -
                            z_centerline_fit[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.changeType('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.file_name
        params.output_file_name = self.fname_output
        params.subtitle = self.msg
        output = self.image_input.copy()
        output.data *= 0
        output.setFileName(self.fname_output)
        launch_sagittal_dialog(self.image_input, output, params)

        return output
Ejemplo n.º 25
0
def segment_3d(model_fname, contrast_type, fname_in, fname_out):
    """Perform segmentation with 3D convolutions."""
    from spinalcordtoolbox.deepseg_sc.cnn_models_3d import load_trained_model
    dct_patch_sc_3d = {
        't2': {
            'size': (64, 64, 48),
            'mean': 65.8562,
            'std': 59.7999
        },
        't2s': {
            'size': (96, 96, 48),
            'mean': 87.0212,
            'std': 64.425
        },
        't1': {
            'size': (64, 64, 48),
            'mean': 88.5001,
            'std': 66.275
        }
    }
    # load 3d model
    seg_model = load_trained_model(model_fname)

    im = Image(fname_in)
    out = im.copy()
    out.data *= 0
    out.changeType('uint8')

    # segment the spinal cord
    sct.log.info(
        "Segmenting the spinal cord using deep learning on 3D patches...")
    z_patch_size = dct_patch_sc_3d[contrast_type]['size'][2]
    z_step_keep = range(0, im.data.shape[2], z_patch_size)
    for zz in z_step_keep:
        if zz == z_step_keep[
                -1]:  # deal with instances where the im.data.shape[2] % patch_size_z != 0
            patch_im = np.zeros(dct_patch_sc_3d[contrast_type]['size'])
            z_patch_extracted = im.data.shape[2] - zz
            patch_im[:, :, :z_patch_extracted] = im.data[:, :, zz:]
        else:
            z_patch_extracted = z_patch_size
            patch_im = im.data[:, :, zz:z_patch_size + zz]

        if np.sum(
                patch_im
        ):  # Check if the patch is (not) empty, which could occur after a brain detection.
            patch_norm = _normalize_data(
                patch_im, dct_patch_sc_3d[contrast_type]['mean'],
                dct_patch_sc_3d[contrast_type]['std'])
            patch_pred_proba = seg_model.predict(
                np.expand_dims(np.expand_dims(patch_norm, 0), 0))
            pred_seg_th = (patch_pred_proba > 0.5).astype(int)[0, 0, :, :, :]

            x_cOm, y_cOm = None, None
            for zz_pp in range(z_patch_size):
                pred_seg_pp = post_processing_slice_wise(
                    pred_seg_th[:, :, zz_pp], x_cOm, y_cOm)
                pred_seg_th[:, :, zz_pp] = pred_seg_pp
                x_cOm, y_cOm = center_of_mass(pred_seg_pp)
                x_cOm, y_cOm = np.round(x_cOm), np.round(y_cOm)

            if zz == z_step_keep[-1]:
                out.data[:, :, zz:] = pred_seg_th[:, :, :z_patch_extracted]
            else:
                out.data[:, :, zz:z_patch_size + zz] = pred_seg_th

    out.setFileName(fname_out)
    out.save()
    del im, out
def prepare_input_image_for_viewer(fname_data):
    # reorient image to SAL to be compatible with viewer
    im_input = Image(fname_data)
    im_input_SAL = im_input.copy()
    im_input_SAL.change_orientation('SAL')
    return(im_input_SAL)
class MultiLabelRegistration:
    def __init__(self,
                 fname_gm,
                 fname_wm,
                 path_template,
                 fname_warp_template2target,
                 param=None,
                 fname_warp_target2template=None,
                 apply_warp_template=0,
                 fname_template_dest=None):
        if param is None:
            self.param = Param()
        else:
            self.param = param
        self.im_gm = Image(fname_gm)
        self.im_wm = Image(fname_wm)
        self.path_template = sct.slash_at_the_end(path_template, 1)

        # get GM and WM files from template:
        fname_template_gm, fname_template_wm = None, None
        for fname in os.listdir(self.path_template + 'template/'):
            if 'gm' in fname.lower():
                fname_template_gm = self.path_template + 'template/' + fname
            elif 'wm' in fname.lower():
                fname_template_wm = self.path_template + 'template/' + fname
        if fname_template_gm is not None and fname_template_wm is not None:
            self.im_template_gm = Image(fname_template_gm)
            self.im_template_wm = Image(fname_template_wm)
            if fname_template_gm.split('/')[-1] == 'MNI-Poly-AMU_GM.nii.gz':
                self.template = 'MNI-Poly-AMU'
            elif fname_template_gm.split('/')[-1] == 'PAM50_gm.nii.gz':
                self.template = 'PAM50'
            else:
                self.template = 'custom'

        # template file in its original space:
        self.fname_template_dest = fname_template_dest

        # Previous warping fields:
        self.fname_warp_template2target = fname_warp_template2target
        self.fname_warp_target2template = fname_warp_target2template

        # new warping fields:
        self.fname_warp_template2gm = ''
        self.fname_warp_gm2template = ''

        # temporary fix - related to issue #871
        self.apply_warp_template = apply_warp_template

    def register(self):
        # accentuate separation WM/GM
        self.im_gm = thr_im(self.im_gm, 0.01, self.param.thr)
        self.im_wm = thr_im(self.im_wm, 0.01, self.param.thr)
        self.im_template_gm = thr_im(self.im_template_gm, 0.01, self.param.thr)
        self.im_template_wm = thr_im(self.im_template_wm, 0.01, self.param.thr)

        # create multilabel images:
        # copy GM images to keep header information
        im_automatic_ml = self.im_gm.copy()
        im_template_ml = self.im_template_gm.copy()

        # create multi-label segmentation with GM*200 + WM*100 (100 and 200 encoded in self.param.gap)
        im_automatic_ml.data = self.param.gap[
            1] * self.im_gm.data + self.param.gap[0] * self.im_wm.data
        im_template_ml.data = self.param.gap[
            1] * self.im_template_gm.data + self.param.gap[
                0] * self.im_template_wm.data

        # set new names
        fname_automatic_ml = 'multilabel_automatic_seg.nii.gz'
        fname_template_ml = 'multilabel_template_seg.nii.gz'
        im_automatic_ml.setFileName(fname_automatic_ml)
        im_template_ml.setFileName(fname_template_ml)

        # Create temporary folder and put files in it
        tmp_dir = sct.tmp_create()

        path_gm, file_gm, ext_gm = sct.extract_fname(fname_gm)
        path_warp_template2target, file_warp_template2target, ext_warp_template2target = sct.extract_fname(
            self.fname_warp_template2target)

        convert(fname_gm, tmp_dir + file_gm + ext_gm)
        convert(fname_warp_template,
                tmp_dir + file_warp_template2target + ext_warp_template2target,
                squeeze_data=0)
        if self.fname_warp_target2template is not None:
            path_warp_target2template, file_warp_target2template, ext_warp_target2template = sct.extract_fname(
                self.fname_warp_target2template)
            convert(self.fname_warp_target2template,
                    tmp_dir + file_warp_target2template +
                    ext_warp_target2template,
                    squeeze_data=0)

        os.chdir(tmp_dir)
        # save images
        im_automatic_ml.save()
        im_template_ml.save()

        # apply template2image warping field
        if self.apply_warp_template == 1:
            fname_template_ml_new = sct.add_suffix(fname_template_ml, '_r')
            sct.run('sct_apply_transfo -i ' + fname_template_ml + ' -d ' +
                    fname_automatic_ml + ' -w ' + file_warp_template2target +
                    ext_warp_template2target + ' -o ' + fname_template_ml_new)
            fname_template_ml = fname_template_ml_new

        nx, ny, nz, nt, px, py, pz, pt = im_automatic_ml.dim
        size_mask = int(22.5 / px)
        fname_mask = 'square_mask.nii.gz'
        sct.run('sct_create_mask -i ' + fname_automatic_ml +
                ' -p centerline,' + fname_automatic_ml + ' -f box -size ' +
                str(size_mask) + ' -o ' + fname_mask)

        fname_automatic_ml, xi, xf, yi, yf, zi, zf = crop_im(
            fname_automatic_ml, fname_mask)
        fname_template_ml, xi, xf, yi, yf, zi, zf = crop_im(
            fname_template_ml, fname_mask)

        #        fname_automatic_ml_smooth = sct.add_suffix(fname_automatic_ml, '_smooth')
        #        sct.run('sct_maths -i '+fname_automatic_ml+' -smooth '+str(self.param.smooth)+','+str(self.param.smooth)+',0 -o '+fname_automatic_ml_smooth)
        #        fname_automatic_ml = fname_automatic_ml_smooth

        path_automatic_ml, file_automatic_ml, ext_automatic_ml = sct.extract_fname(
            fname_automatic_ml)
        path_template_ml, file_template_ml, ext_template_ml = sct.extract_fname(
            fname_template_ml)

        # Register multilabel images together
        cmd_reg = 'sct_register_multimodal -i ' + fname_template_ml + ' -d ' + fname_automatic_ml + ' -param ' + self.param.param_reg
        if 'centermass' in self.param.param_reg:
            fname_template_ml_seg = sct.add_suffix(fname_template_ml, '_bin')
            sct.run('sct_maths -i ' + fname_template_ml + ' -bin 0 -o ' +
                    fname_template_ml_seg)

            fname_automatic_ml_seg = sct.add_suffix(fname_automatic_ml, '_bin')
            # sct.run('sct_maths -i '+fname_automatic_ml+' -thr 50 -o '+fname_automatic_ml_seg)
            sct.run('sct_maths -i ' + fname_automatic_ml + ' -bin 50 -o ' +
                    fname_automatic_ml_seg)

            cmd_reg += ' -iseg ' + fname_template_ml_seg + ' -dseg ' + fname_automatic_ml_seg

        sct.run(cmd_reg)
        fname_warp_multilabel_template2auto = 'warp_' + file_template_ml + '2' + file_automatic_ml + '.nii.gz'
        fname_warp_multilabel_auto2template = 'warp_' + file_automatic_ml + '2' + file_template_ml + '.nii.gz'

        self.fname_warp_template2gm = sct.extract_fname(
            self.fname_warp_template2target
        )[1] + '_reg_gm' + sct.extract_fname(
            self.fname_warp_template2target)[2]
        # fname_warp_multilabel_template2auto = pad_im(fname_warp_multilabel_template2auto, nx, ny, nz, xi, xf, yi, yf, zi, zf)
        # fname_warp_multilabel_auto2template = pad_im(fname_warp_multilabel_auto2template, nx, ny, nz, xi, xf, yi, yf, zi, zf)

        sct.run('sct_concat_transfo -w ' + file_warp_template2target +
                ext_warp_template2target + ',' +
                fname_warp_multilabel_template2auto + ' -d ' + file_gm +
                ext_gm + ' -o ' + self.fname_warp_template2gm)

        if self.fname_warp_target2template is not None:
            if self.fname_template_dest is None:
                path_script = os.path.dirname(__file__)
                path_sct = os.path.dirname(path_script)
                if self.template == 'MNI-Poly-AMU':
                    self.fname_template_dest = path_sct + '/data/MNI-Poly-AMU/template/MNI-Poly-AMU_T2.nii.gz'
                elif self.template == 'PAM50':
                    self.fname_template_dest = path_sct + '/data/PAM50/template/PAM50_t2.nii.gz'

            self.fname_warp_gm2template = sct.extract_fname(
                self.fname_warp_target2template
            )[1] + '_reg_gm' + sct.extract_fname(
                self.fname_warp_target2template)[2]
            sct.run('sct_concat_transfo -w ' +
                    fname_warp_multilabel_auto2template + ',' +
                    file_warp_target2template + ext_warp_target2template +
                    ' -d ' + self.fname_template_dest + ' -o ' +
                    self.fname_warp_gm2template)

        os.chdir('..')

        # sct.generate_output_file(tmp_dir+fname_warp_multilabel_template2auto, self.param.output_folder+'warp_template_multilabel2automatic_seg_multilabel.nii.gz')
        # sct.generate_output_file(tmp_dir+fname_warp_multilabel_auto2template, self.param.output_folder+'warp_automatic_seg_multilabel2template_multilabel.nii.gz')

        sct.generate_output_file(
            tmp_dir + self.fname_warp_template2gm,
            self.param.output_folder + self.fname_warp_template2gm)
        if self.fname_warp_target2template is not None:
            sct.generate_output_file(
                tmp_dir + self.fname_warp_gm2template,
                self.param.output_folder + self.fname_warp_gm2template)

        if self.param.qc:
            fname_grid_warped = visualize_warp(
                tmp_dir + fname_warp_multilabel_template2auto,
                rm_tmp=self.param.remove_tmp)
            path_grid_warped, file_grid_warped, ext_grid_warped = sct.extract_fname(
                fname_grid_warped)
            sct.generate_output_file(
                fname_grid_warped,
                self.param.output_folder + file_grid_warped + ext_grid_warped)

        if self.param.remove_tmp:
            sct.run('rm -rf ' + tmp_dir, error_exit='warning')

    def validation(self, fname_manual_gmseg, fname_sc_seg):
        path_manual_gmseg, file_manual_gmseg, ext_manual_gmseg = sct.extract_fname(
            fname_manual_gmseg)
        path_sc_seg, file_sc_seg, ext_sc_seg = sct.extract_fname(fname_sc_seg)

        # Create tmp folder and copy files in it
        tmp_dir = sct.tmp_create()
        sct.run('cp ' + fname_manual_gmseg + ' ' + tmp_dir +
                file_manual_gmseg + ext_manual_gmseg)
        sct.run('cp ' + fname_sc_seg + ' ' + tmp_dir + file_sc_seg +
                ext_sc_seg)
        sct.run('cp ' + self.param.output_folder +
                self.fname_warp_template2gm + ' ' + tmp_dir +
                self.fname_warp_template2gm)
        os.chdir(tmp_dir)

        sct.run('sct_warp_template -d ' + fname_manual_gmseg + ' -w ' +
                self.fname_warp_template2gm + ' -qc 0 -a 0')
        if 'MNI-Poly-AMU_GM.nii.gz' in os.listdir('label/template/'):
            im_new_template_gm = Image('label/template/MNI-Poly-AMU_GM.nii.gz')
            im_new_template_wm = Image('label/template/MNI-Poly-AMU_WM.nii.gz')
        else:
            im_new_template_gm = Image('label/template/PAM50_gm.nii.gz')
            im_new_template_wm = Image('label/template/PAM50_wm.nii.gz')

        im_new_template_gm = thr_im(im_new_template_gm, self.param.thr,
                                    self.param.thr)
        im_new_template_wm = thr_im(im_new_template_wm, self.param.thr,
                                    self.param.thr)

        self.im_template_gm = thr_im(self.im_template_gm, self.param.thr,
                                     self.param.thr)
        self.im_template_wm = thr_im(self.im_template_wm, self.param.thr,
                                     self.param.thr)

        fname_new_template_gm = 'new_template_gm.nii.gz'
        im_new_template_gm.setFileName(fname_new_template_gm)
        im_new_template_gm.save()

        fname_new_template_wm = 'new_template_wm.nii.gz'
        im_new_template_wm.setFileName(fname_new_template_wm)
        im_new_template_wm.save()

        fname_old_template_wm = 'old_template_wm.nii.gz'
        self.im_template_wm.setFileName(fname_old_template_wm)
        self.im_template_wm.save()

        fname_old_template_gm = 'old_template_gm.nii.gz'
        self.im_template_gm.setFileName(fname_old_template_gm)
        self.im_template_gm.save()

        fname_manual_wmseg = 'target_manual_wmseg.nii.gz'
        sct.run('sct_maths -i ' + file_sc_seg + ext_sc_seg + ' -sub ' +
                file_manual_gmseg + ext_manual_gmseg + ' -o ' +
                fname_manual_wmseg)

        # Compute Hausdorff distance
        status, output_old_hd = sct.run('sct_compute_hausdorff_distance -i ' +
                                        fname_old_template_gm + ' -r ' +
                                        file_manual_gmseg + ext_manual_gmseg +
                                        ' -t 1  -v 1')
        status, output_new_hd = sct.run('sct_compute_hausdorff_distance -i ' +
                                        fname_new_template_gm + ' -r ' +
                                        file_manual_gmseg + ext_manual_gmseg +
                                        ' -t 1  -v 1')

        hd_name = 'hd_md_multilabel_reg.txt'
        hd_fic = open(hd_name, 'w')
        hd_fic.write(
            'The "diff" columns are comparisons between regular template registration and corrected template registration according to SC internal structure\n'
            'Diff = metric_regular_reg - metric_corrected_reg\n')
        hd_fic.write('#Slice, HD, HD diff, MD, MD diff\n')

        no_ref_slices = []

        init_hd = "Hausdorff's distance  -  First relative Hausdorff's distance median - Second relative Hausdorff's distance median(all in mm)\n"
        old_gm_hd = output_old_hd[output_old_hd.find(init_hd) +
                                  len(init_hd):].split('\n')
        new_gm_hd = output_new_hd[output_new_hd.find(init_hd) +
                                  len(init_hd):].split('\n')

        for i in range(len(old_gm_hd) - 3):  # last two lines are informations
            i_old, val_old = old_gm_hd[i].split(':')
            i_new, val_new = new_gm_hd[i].split(':')
            i_old = int(i_old[-2:])
            i_new = int(i_new[-2:])

            assert i == i_old == i_new, 'ERROR: when comparing Hausdorff distances, slice numbers differs.'
            hd_old, med1_old, med2_old = val_old.split('-')
            hd_new, med1_new, med2_new = val_new.split('-')

            if float(hd_old) == 0.0:
                no_ref_slices.append(i)
                hd_fic.write(str(i) + ', NO MANUAL SEGMENTATION\n')
            else:
                md_new = max(float(med1_new), float(med2_new))
                md_old = max(float(med1_old), float(med2_old))

                hd_fic.write(
                    str(i) + ', ' + hd_new + ', ' +
                    str(float(hd_old) - float(hd_new)) + ', ' + str(md_new) +
                    ', ' + str(md_old - md_new) + '\n')
        hd_fic.close()

        # Compute Dice coefficient
        # --- DC old template
        try:
            status_old_gm, output_old_gm = sct.run(
                'sct_dice_coefficient -i ' + file_manual_gmseg +
                ext_manual_gmseg + ' -d ' + fname_old_template_gm +
                ' -2d-slices 2',
                error_exit='warning',
                raise_exception=True)
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            corrected_manual_gmseg = file_manual_gmseg + '_in_old_template_space' + ext_manual_gmseg
            sct.run('isct_antsRegistration -d 3 -t Translation[0] -m MI[' +
                    fname_old_template_gm + ',' + file_manual_gmseg +
                    ext_manual_gmseg + ',1,16] -o [reg_ref_to_res,' +
                    corrected_manual_gmseg + '] -n BSpline[3] -c 0 -f 1 -s 0')
            # sct.run('sct_maths -i '+corrected_manual_gmseg+' -thr 0.1 -o '+corrected_manual_gmseg)
            sct.run('sct_maths -i ' + corrected_manual_gmseg +
                    ' -bin 0.1 -o ' + corrected_manual_gmseg)
            status_old_gm, output_old_gm = sct.run(
                'sct_dice_coefficient -i ' + corrected_manual_gmseg + ' -d ' +
                fname_old_template_gm + '  -2d-slices 2',
                error_exit='warning')

        try:
            status_old_wm, output_old_wm = sct.run(
                'sct_dice_coefficient -i ' + fname_manual_wmseg + ' -d ' +
                fname_old_template_wm + ' -2d-slices 2',
                error_exit='warning',
                raise_exception=True)
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            path_manual_wmseg, file_manual_wmseg, ext_manual_wmseg = sct.extract_fname(
                fname_manual_wmseg)
            corrected_manual_wmseg = file_manual_wmseg + '_in_old_template_space' + ext_manual_wmseg
            sct.run('isct_antsRegistration -d 3 -t Translation[0] -m MI[' +
                    fname_old_template_wm + ',' + fname_manual_wmseg +
                    ',1,16] -o [reg_ref_to_res,' + corrected_manual_wmseg +
                    '] -n BSpline[3] -c 0 -f 1 -s 0')
            # sct.run('sct_maths -i '+corrected_manual_wmseg+' -thr 0.1 -o '+corrected_manual_wmseg)
            sct.run('sct_maths -i ' + corrected_manual_wmseg +
                    ' -bin 0.1 -o ' + corrected_manual_wmseg)
            status_old_wm, output_old_wm = sct.run(
                'sct_dice_coefficient -i ' + corrected_manual_wmseg + ' -d ' +
                fname_old_template_wm + '  -2d-slices 2',
                error_exit='warning')

        # --- DC new template
        try:
            status_new_gm, output_new_gm = sct.run(
                'sct_dice_coefficient -i ' + file_manual_gmseg +
                ext_manual_gmseg + ' -d ' + fname_new_template_gm +
                ' -2d-slices 2',
                error_exit='warning',
                raise_exception=True)
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            corrected_manual_gmseg = file_manual_gmseg + '_in_new_template_space' + ext_manual_gmseg
            sct.run('isct_antsRegistration -d 3 -t Translation[0] -m MI[' +
                    fname_new_template_gm + ',' + file_manual_gmseg +
                    ext_manual_gmseg + ',1,16] -o [reg_ref_to_res,' +
                    corrected_manual_gmseg + '] -n BSpline[3] -c 0 -f 1 -s 0')
            # sct.run('sct_maths -i '+corrected_manual_gmseg+' -thr 0.1 -o '+corrected_manual_gmseg)
            sct.run('sct_maths -i ' + corrected_manual_gmseg +
                    ' -bin 0.1 -o ' + corrected_manual_gmseg)
            status_new_gm, output_new_gm = sct.run(
                'sct_dice_coefficient -i ' + corrected_manual_gmseg + ' -d ' +
                fname_new_template_gm + '  -2d-slices 2',
                error_exit='warning')

        try:
            status_new_wm, output_new_wm = sct.run(
                'sct_dice_coefficient -i ' + fname_manual_wmseg + ' -d ' +
                fname_new_template_wm + ' -2d-slices 2',
                error_exit='warning',
                raise_exception=True)
        except Exception:
            # put the result and the reference in the same space using a registration with ANTs with no iteration:
            path_manual_wmseg, file_manual_wmseg, ext_manual_wmseg = sct.extract_fname(
                fname_manual_wmseg)
            corrected_manual_wmseg = file_manual_wmseg + '_in_new_template_space' + ext_manual_wmseg
            sct.run('isct_antsRegistration -d 3 -t Translation[0] -m MI[' +
                    fname_new_template_wm + ',' + fname_manual_wmseg +
                    ',1,16] -o [reg_ref_to_res,' + corrected_manual_wmseg +
                    '] -n BSpline[3] -c 0 -f 1 -s 0')
            # sct.run('sct_maths -i '+corrected_manual_wmseg+' -thr 0.1 -o '+corrected_manual_wmseg)
            sct.run('sct_maths -i ' + corrected_manual_wmseg +
                    ' -bin 0.1 -o ' + corrected_manual_wmseg)
            status_new_wm, output_new_wm = sct.run(
                'sct_dice_coefficient -i ' + corrected_manual_wmseg + ' -d ' +
                fname_new_template_wm + '  -2d-slices 2',
                error_exit='warning')

        dice_name = 'dice_multilabel_reg.txt'
        dice_fic = open(dice_name, 'w')
        dice_fic.write(
            'The "diff" columns are comparisons between regular template registration and corrected template registration according to SC internal structure\n'
            'Diff = metric_corrected_reg - metric_regular_reg\n')
        dice_fic.write('#Slice, WM DC, WM diff, GM DC, GM diff\n')

        init_dc = '2D Dice coefficient by slice:\n'

        old_gm_dc = output_old_gm[output_old_gm.find(init_dc) +
                                  len(init_dc):].split('\n')
        old_wm_dc = output_old_wm[output_old_wm.find(init_dc) +
                                  len(init_dc):].split('\n')
        new_gm_dc = output_new_gm[output_new_gm.find(init_dc) +
                                  len(init_dc):].split('\n')
        new_wm_dc = output_new_wm[output_new_wm.find(init_dc) +
                                  len(init_dc):].split('\n')

        for i in range(len(old_gm_dc)):
            if i not in no_ref_slices:
                i_new_gm, val_new_gm = new_gm_dc[i].split(' ')
                i_new_wm, val_new_wm = new_wm_dc[i].split(' ')
                i_old_gm, val_old_gm = old_gm_dc[i].split(' ')
                i_old_wm, val_old_wm = old_wm_dc[i].split(' ')

                assert i == int(i_new_gm) == int(i_new_wm) == int(
                    i_old_gm
                ) == int(
                    i_old_wm
                ), 'ERROR: when comparing Dice coefficients, slice numbers differs.'

                dice_fic.write(
                    str(i) + ', ' + val_new_wm + ', ' +
                    str(float(val_new_wm) - float(val_old_wm)) + ', ' +
                    val_new_gm + ', ' +
                    str(float(val_new_gm) - float(val_old_gm)) + '\n')
            else:
                dice_fic.write(str(i) + ', NO MANUAL SEGMENTATION\n')
        dice_fic.close()
        os.chdir('..')

        sct.generate_output_file(tmp_dir + hd_name,
                                 self.param.output_folder + hd_name)
        sct.generate_output_file(tmp_dir + dice_name,
                                 self.param.output_folder + dice_name)

        if self.param.remove_tmp:
            sct.run('rm -rf ' + tmp_dir, error_exit='warning')
Ejemplo n.º 28
0
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):
        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

    def process(self, type_process):
        if type_process == 'cross':
            self.output_image = self.cross()
        elif type_process == 'plan':
            self.output_image = self.plan(self.cross_radius, 100, 5)
        elif type_process == 'plan_ref':
            self.output_image = self.plan_ref()
        elif type_process == 'increment':
            self.output_image = self.increment_z_inverse()
        elif type_process == 'disks':
            self.output_image = self.labelize_from_disks()
        elif type_process == 'MSE':
            self.MSE()
            self.fname_output = None
        elif type_process == 'remove':
            self.output_image = self.remove_label()
        elif type_process == 'remove-symm':
            self.output_image = self.remove_label(symmetry=True)
        elif type_process == 'centerline':
            self.extract_centerline()
        elif type_process == 'display-voxel':
            self.display_voxel()
            self.fname_output = None
        elif type_process == 'create':
            self.output_image = self.create_label()
        elif type_process == 'add':
            self.output_image = self.create_label(add=True)
        elif type_process == 'diff':
            self.diff()
            self.fname_output = None
        elif type_process == 'dist-inter':  # second argument is in pixel distance
            self.distance_interlabels(5)
            self.fname_output = None
        elif type_process == 'cubic-to-point':
            self.output_image = self.cubic_to_point()
        elif type_process == 'label-vertebrae':
            self.output_image = self.label_vertebrae(self.vertebral_levels)
        elif type_process == 'label-vertebrae-from-disks':
            self.output_image = self.label_vertebrae_from_disks(self.vertebral_levels)
        else:
            sct.printv('Error: The chosen process is not available.', 1, 'error')

        # save the output image as minimized integers
        if self.fname_output is not None:
            self.output_image.setFileName(self.fname_output)
            if type_process != 'plan_ref':
                self.output_image.save('minimize_int')
            else:
                self.output_image.save()

    @staticmethod
    def get_crosses_coordinates(coordinates_input, gapxy=15, image_ref=None, dilate=False):
        from msct_types import Coordinate

        # if reference image is provided (segmentation), we draw the cross perpendicular to the centerline
        if image_ref is not None:
            # smooth centerline
            from sct_straighten_spinalcord import smooth_centerline
            x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(self.image_ref, verbose=self.verbose)

        # compute crosses
        cross_coordinates = []
        for coord in coordinates_input:
            if image_ref is None:
                from sct_straighten_spinalcord import compute_cross
                cross_coordinates_temp = compute_cross(coord, gapxy)
            else:
                from sct_straighten_spinalcord import compute_cross_centerline
                from numpy import where
                index_z = where(z_centerline == coord.z)
                deriv = Coordinate([x_centerline_deriv[index_z][0], y_centerline_deriv[index_z][0], z_centerline_deriv[index_z][0], 0.0])
                cross_coordinates_temp = compute_cross_centerline(coord, deriv, gapxy)

            for i, coord_cross in enumerate(cross_coordinates_temp):
                coord_cross.value = coord.value * 10 + i + 1

            # dilate cross to 3x3x3
            if dilate:
                additional_coordinates = []
                for coord_temp in cross_coordinates_temp:
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y+1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y+1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y+1.0, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y-1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y-1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x, coord_temp.y-1.0, coord_temp.z-1.0, coord_temp.value]))

                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y+1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y+1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y+1.0, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y-1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y-1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x+1.0, coord_temp.y-1.0, coord_temp.z-1.0, coord_temp.value]))

                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y+1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y+1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y+1.0, coord_temp.z-1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y-1.0, coord_temp.z, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y-1.0, coord_temp.z+1.0, coord_temp.value]))
                    additional_coordinates.append(Coordinate([coord_temp.x-1.0, coord_temp.y-1.0, coord_temp.z-1.0, coord_temp.value]))

                cross_coordinates_temp.extend(additional_coordinates)

            cross_coordinates.extend(cross_coordinates_temp)

        cross_coordinates = sorted(cross_coordinates, key=lambda obj: obj.value)
        return cross_coordinates

    # JULIEN <<<<<<
    # OLD IMPLEMENTATION:
    # def cross(self):
    #     """
    #     create a cross.
    #     :return:
    #     """
    #     image_output = Image(self.image_input, self.verbose)
    #     nx, ny, nz, nt, px, py, pz, pt = Image(self.image_input.absolutepath).dim
    #
    #     coordinates_input = self.image_input.getNonZeroCoordinates()
    #     d = self.cross_radius  # cross radius in pixel
    #     dx = d / px  # cross radius in mm
    #     dy = d / py
    #
    #     # for all points with non-zeros neighbors, force the neighbors to 0
    #     for coord in coordinates_input:
    #         image_output.data[coord.x][coord.y][coord.z] = 0  # remove point on the center of the spinal cord
    #         image_output.data[coord.x][coord.y + dy][
    #             coord.z] = coord.value * 10 + 1  # add point at distance from center of spinal cord
    #         image_output.data[coord.x + dx][coord.y][coord.z] = coord.value * 10 + 2
    #         image_output.data[coord.x][coord.y - dy][coord.z] = coord.value * 10 + 3
    #         image_output.data[coord.x - dx][coord.y][coord.z] = coord.value * 10 + 4
    #
    #         # dilate cross to 3x3
    #         if self.dilate:
    #             image_output.data[coord.x - 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x][coord.y + dy - 1][coord.z] = \
    #                 image_output.data[coord.x + 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y + dy][coord.z] = \
    #                 image_output.data[coord.x + 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x][coord.y + dy + 1][coord.z] = \
    #                 image_output.data[coord.x - 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y + dy][coord.z] = \
    #                 image_output.data[coord.x][coord.y + dy][coord.z]
    #             image_output.data[coord.x + dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx][coord.y - 1][coord.z] = \
    #                 image_output.data[coord.x + dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx + 1][coord.y][coord.z] = \
    #                 image_output.data[coord.x + dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx][coord.y + 1][coord.z] = \
    #                 image_output.data[coord.x + dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx - 1][coord.y][coord.z] = \
    #                 image_output.data[coord.x + dx][coord.y][coord.z]
    #             image_output.data[coord.x - 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x][coord.y - dy - 1][coord.z] = \
    #                 image_output.data[coord.x + 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y - dy][coord.z] = \
    #                 image_output.data[coord.x + 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x][coord.y - dy + 1][coord.z] = \
    #                 image_output.data[coord.x - 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y - dy][coord.z] = \
    #                 image_output.data[coord.x][coord.y - dy][coord.z]
    #             image_output.data[coord.x - dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx][coord.y - 1][coord.z] = \
    #                 image_output.data[coord.x - dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx + 1][coord.y][coord.z] = \
    #                 image_output.data[coord.x - dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx][coord.y + 1][coord.z] = \
    #                 image_output.data[coord.x - dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx - 1][coord.y][coord.z] = \
    #                 image_output.data[coord.x - dx][coord.y][coord.z]
    #
    #     return image_output
    # >>>>>>>>>
    def cross(self):
        """
        create a cross.
        :return:
        """
        output_image = Image(self.image_input, self.verbose)
        nx, ny, nz, nt, px, py, pz, pt = Image(self.image_input.absolutepath).dim

        coordinates_input = self.image_input.getNonZeroCoordinates()
        d = self.cross_radius  # cross radius in pixel
        dx = d / px  # cross radius in mm
        dy = d / py

        # clean output_image
        output_image.data *= 0

        cross_coordinates = self.get_crosses_coordinates(coordinates_input, dx, self.image_ref, self.dilate)

        for coord in cross_coordinates:
            output_image.data[round(coord.x), round(coord.y), round(coord.z)] = coord.value

        return output_image
    # >>>

    def plan(self, width, offset=0, gap=1):
        """
        This function creates a plan of thickness="width" and changes its value with an offset and a gap between labels.
        """
        image_output = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[:,:,coord.z-width:coord.z+width] = offset + gap * coord.value

        return image_output

    def plan_ref(self):
        """
        This function generate a plan in the reference space for each label present in the input image
        """

        image_output = Image(self.image_ref, self.verbose)
        image_output.data *= 0

        image_input_neg = Image(self.image_input, self.verbose).copy()
        image_input_pos = Image(self.image_input, self.verbose).copy()
        image_input_neg.data *=0
        image_input_pos.data *=0
        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.changeType('float32')
        for coord in coordinates_input_neg:
            image_output.data[:, :, coord.z] = -coord.value #PB: takes the int value of coord.value
        for coord in coordinates_input_pos:
            image_output.data[:, :, coord.z] = coord.value

        return image_output

    def cubic_to_point(self):
        """
        This function calculates 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
        """
        from scipy import ndimage
        from numpy import array, mean

        # 0. Initialization of output image
        output_image = self.image_input.copy()
        output_image.data *= 0

        # 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.iteritems():
            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(round(center_of_mass.x)) + ", " + str(round(center_of_mass.y)) + ", " + str(round(center_of_mass.z)) + ")", verbose=self.verbose)
            output_image.data[round(center_of_mass.x), round(center_of_mass.y), round(center_of_mass.z)] = center_of_mass.value

        return output_image

    def increment_z_inverse(self):
        """
        This function increments all the labels present in the input image, inversely ordered by Z.
        Therefore, labels are incremented from top to bottom, assuming a RPI orientation
        Labels are assumed to be non-zero.
        """
        image_output = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = i + 1

        return image_output

    def labelize_from_disks(self):
        """
        This function creates 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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = coordinates_ref[j].value

        return image_output


    def label_vertebrae(self, levels_user=None):
        """
        Finds 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 == None:
            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[list_coordinates[i_label].x, list_coordinates[i_label].y, list_coordinates[i_label].z] = 0

        # list all labels
        return image_cubic2point

    def label_vertebrae_from_disks(self, levels_user):
        """
        Finds the center of mass of vertebral levels specified by the user.
        :param levels_user:
        :return:
        """
        image_cubic2point = self.cubic_to_point()
        # get list of coordinates for each label
        list_coordinates_disks = image_cubic2point.getNonZeroCoordinates(sorting='value')
        image_cubic2point.data *= 0
        # compute vertebral labels from disk labels
        list_coordinates_vertebrae = []
        for i_label in range(len(list_coordinates_disks)-1):
            list_coordinates_vertebrae.append((list_coordinates_disks[i_label] + list_coordinates_disks[i_label+1]) / 2.0)
        # loop across labels and remove those that are not listed by the user
        for i_label in range(len(list_coordinates_vertebrae)):
            # check if this level is NOT in levels_user
            if levels_user.count(int(list_coordinates_vertebrae[i_label].value)):
                image_cubic2point.data[int(list_coordinates_vertebrae[i_label].x), int(list_coordinates_vertebrae[i_label].y), int(list_coordinates_vertebrae[i_label].z)] = list_coordinates_vertebrae[i_label].value

        return image_cubic2point


    def symmetrizer(self, side='left'):
        """
        This function symmetrize the input image. One side of the image will be copied on the other side. We assume a
        RPI orientation.
        :param side: string 'left' or 'right'. Side that will be copied on the other side.
        :return:
        """
        image_output = Image(self.image_input, self.verbose)

        image_output[0:]

        """inspiration: (from atlas creation matlab script)
        temp_sum = temp_g + temp_d;
        temp_sum_flip = temp_sum(end:-1:1,:);
        temp_sym = (temp_sum + temp_sum_flip) / 2;

        temp_g(1:end / 2,:) = 0;
        temp_g(1 + end / 2:end,:) = temp_sym(1 + end / 2:end,:);
        temp_d(1:end / 2,:) = temp_sym(1:end / 2,:);
        temp_d(1 + end / 2:end,:) = 0;

        tractsHR
        {label_l}(:,:, num_slice_ref) = temp_g;
        tractsHR
        {label_r}(:,:, num_slice_ref) = temp_d;
        """

        return image_output

    def MSE(self, threshold_mse=0):
        """
        This function computes 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 round(coord.value) not in [round(coord_ref.value) for coord_ref in coordinates_ref]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if round(coord_ref.value) not in [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 round(coord_ref.value) == round(coord.value):
                    result += (coord_ref.z - coord.z) ** 2
                    break
        result = math.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            f = open(self.image_input.path + 'error_log_' + self.image_input.file_name + '.txt', 'w')
            f.write(
                'The labels error (MSE) between ' + self.image_input.file_name + ' and ' + self.image_ref.file_name + ' is: ' + str(
                    result))
            f.close()

        return result

    def create_label(self, add=False):
        """
        This function 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 not add:
            image_output.data *= 0

        # loop across labels
        for i, coord in enumerate(self.coordinates):
            # display info
            sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' +
                       str(coord.value), 1)
            image_output.data[coord.x, coord.y, coord.z] = coord.value

        return image_output

    @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 filter(lambda x: x.value == coord.value, coord_ref)]
            if symmetry:
                result_coord_ref = [coord for coord in coord_ref if filter(lambda x: x.value == coord.value, result_coord_input)]

        return result_coord_input, result_coord_ref

    def remove_label(self, symmetry=False):
        """
        This function compares 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 = Image(self.image_input, verbose=self.verbose)
        image_output.data *= 0  # put all voxels to 0

        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[coord.x, coord.y, coord.z] = int(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[coord.x, coord.y, coord.z] = int(round(coord.value))
            image_output_ref.setFileName(self.fname_output[1])
            image_output_ref.save('minimize_int')

            self.fname_output = self.fname_output[0]

        return image_output

    def extract_centerline(self):
        """
        This function write a text file with the coordinates of the centerline.
        The image is suppose to be RPI
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z')

        fo = open(self.fname_output, "wb")
        for coord in coordinates_input:
            line = (coord.x,coord.y, coord.z)
            fo.write("%i %i %i\n" % line)
        fo.close()

    def display_voxel(self):
        """
        This function displays 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='z')
        useful_notation = ''
        for coord in coordinates_input:
            print 'Position=(' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ') -- Value= ' + str(coord.value)
            if useful_notation != '':
                useful_notation = useful_notation + ':'
            useful_notation = useful_notation + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ',' + str(coord.value)
        print 'Useful notation:'
        print useful_notation
        return coordinates_input

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

        print "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:
                print coord.value

        print "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:
                print coord_ref.value

    def distance_interlabels(self, max_dist):
        """
        This function calculates 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 = math.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:
                print '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)
Ejemplo n.º 29
0
def interpolate_im_to_ref(im_input, im_input_sc, new_res=0.3, sq_size_size_mm=22.5, interpolation_mode=3):
    nx, ny, nz, nt, px, py, pz, pt = im_input.dim

    im_input_sc = im_input_sc.copy()
    im_input= im_input.copy()

    # keep only spacing and origin in qform to avoid rotation issues
    input_qform = im_input.hdr.get_qform()
    for i in range(4):
        for j in range(4):
            if i!=j and j!=3:
                input_qform[i, j] = 0

    im_input.hdr.set_qform(input_qform)
    im_input.hdr.set_sform(input_qform)
    im_input_sc.hdr = im_input.hdr

    sq_size = int(sq_size_size_mm/new_res)
    # create a reference image : square of ones
    im_ref = Image(np.ones((sq_size, sq_size, 1), dtype=np.int), dim=(sq_size, sq_size, 1, 0, new_res, new_res, pz, 0), orientation='RPI')

    # copy input qform matrix to reference image
    im_ref.hdr.set_qform(im_input.hdr.get_qform())
    im_ref.hdr.set_sform(im_input.hdr.get_sform())

    # set correct header to reference image
    im_ref.hdr.set_data_shape((sq_size, sq_size, 1))
    im_ref.hdr.set_zooms((new_res, new_res, pz))

    # save image to set orientation to RPI (not properly done at the creation of the image)
    fname_ref = 'im_ref.nii.gz'
    im_ref.setFileName(fname_ref)
    im_ref.save()
    im_ref = set_orientation(im_ref, 'RPI', fname_out=fname_ref)

    # set header origin to zero to get physical coordinates of the center of the square
    im_ref.hdr.as_analyze_map()['qoffset_x'] = 0
    im_ref.hdr.as_analyze_map()['qoffset_y'] = 0
    im_ref.hdr.as_analyze_map()['qoffset_z'] = 0
    im_ref.hdr.set_sform(im_ref.hdr.get_qform())
    im_ref.hdr.set_qform(im_ref.hdr.get_qform())
    [[x_square_center_phys, y_square_center_phys, z_square_center_phys]] = im_ref.transfo_pix2phys(coordi=[[int(sq_size / 2), int(sq_size / 2), 0]])

    list_interpolate_images = []
    # iterate on z dimension of input image
    for iz in range(nz):
        # copy reference image: one reference image per slice
        im_ref_slice_iz = im_ref.copy()

        # get center of mass of SC for slice iz
        x_seg, y_seg = (im_input_sc.data[:, :, iz] > 0).nonzero()
        x_center, y_center = np.mean(x_seg), np.mean(y_seg)
        [[x_center_phys, y_center_phys, z_center_phys]] = im_input_sc.transfo_pix2phys(coordi=[[x_center, y_center, iz]])

        # center reference image on SC for slice iz
        im_ref_slice_iz.hdr.as_analyze_map()['qoffset_x'] = x_center_phys - x_square_center_phys
        im_ref_slice_iz.hdr.as_analyze_map()['qoffset_y'] = y_center_phys - y_square_center_phys
        im_ref_slice_iz.hdr.as_analyze_map()['qoffset_z'] = z_center_phys
        im_ref_slice_iz.hdr.set_sform(im_ref_slice_iz.hdr.get_qform())
        im_ref_slice_iz.hdr.set_qform(im_ref_slice_iz.hdr.get_qform())

        # interpolate input image to reference image
        im_input_interpolate_iz = im_input.interpolate_from_image(im_ref_slice_iz, interpolation_mode=interpolation_mode, border='nearest')
        # reshape data to 2D if needed
        if len(im_input_interpolate_iz.data.shape) == 3:
            im_input_interpolate_iz.data = im_input_interpolate_iz.data.reshape(im_input_interpolate_iz.data.shape[:-1])
        # add slice to list
        list_interpolate_images.append(im_input_interpolate_iz)

    return list_interpolate_images
Ejemplo n.º 30
0
def vanderbilt_processing(data_path):
    """
    get a segmentation image of the spinal cord an of the graymatter from a three level mask

    :param data_path: path to the data

    :return:
    """
    im_ext = '.nii.gz'
    if data_path[-1] == '/':
        data_path = data_path[:-1]
    original_path = os.path.abspath('.')
    os.chdir(data_path)
    for subject_dir in os.listdir('.'):
        if os.path.isdir(subject_dir):
            os.chdir(subject_dir)
            sc_seg_list = []
            gm_seg_list = []
            im_list = []
            for file_name in os.listdir('.'):
                if 'seg' in file_name:
                    mask_im = Image(file_name)

                    sc_seg_im = mask_im.copy()
                    sc_seg_im.file_name = sct.extract_fname(file_name)[1][:-4] + '_manual_sc_seg'
                    sc_seg_im.ext = '.nii.gz'
                    sc_seg_im.data = (sc_seg_im.data > 0).astype(int)
                    sc_seg_im.save()
                    sc_seg_list.append(sc_seg_im.file_name + sc_seg_im.ext)

                    gm_seg_im = mask_im.copy()
                    gm_seg_im.file_name = sct.extract_fname(file_name)[1][:-4] + '_manual_gm_seg'
                    gm_seg_im.ext = '.nii.gz'
                    gm_seg_im.data = (gm_seg_im.data > 1).astype(int)
                    gm_seg_im.save()
                    gm_seg_list.append(gm_seg_im.file_name + gm_seg_im.ext)

                    im_list.append(file_name[:17] + im_ext)

            # merging the slice images into a 3D image
            cmd_merge = 'fslmerge -z '
            im_name = subject_dir + '_im.nii.gz'
            cmd_merge_im = cmd_merge + im_name
            gmseg_name = subject_dir + '_manual_gmseg.nii.gz'
            cmd_merge_gm_seg = cmd_merge + gmseg_name
            scseg_name = subject_dir + '_manual_scseg.nii.gz'
            cmd_merge_sc_seg = cmd_merge + scseg_name

            for im_i, gm_i, sc_i in zip(im_list, gm_seg_list, sc_seg_list):
                cmd_merge_im += ' ' + im_i
                cmd_merge_gm_seg += ' ' + gm_i
                cmd_merge_sc_seg += ' ' + sc_i

            sct.run(cmd_merge_im)
            sct.run(cmd_merge_gm_seg)
            sct.run(cmd_merge_sc_seg)

            label_slices = [im_slice.split('_')[-1][2:4] for im_slice in im_list]
            i_slice_to_level = {0: 6, 1: 6, 2: 6, 3: 6, 4: 6, 5: 5, 6: 5, 7: 5, 8: 5, 9: 5, 10: 4, 11: 4, 12: 4, 13: 4, 14: 4, 15: 3, 16: 3, 17: 3, 18: 3, 19: 3, 20: 3, 21: 2, 22: 2, 23: 2, 24: 2, 25: 1, 26: 1, 27: 1, 28: 1, 29: 1}

            level_dat = np.zeros((mask_im.data.shape[0], mask_im.data.shape[1], len(im_list)))
            for i, l_slice in enumerate(label_slices):
                i_slice = int(l_slice) - 1
                level_dat.T[:][:][i] = i_slice_to_level[i_slice]
            Image(param=level_dat, absolutepath=subject_dir + '_levels.nii.gz').save()

            # resampling
            resample_image(im_name)
            resample_image(gmseg_name, binary=True)
            resample_image(scseg_name, binary=True)

            # organizing data
            sct.run('mkdir original_data/')
            sct.run('mkdir extracted_data/')
            sct.run('mkdir 3d_data/')
            sct.run('mkdir 3d_resampled_data/')
            sct.run('mkdir dic_data/')

            for file_name in os.listdir('.'):
                if '_manual_gm_seg' in file_name and 'sl' in file_name:
                    sct.run('cp ' + file_name + ' dic_data/')
                    sct.run('cp ' + file_name[:-21] + '.nii.gz dic_data/')
            for file_name in os.listdir('.'):
                if 'sl' in file_name and 'manual' not in file_name:
                    sct.run('mv ' + file_name + ' original_data/')
                elif 'manual' in file_name and 'sl' in file_name:
                    sct.run('mv ' + file_name + ' extracted_data/')
                elif 'resampled.nii' in file_name:
                    sct.run('mv ' + file_name + ' 3d_resampled_data/')
                elif '_sl' not in file_name and not os.path.isdir(os.path.abspath('.') + '/' + file_name) or 'level' in file_name:
                    sct.run('mv ' + file_name + ' 3d_data/')
                elif not os.path.isdir(os.path.abspath('.') + '/' + file_name):
                    sct.run('mv ' + file_name + ' original_data/')

            os.chdir('..')
    os.chdir(original_path)
Ejemplo n.º 31
0
def normalize_intensity_template(dataset_info, fname_template_centerline=None, contrast='t1', verbose=1):
    """
    This function normalizes the intensity of the image inside the spinal cord
    :param fname_template: path to template image
    :param fname_template_centerline: path to template centerline (binary image or npz)
    :return:
    """

    path_data = dataset_info['path_data']
    list_subjects = dataset_info['subjects']
    path_template = dataset_info['path_template']

    average_intensity = []
    intensity_profiles = {}

    timer_profile = sct.Timer(len(list_subjects))
    timer_profile.start()

    # computing the intensity profile for each subject
    for subject_name in list_subjects:
        path_data_subject = path_data + subject_name + '/' + contrast + '/'
        if fname_template_centerline is None:
            fname_image = path_data_subject + contrast + '.nii.gz'
            fname_image_centerline = path_data_subject + contrast + dataset_info['suffix_centerline'] + '.nii.gz'
        else:
            fname_image = path_data_subject + contrast + '_straight.nii.gz'
            if fname_template_centerline.endswith('.npz'):
                fname_image_centerline = None
            else:
                fname_image_centerline = fname_template_centerline

        image = Image(fname_image)
        nx, ny, nz, nt, px, py, pz, pt = image.dim

        if fname_image_centerline is not None:
            # open centerline from template
            number_of_points_in_centerline = 4000
            x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(
                fname_image_centerline, algo_fitting='nurbs', verbose=0,
                nurbs_pts_number=number_of_points_in_centerline,
                all_slices=False, phys_coordinates=True, remove_outliers=True)
            centerline_template = Centerline(x_centerline_fit, y_centerline_fit, z_centerline,
                                             x_centerline_deriv, y_centerline_deriv, z_centerline_deriv)
        else:
            centerline_template = Centerline(fname=fname_template_centerline)

        x, y, z, xd, yd, zd = centerline_template.average_coordinates_over_slices(image)

        # Compute intensity values
        z_values, intensities = [], []
        extend = 1  # this means the mean intensity of the slice will be calculated over a 3x3 square
        for i in range(len(z)):
            coord_z = image.transfo_phys2pix([[x[i], y[i], z[i]]])[0]
            z_values.append(coord_z[2])
            intensities.append(np.mean(image.data[coord_z[0] - extend - 1:coord_z[0] + extend, coord_z[1] - extend - 1:coord_z[1] + extend, coord_z[2]]))

        # for the slices that are not in the image, extend min and max values to cover the whole image
        min_z, max_z = min(z_values), max(z_values)
        intensities_temp = copy(intensities)
        z_values_temp = copy(z_values)
        for cz in range(nz):
            if cz not in z_values:
                z_values_temp.append(cz)
                if cz < min_z:
                    intensities_temp.append(intensities[z_values.index(min_z)])
                elif cz > max_z:
                    intensities_temp.append(intensities[z_values.index(max_z)])
                else:
                    print 'error...', cz
        intensities = intensities_temp
        z_values = z_values_temp

        # Preparing data for smoothing
        arr_int = [[z_values[i], intensities[i]] for i in range(len(z_values))]
        arr_int.sort(key=lambda x: x[0])  # and make sure it is ordered with z

        def smooth(x, window_len=11, window='hanning'):
            """smooth the data using a window with requested size.
            """

            if x.ndim != 1:
                raise ValueError, "smooth only accepts 1 dimension arrays."

            if x.size < window_len:
                raise ValueError, "Input vector needs to be bigger than window size."

            if window_len < 3:
                return x

            if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
                raise ValueError, "Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'"

            s = np.r_[x[window_len - 1:0:-1], x, x[-2:-window_len - 1:-1]]
            if window == 'flat':  # moving average
                w = np.ones(window_len, 'd')
            else:
                w = eval('np.' + window + '(window_len)')

            y = np.convolve(w / w.sum(), s, mode='same')
            return y[window_len - 1:-window_len + 1]

        # Smoothing
        intensities = [c[1] for c in arr_int]
        intensity_profile_smooth = smooth(np.array(intensities), window_len=50)
        average_intensity.append(np.mean(intensity_profile_smooth))

        intensity_profiles[subject_name] = intensity_profile_smooth

        if verbose == 2:
            import matplotlib.pyplot as plt
            plt.figure()
            plt.title(subject_name)
            plt.plot(intensities)
            plt.plot(intensity_profile_smooth)
            plt.show()

    # set the average image intensity over the entire dataset
    average_intensity = 1000.0

    # normalize the intensity of the image based on spinal cord
    for subject_name in list_subjects:
        path_data_subject = path_data + subject_name + '/' + contrast + '/'
        fname_image = path_data_subject + contrast + '_straight.nii.gz'

        image = Image(fname_image)
        nx, ny, nz, nt, px, py, pz, pt = image.dim

        image_image_new = image.copy()
        image_image_new.changeType(type='float32')
        for i in range(nz):
            image_image_new.data[:, :, i] *= average_intensity / intensity_profiles[subject_name][i]

        # Save intensity normalized template
        fname_image_normalized = sct.add_suffix(fname_image, '_norm')
        image_image_new.setFileName(fname_image_normalized)
        image_image_new.save()
Ejemplo n.º 32
0
def amu_processing(data_path):
    """
    get a segmentation image of the spinal cord an of the graymatter from a three level mask

    :param data_path: path to the data

    :return:
    """
    im_ext = '.nii'
    if data_path[-1] == '/':
        data_path = data_path[:-1]
    original_path = os.path.abspath('.')
    os.chdir(data_path)
    for subject_dir in os.listdir('.'):
        subject_path = data_path + '/' + subject_dir
        if os.path.isdir(subject_dir):
            os.chdir(subject_dir)
            sc_seg_list = []
            gm_seg_list = []
            im_list = []
            for file_name in os.listdir('.'):
                ext = sct.extract_fname(file_name)[2]
                if 'mask' in file_name and ext != '.hdr':
                    mask_im = Image(file_name)

                    sc_seg_im = mask_im.copy()
                    sc_seg_im.file_name = sct.extract_fname(file_name)[1][:-5] + '_manual_sc_seg'
                    sc_seg_im.ext = '.nii.gz'
                    sc_seg_im.data = (sc_seg_im.data > 1).astype(int)
                    # sc_seg_im = Image(param=sc_seg, absolutepath=subject_path + '/' + sct.extract_fname(file_name)[1][:-5] + '_manual_sc_seg.nii.gz')
                    # sc_seg_im.orientation = 'RPI'
                    sc_seg_im.save()
                    sc_seg_list.append(sc_seg_im.file_name + sc_seg_im.ext)

                    gm_seg_im = mask_im.copy()
                    gm_seg_im.file_name = sct.extract_fname(file_name)[1][:-5] + '_manual_gm_seg'
                    gm_seg_im.ext = '.nii.gz'
                    gm_seg_im.data = (gm_seg_im.data > 2).astype(int)
                    # gm_seg_im = Image(param=gm_seg, absolutepath=subject_path + '/' + sct.extract_fname(file_name)[1][:-5] + '_manual_gm_seg.nii.gz')
                    # gm_seg_im.orientation = 'RPI'
                    gm_seg_im.save()
                    gm_seg_list.append(gm_seg_im.file_name + gm_seg_im.ext)

                    im_list.append(file_name[:2] + im_ext)

            # merging the slice images into a 3D image
            im_list.reverse()
            gm_seg_list.reverse()
            sc_seg_list.reverse()
            cmd_merge = 'fslmerge -z '
            im_name = subject_dir + '_im.nii.gz '
            cmd_merge_im = cmd_merge + im_name
            gmseg_name = subject_dir + '_manual_gmseg.nii.gz '
            cmd_merge_gm_seg = cmd_merge + gmseg_name
            scseg_name = subject_dir + '_manual_scseg.nii.gz '
            cmd_merge_sc_seg = cmd_merge + scseg_name

            for im_i, gm_i, sc_i in zip(im_list, gm_seg_list, sc_seg_list):
                cmd_merge_im += im_i + ' '
                cmd_merge_gm_seg += gm_i + ' '
                cmd_merge_sc_seg += sc_i + ' '

            sct.run(cmd_merge_im)
            sct.run(cmd_merge_gm_seg)
            sct.run(cmd_merge_sc_seg)

            # creating a level image
            level_label = {0: '', 1: 'C1', 2: 'C2', 3: 'C3', 4: 'C4', 5: 'C5', 6: 'C6', 7: 'C7', 8: 'T1', 9: 'T2', 10: 'T3', 11: 'T4', 12: 'T5', 13: 'T6'}
            level_dat = np.zeros((mask_im.data.shape[0], mask_im.data.shape[1], len(im_list)))
            for i, im_i_name in enumerate(im_list):
                level_dat.T[:][:][i] = get_key_from_val(level_label, im_i_name[:2].upper())
            Image(param=level_dat, absolutepath=subject_dir + '_levels.nii.gz').save()

            # resampling
            resample_image(im_name)
            resample_image(gmseg_name, binary=True, thr=0.45)
            resample_image(scseg_name, binary=True, thr=0.55)

            # organizing data
            sct.run('mkdir original_data/')
            sct.run('mkdir extracted_data/')
            sct.run('mkdir 3d_data/')
            sct.run('mkdir 3d_resampled_data/')

            for file_name in os.listdir('.'):
                if 'mask' in file_name:
                    sct.run('mv ' + file_name + ' original_data/')
                elif 'manual' in file_name and 'G1' not in file_name:
                    sct.run('mv ' + file_name + ' extracted_data/')
                elif 'resampled.nii' in file_name:
                    sct.run('mv ' + file_name + ' 3d_resampled_data/')
                elif 'G1' in file_name:
                    sct.run('mv ' + file_name + ' 3d_data/')
                elif not os.path.isdir(os.path.abspath('.') + '/' + file_name):
                    sct.run('mv ' + file_name + ' original_data/')

            os.chdir('..')
    os.chdir(original_path)
Ejemplo n.º 33
0
class Vesselness:
    def __init__(self, fname):
        self.image = Image(fname)

        self.pretreated_im = self.pretreat()

        self.result_im = self.image.copy()
        self.result_im.file_name = self.image.file_name + '_vessel_mask'
        self.result_im.path = './'

        self.hess = hessian(self.pretreated_im.data)
        self.vessel_mask = self.compute_vessel_mask()

        self.result_im.data = self.vessel_mask
        self.result_im.save()

    def pretreat(self):
        import scipy.ndimage.filters as scp_filters
        '''
        status, orientation = sct.run('sct_orientation.py -i ' + self.image.path + self.image.file_name + self.image.ext)
        orientation = orientation[4:7]
        if orientation != 'RPI':
            sct.run('sct_orientation.py -i ' + self.image.path + self.image.file_name + self.image.ext + ' -s RPI')
        fname = self.image.file_name[:-7] + '_RPI.nii.gz'
        '''
        fname = self.image.file_name

        pretreated = self.image.copy()
        pretreated.file_name = fname

        nx, ny, nz, nt, px, py, pz, pt = sct.get_dimension(pretreated.file_name)
        sc_size = 3  # in mm
        sc_npix = ((sc_size/px) + (sc_size/py))/2.0
        pretreated.data = scp_filters.gaussian_filter(pretreated.data, sigma=sc_npix)
        pretreated.file_name = pretreated.file_name + '_gaussian'
        pretreated.save()
        return pretreated


    def compute_vessel_mask(self):
        print 'COMPUTE VESSEL MASK'
        vessel_mask = np.zeros(self.pretreated_im.data.shape)
        print 'Image shape : ', str(self.pretreated_im.data.shape)
        for x in range(self.pretreated_im.data.shape[0]):
            print '----------> x =', x
            now_x = time.time()
            for y in range(self.pretreated_im.data.shape[1]):
                for z in range(self.pretreated_im.data.shape[2]):
                    # H = self.hess[x][y][z]
                    H = self.get_hessian(x, y, z)
                    vals, vects = np.linalg.eig(H)
                    l1, l2, l3 = sort_eigen_vals(vals)
                    vessel_mask[x][y][z] = line_filter(l1, l2, l3)
            print 'x ' + str(x) + ' --> in ' + str(time.time() - now_x) + ' sec'
        return vessel_mask

    def get_hessian(self, x, y, z):
        Hxx = self.hess[0][0][x][y][z]
        Hxy = self.hess[0][1][x][y][z]
        Hxz = self.hess[0][2][x][y][z]
        Hyx = self.hess[1][0][x][y][z]
        Hyy = self.hess[1][1][x][y][z]
        Hyz = self.hess[1][2][x][y][z]
        Hzx = self.hess[2][0][x][y][z]
        Hzy = self.hess[2][1][x][y][z]
        Hzz = self.hess[2][2][x][y][z]
        H = [[Hxx, Hxy, Hxz],
             [Hyx, Hyy, Hyz],
             [Hzx, Hzy, Hzz]]
        return H
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)
    im_seg = Image(fname_seg)
    # orient to RPI
    native_orient = im_seg.change_orientation('RPI')
    im_label.change_orientation('RPI')
    # smooth centerline and return fitted coordinates in voxel space
    centerline_x, centerline_y, centerline_z, centerline_derivx, centerline_derivy, centerline_derivz = smooth_centerline(
        im_seg,
        algo_fitting="hanning",
        type_window="hanning",
        window_length=50,
        nurbs_pts_number=3000,
        phys_coordinates=False,
        all_slices=True)
    # convert pixel into physical coordinates
    centerline_xyz_transposed = [
        im_seg.transfo_pix2phys(
            [[centerline_x[i], centerline_y[i], centerline_z[i]]])[0]
        for i in range(len(centerline_x))
    ]
    # transpose list
    centerline_phys_x, centerline_phys_y, centerline_phys_z = map(
        list, map(None, *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 = im_seg.copy()
    im_label_projected.data = np.zeros(im_label_projected.data.shape,
                                       dtype='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(centerline_x))
        ]
        # 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)  # note: native_orient refers to im_seg (not im_label)
    im_label_projected.setFileName(fname_label_projected)
    im_label_projected.save()
    return fname_label_projected
class ProcessLabels(object):
    def __init__(self, fname_label, fname_output=None, fname_ref=None, cross_radius=5, dilate=False,
                 coordinates=None, verbose='1'):
        self.image_input = Image(fname_label)

        if fname_ref is not None:
            self.image_ref = Image(fname_ref)

        self.fname_output = fname_output
        self.cross_radius = cross_radius
        self.dilate = dilate
        self.coordinates = coordinates
        self.verbose = verbose

    def process(self, type_process):
        if type_process == 'cross':
            self.output_image = self.cross()
        elif type_process == 'plan':
            self.output_image = self.plan(self.cross_radius, 100, 5)
        elif type_process == 'plan_ref':
            self.output_image = self.plan_ref()
        elif type_process == 'increment':
            self.output_image = self.increment_z_inverse()
        elif type_process == 'disks':
            self.output_image = self.labelize_from_disks()
        elif type_process == 'MSE':
            self.MSE()
            self.fname_output = None
        elif type_process == 'remove':
            self.output_image = self.remove_label()
        elif type_process == 'centerline':
            self.extract_centerline()
        elif type_process == 'display-voxel':
            self.display_voxel()
            self.fname_output = None
        elif type_process == 'create':
            self.output_image = self.create_label()
        elif type_process == 'add':
            self.output_image = self.create_label(add=True)
        elif type_process == 'diff':
            self.diff()
            self.fname_output = None
        elif type_process == 'dist-inter':  # second argument is in pixel distance
            self.distance_interlabels(5)
            self.fname_output = None
        elif type_process == 'cubic-to-point':
            self.output_image = self.cubic_to_point()
        else:
            sct.printv('Error: The chosen process is not available.',1,'error')

        # save the output image as minimized integers
        if self.fname_output is not None:
            self.output_image.setFileName(self.fname_output)
            self.output_image.save('minimize_int')


    def cross(self):
        image_output = Image(self.image_input)
        nx, ny, nz, nt, px, py, pz, pt = sct.get_dimension(self.image_input.absolutepath)

        coordinates_input = self.image_input.getNonZeroCoordinates()
        d = self.cross_radius  # cross radius in pixel
        dx = d / px  # cross radius in mm
        dy = d / py

        # for all points with non-zeros neighbors, force the neighbors to 0
        for coord in coordinates_input:
            image_output.data[coord.x][coord.y][coord.z] = 0  # remove point on the center of the spinal cord
            image_output.data[coord.x][coord.y + dy][
                coord.z] = coord.value * 10 + 1  # add point at distance from center of spinal cord
            image_output.data[coord.x + dx][coord.y][coord.z] = coord.value * 10 + 2
            image_output.data[coord.x][coord.y - dy][coord.z] = coord.value * 10 + 3
            image_output.data[coord.x - dx][coord.y][coord.z] = coord.value * 10 + 4

            # dilate cross to 3x3
            if self.dilate:
                image_output.data[coord.x - 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x][coord.y + dy - 1][coord.z] = \
                    image_output.data[coord.x + 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y + dy][coord.z] = \
                    image_output.data[coord.x + 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x][coord.y + dy + 1][coord.z] = \
                    image_output.data[coord.x - 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y + dy][coord.z] = \
                    image_output.data[coord.x][coord.y + dy][coord.z]
                image_output.data[coord.x + dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx][coord.y - 1][coord.z] = \
                    image_output.data[coord.x + dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx + 1][coord.y][coord.z] = \
                    image_output.data[coord.x + dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx][coord.y + 1][coord.z] = \
                    image_output.data[coord.x + dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx - 1][coord.y][coord.z] = \
                    image_output.data[coord.x + dx][coord.y][coord.z]
                image_output.data[coord.x - 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x][coord.y - dy - 1][coord.z] = \
                    image_output.data[coord.x + 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y - dy][coord.z] = \
                    image_output.data[coord.x + 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x][coord.y - dy + 1][coord.z] = \
                    image_output.data[coord.x - 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y - dy][coord.z] = \
                    image_output.data[coord.x][coord.y - dy][coord.z]
                image_output.data[coord.x - dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx][coord.y - 1][coord.z] = \
                    image_output.data[coord.x - dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx + 1][coord.y][coord.z] = \
                    image_output.data[coord.x - dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx][coord.y + 1][coord.z] = \
                    image_output.data[coord.x - dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx - 1][coord.y][coord.z] = \
                    image_output.data[coord.x - dx][coord.y][coord.z]

        return image_output

    def plan(self, width, offset=0, gap=1):
        """
        This function creates a plan of thickness="width" and changes its value with an offset and a gap between labels.
        """
        image_output = Image(self.image_input)
        image_output.data *= 0
        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[:,:,coord.z-width:coord.z+width] = offset + gap * coord.value

        return image_output

    def plan_ref(self):
        """
        This function generate a plan in the reference space for each label present in the input image
        """
        image_output = Image(self.image_ref)
        image_output.data *= 0
        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[:, :, coord.z] = coord.value

        return image_output
    def cubic_to_point(self):
        """
        This function calculates 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.
        It is to be used after applying homothetic warping field to a label file as the labels will be dilated.
        :return:
        """
        from scipy import ndimage
        from numpy import array
        data = self.image_input.data

        image_output = self.image_input.copy()
        data_output = image_output.data
        data_output *= 0
        nx = image_output.data.shape[0]
        ny = image_output.data.shape[1]
        nz = image_output.data.shape[2]
        print '.. matrix size: '+str(nx)+' x '+str(ny)+' x '+str(nz)

        z_centerline = [iz for iz in range(0, nz, 1) if data[:,:,iz].any() ]
        nz_nonz = len(z_centerline)
        if nz_nonz==0 :
            print '\nERROR: Label file is empty'
            sys.exit()
        x_centerline = [0 for iz in range(0, nz_nonz, 1)]
        y_centerline = [0 for iz in range(0, nz_nonz, 1)]
        print '\nGet center of mass for each slice of the label file ...'
        for iz in xrange(len(z_centerline)):
            x_centerline[iz], y_centerline[iz] = ndimage.measurements.center_of_mass(array(data[:,:,z_centerline[iz]]))

        ## Calculate mean coordinate according to z for each cube of labels:
        list_cube_labels_x = [[]]
        list_cube_labels_y = [[]]
        list_cube_labels_z = [[]]
        count = 0
        for i in range(nz_nonz-1):
            # Make a list of group of slices that contains a non zero value
            if z_centerline[i] - z_centerline[i+1] == -1:
                # Verify if the value has already been recovered and add if not
                #If the group is empty add first value do not if it is not empty as it will copy it for a second time
                if len(list_cube_labels_z[count]) == 0 :#or list_cube_labels[count][-1] != z_centerline[i]:
                    list_cube_labels_z[count].append(z_centerline[i])
                    list_cube_labels_x[count].append(x_centerline[i])
                    list_cube_labels_y[count].append(y_centerline[i])
                list_cube_labels_z[count].append(z_centerline[i+1])
                list_cube_labels_x[count].append(x_centerline[i+1])
                list_cube_labels_y[count].append(y_centerline[i+1])
                if i+2 < nz_nonz-1 and z_centerline[i+1] - z_centerline[i+2] != -1:
                    list_cube_labels_z.append([])
                    list_cube_labels_x.append([])
                    list_cube_labels_y.append([])
                    count += 1

        z_label_mean = [0 for i in range(len(list_cube_labels_z))]
        x_label_mean = [0 for i in range(len(list_cube_labels_z))]
        y_label_mean = [0 for i in range(len(list_cube_labels_z))]
        for i in range(len(list_cube_labels_z)):
            for j in range(len(list_cube_labels_z[i])):
                z_label_mean[i] += list_cube_labels_z[i][j]
                x_label_mean[i] += list_cube_labels_x[i][j]
                y_label_mean[i] += list_cube_labels_y[i][j]
            z_label_mean[i] = int(round(z_label_mean[i]/len(list_cube_labels_z[i])))
            x_label_mean[i] = int(round(x_label_mean[i]/len(list_cube_labels_x[i])))
            y_label_mean[i] = int(round(y_label_mean[i]/len(list_cube_labels_y[i])))


        ## Put labels of value one into mean coordinates
        for i in range(len(z_label_mean)):
            data_output[x_label_mean[i],y_label_mean[i], z_label_mean[i]] = 1

        return image_output

    def increment_z_inverse(self):
        """
        This function increments all the labels present in the input image, inversely ordered by Z.
        Therefore, labels are incremented from top to bottom, assuming a RPI orientation
        Labels are assumed to be non-zero.
        """
        image_output = Image(self.image_input)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = i + 1

        return image_output

    def labelize_from_disks(self):
        """
        This function creates an image with regions labelized depending on values from reference.
        Typically, user inputs an 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 = Image(self.image_input)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = coordinates_ref[j].value

        return image_output

    def symmetrizer(self, side='left'):
        """
        This function symmetrize the input image. One side of the image will be copied on the other side. We assume a
        RPI orientation.
        :param side: string 'left' or 'right'. Side that will be copied on the other side.
        :return:
        """
        image_output = Image(self.image_input)

        image_output[0:]

        """inspiration: (from atlas creation matlab script)
        temp_sum = temp_g + temp_d;
        temp_sum_flip = temp_sum(end:-1:1,:);
        temp_sym = (temp_sum + temp_sum_flip) / 2;

        temp_g(1:end / 2,:) = 0;
        temp_g(1 + end / 2:end,:) = temp_sym(1 + end / 2:end,:);
        temp_d(1:end / 2,:) = temp_sym(1:end / 2,:);
        temp_d(1 + end / 2:end,:) = 0;

        tractsHR
        {label_l}(:,:, num_slice_ref) = temp_g;
        tractsHR
        {label_r}(:,:, num_slice_ref) = temp_d;
        """

        return image_output

    def MSE(self, threshold_mse=0):
        """
        This function computes 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 round(coord.value) not in [round(coord_ref.value) for coord_ref in coordinates_ref]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if round(coord_ref.value) not in [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 round(coord_ref.value) == round(coord.value):
                    result += (coord_ref.z - coord.z) ** 2
                    break
        result = math.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            f = open(self.image_input.path + 'error_log_' + self.image_input.file_name + '.txt', 'w')
            f.write(
                'The labels error (MSE) between ' + self.image_input.file_name + ' and ' + self.image_ref.file_name + ' is: ' + str(
                    result))
            f.close()

        return result

    def create_label(self, add=False):
        """
        This function 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 not add:
            image_output.data *= 0

        # loop across labels
        for i, coord in enumerate(self.coordinates):
            # display info
            sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' +
                       str(coord.value), 1)
            image_output.data[coord.x, coord.y, coord.z] = int(coord.value)

        return image_output

    def remove_label(self):
        """
        This function compares two label images and remove any labels in input image that are not in reference image.
        """
        image_output = Image(self.image_input)
        coordinates_input = self.image_input.getNonZeroCoordinates()
        coordinates_ref = self.image_ref.getNonZeroCoordinates()

        for coord in coordinates_input:
            value = self.image_input.data[coord.x, coord.y, coord.z]
            isInRef = False
            for coord_ref in coordinates_ref:
                # the following line could make issues when down sampling input, for example 21,00001 not = 21,0
                if abs(coord.value - coord_ref.value) < 0.1:
                    image_output.data[coord.x, coord.y, coord.z] = int(round(coord_ref.value))
                    isInRef = True
            if isInRef == False:
                image_output.data[coord.x, coord.y, coord.z] = 0

        return image_output

    def extract_centerline(self):
        """
        This function write a text file with the coordinates of the centerline.
        The image is suppose to be RPI
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z')

        fo = open(self.fname_output, "wb")
        for coord in coordinates_input:
            line = (coord.x,coord.y, coord.z)
            fo.write("%i %i %i\n" % line)
        fo.close()

    def display_voxel(self):
        """
        This function displays 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='z')
        useful_notation = ''
        for coord in coordinates_input:
            print 'Position=(' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ') -- Value= ' + str(coord.value)
            if useful_notation != '':
                useful_notation = useful_notation + ':'
            useful_notation = useful_notation + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ',' + str(coord.value)
        print 'Useful notation:'
        print useful_notation

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

        print "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:
                print coord.value

        print "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:
                print coord_ref.value

    def distance_interlabels(self, max_dist):
        """
        This function calculates 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 = math.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:
                print '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)
Ejemplo n.º 36
0
class ProcessLabels(object):
    def __init__(self, fname_label, fname_output=None, fname_ref=None, cross_radius=5, dilate=False,
                 coordinates=None, verbose=1):
        self.image_input = Image(fname_label, verbose=verbose)

        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.dilate = dilate
        self.coordinates = coordinates
        self.verbose = verbose

    def process(self, type_process):
        if type_process == 'cross':
            self.output_image = self.cross()
        elif type_process == 'plan':
            self.output_image = self.plan(self.cross_radius, 100, 5)
        elif type_process == 'plan_ref':
            self.output_image = self.plan_ref()
        elif type_process == 'increment':
            self.output_image = self.increment_z_inverse()
        elif type_process == 'disks':
            self.output_image = self.labelize_from_disks()
        elif type_process == 'MSE':
            self.MSE()
            self.fname_output = None
        elif type_process == 'remove':
            self.output_image = self.remove_label()
        elif type_process == 'remove-symm':
            self.output_image = self.remove_label(symmetry=True)
        elif type_process == 'centerline':
            self.extract_centerline()
        elif type_process == 'display-voxel':
            self.display_voxel()
            self.fname_output = None
        elif type_process == 'create':
            self.output_image = self.create_label()
        elif type_process == 'add':
            self.output_image = self.create_label(add=True)
        elif type_process == 'diff':
            self.diff()
            self.fname_output = None
        elif type_process == 'dist-inter':  # second argument is in pixel distance
            self.distance_interlabels(5)
            self.fname_output = None
        elif type_process == 'cubic-to-point':
            self.output_image = self.cubic_to_point()
        else:
            sct.printv('Error: The chosen process is not available.',1,'error')

        # save the output image as minimized integers
        if self.fname_output is not None:
            self.output_image.setFileName(self.fname_output)
            if type_process != 'plan_ref':
                self.output_image.save('minimize_int')
            else:
                self.output_image.save()


    def cross(self):
        image_output = Image(self.image_input, self.verbose)
        nx, ny, nz, nt, px, py, pz, pt = Image(self.image_input.absolutepath).dim

        coordinates_input = self.image_input.getNonZeroCoordinates()
        d = self.cross_radius  # cross radius in pixel
        dx = d / px  # cross radius in mm
        dy = d / py

        # for all points with non-zeros neighbors, force the neighbors to 0
        for coord in coordinates_input:
            image_output.data[coord.x][coord.y][coord.z] = 0  # remove point on the center of the spinal cord
            image_output.data[coord.x][coord.y + dy][
                coord.z] = coord.value * 10 + 1  # add point at distance from center of spinal cord
            image_output.data[coord.x + dx][coord.y][coord.z] = coord.value * 10 + 2
            image_output.data[coord.x][coord.y - dy][coord.z] = coord.value * 10 + 3
            image_output.data[coord.x - dx][coord.y][coord.z] = coord.value * 10 + 4

            # dilate cross to 3x3
            if self.dilate:
                image_output.data[coord.x - 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x][coord.y + dy - 1][coord.z] = \
                    image_output.data[coord.x + 1][coord.y + dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y + dy][coord.z] = \
                    image_output.data[coord.x + 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x][coord.y + dy + 1][coord.z] = \
                    image_output.data[coord.x - 1][coord.y + dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y + dy][coord.z] = \
                    image_output.data[coord.x][coord.y + dy][coord.z]
                image_output.data[coord.x + dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx][coord.y - 1][coord.z] = \
                    image_output.data[coord.x + dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x + dx + 1][coord.y][coord.z] = \
                    image_output.data[coord.x + dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx][coord.y + 1][coord.z] = \
                    image_output.data[coord.x + dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x + dx - 1][coord.y][coord.z] = \
                    image_output.data[coord.x + dx][coord.y][coord.z]
                image_output.data[coord.x - 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x][coord.y - dy - 1][coord.z] = \
                    image_output.data[coord.x + 1][coord.y - dy - 1][coord.z] = image_output.data[coord.x + 1][coord.y - dy][coord.z] = \
                    image_output.data[coord.x + 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x][coord.y - dy + 1][coord.z] = \
                    image_output.data[coord.x - 1][coord.y - dy + 1][coord.z] = image_output.data[coord.x - 1][coord.y - dy][coord.z] = \
                    image_output.data[coord.x][coord.y - dy][coord.z]
                image_output.data[coord.x - dx - 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx][coord.y - 1][coord.z] = \
                    image_output.data[coord.x - dx + 1][coord.y - 1][coord.z] = image_output.data[coord.x - dx + 1][coord.y][coord.z] = \
                    image_output.data[coord.x - dx + 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx][coord.y + 1][coord.z] = \
                    image_output.data[coord.x - dx - 1][coord.y + 1][coord.z] = image_output.data[coord.x - dx - 1][coord.y][coord.z] = \
                    image_output.data[coord.x - dx][coord.y][coord.z]

        return image_output

    def plan(self, width, offset=0, gap=1):
        """
        This function creates a plan of thickness="width" and changes its value with an offset and a gap between labels.
        """
        image_output = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[:,:,coord.z-width:coord.z+width] = offset + gap * coord.value

        return image_output

    def plan_ref(self):
        """
        This function generate a plan in the reference space for each label present in the input image
        """

        image_output = Image(self.image_ref, self.verbose)
        image_output.data *= 0

        image_input_neg = Image(self.image_input, self.verbose).copy()
        image_input_pos = Image(self.image_input, self.verbose).copy()
        image_input_neg.data *=0
        image_input_pos.data *=0
        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.changeType('float32')
        for coord in coordinates_input_neg:
            image_output.data[:, :, coord.z] = -coord.value #PB: takes the int value of coord.value
        for coord in coordinates_input_pos:
            image_output.data[:, :, coord.z] = coord.value

        return image_output
    def cubic_to_point(self):
        """
        This function calculates 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.
        It is to be used after applying homothetic warping field to a label file as the labels will be dilated.
        :return:
        """
        from scipy import ndimage
        from numpy import array,mean
        data = self.image_input.data


        # pb: doesn't work if several groups have same value
        image_output = self.image_input.copy()
        data_output = image_output.data
        data_output *= 0
        coordinates = self.image_input.getNonZeroCoordinates(sorting='value')
        #list of present values
        list_values = []
        for i,coord in enumerate(coordinates):
            if i == 0 or coord.value != coordinates[i-1].value:
                list_values.append(coord.value)

        # make list of group of labels coordinates per value
        list_group_labels = []
        list_barycenter = []
        for i in range(0, len(list_values)):
            #mean_coord = mean(array([[coord.x, coord.y, coord.z] for coord in coordinates if coord.value==i]))
            list_group_labels.append([])
            list_group_labels[i] = [coordinates[j] for j in range(len(coordinates)) if coordinates[j].value == list_values[i]]
            # find barycenter: first define each case as a coordinate instance then calculate the value
            list_barycenter.append([0,0,0,0])
            sum_x = 0
            sum_y = 0
            sum_z = 0
            for j in range(len(list_group_labels[i])):
                sum_x += list_group_labels[i][j].x
                sum_y += list_group_labels[i][j].y
                sum_z += list_group_labels[i][j].z
            list_barycenter[i][0] = int(round(sum_x/len(list_group_labels[i])))
            list_barycenter[i][1] = int(round(sum_y/len(list_group_labels[i])))
            list_barycenter[i][2] = int(round(sum_z/len(list_group_labels[i])))
            list_barycenter[i][3] = list_group_labels[i][0].value

        # put value of group at each center of mass
        for i in range(len(list_values)):
            data_output[list_barycenter[i][0],list_barycenter[i][1], list_barycenter[i][2]] = list_barycenter[i][3]

        return image_output

        # Process to use if one wants to calculate the center of mass of a group of labels ordered by volume (and not by value)
        # image_output = self.image_input.copy()
        # data_output = image_output.data
        # data_output *= 0
        # nx = image_output.data.shape[0]
        # ny = image_output.data.shape[1]
        # nz = image_output.data.shape[2]
        # print '.. matrix size: '+str(nx)+' x '+str(ny)+' x '+str(nz)
        #
        # z_centerline = [iz for iz in range(0, nz, 1) if data[:,:,iz].any() ]
        # nz_nonz = len(z_centerline)
        # if nz_nonz==0 :
        #     print '\nERROR: Label file is empty'
        #     sys.exit()
        # x_centerline = [0 for iz in range(0, nz_nonz, 1)]
        # y_centerline = [0 for iz in range(0, nz_nonz, 1)]
        # print '\nGet center of mass for each slice of the label file ...'
        # for iz in xrange(len(z_centerline)):
        #     x_centerline[iz], y_centerline[iz] = ndimage.measurements.center_of_mass(array(data[:,:,z_centerline[iz]]))
        #
        # ## Calculate mean coordinate according to z for each cube of labels:
        # list_cube_labels_x = [[]]
        # list_cube_labels_y = [[]]
        # list_cube_labels_z = [[]]
        # count = 0
        # for i in range(nz_nonz-1):
        #     # Make a list of group of slices that contains a non zero value
        #     # check if the group is only one slice of height (at first slice)
        #     if i==0 and z_centerline[i] - z_centerline[i+1] != -1:
        #         list_cube_labels_z[count].append(z_centerline[i])
        #         list_cube_labels_x[count].append(x_centerline[i])
        #         list_cube_labels_y[count].append(y_centerline[i])
        #         list_cube_labels_z.append([])
        #         list_cube_labels_x.append([])
        #         list_cube_labels_y.append([])
        #         count += 1
        #     # check if the group is only one slice of height (in the middle)
        #     if i>0 and z_centerline[i-1] - z_centerline[i] != -1 and z_centerline[i] - z_centerline[i+1] != -1:
        #         list_cube_labels_z[count].append(z_centerline[i])
        #         list_cube_labels_x[count].append(x_centerline[i])
        #         list_cube_labels_y[count].append(y_centerline[i])
        #         list_cube_labels_z.append([])
        #         list_cube_labels_x.append([])
        #         list_cube_labels_y.append([])
        #         count += 1
        #     if z_centerline[i] - z_centerline[i+1] == -1:
        #         # Verify if the value has already been recovered and add if not
        #         #If the group is empty add first value do not if it is not empty as it will copy it for a second time
        #         if len(list_cube_labels_z[count]) == 0 :#or list_cube_labels[count][-1] != z_centerline[i]:
        #             list_cube_labels_z[count].append(z_centerline[i])
        #             list_cube_labels_x[count].append(x_centerline[i])
        #             list_cube_labels_y[count].append(y_centerline[i])
        #         list_cube_labels_z[count].append(z_centerline[i+1])
        #         list_cube_labels_x[count].append(x_centerline[i+1])
        #         list_cube_labels_y[count].append(y_centerline[i+1])
        #         if i+2 < nz_nonz-1 and z_centerline[i+1] - z_centerline[i+2] != -1:
        #             list_cube_labels_z.append([])
        #             list_cube_labels_x.append([])
        #             list_cube_labels_y.append([])
        #             count += 1
        #
        # z_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # x_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # y_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # v_label_mean = [0 for i in range(len(list_cube_labels_z))]
        # for i in range(len(list_cube_labels_z)):
        #     for j in range(len(list_cube_labels_z[i])):
        #         z_label_mean[i] += list_cube_labels_z[i][j]
        #         x_label_mean[i] += list_cube_labels_x[i][j]
        #         y_label_mean[i] += list_cube_labels_y[i][j]
        #     z_label_mean[i] = int(round(z_label_mean[i]/len(list_cube_labels_z[i])))
        #     x_label_mean[i] = int(round(x_label_mean[i]/len(list_cube_labels_x[i])))
        #     y_label_mean[i] = int(round(y_label_mean[i]/len(list_cube_labels_y[i])))
        #     # We suppose that the labels' value of the group is the value of the barycentre
        #     v_label_mean[i] = data[x_label_mean[i],y_label_mean[i], z_label_mean[i]]
        #
        # ## Put labels of value one into mean coordinates
        # for i in range(len(z_label_mean)):
        #     data_output[x_label_mean[i],y_label_mean[i], z_label_mean[i]] = v_label_mean[i]
        #
        # return image_output

    def increment_z_inverse(self):
        """
        This function increments all the labels present in the input image, inversely ordered by Z.
        Therefore, labels are incremented from top to bottom, assuming a RPI orientation
        Labels are assumed to be non-zero.
        """
        image_output = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = i + 1

        return image_output

    def labelize_from_disks(self):
        """
        This function creates an image with regions labelized depending on values from reference.
        Typically, user inputs an 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 = Image(self.image_input, self.verbose)
        image_output.data *= 0
        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[coord.x, coord.y, coord.z] = coordinates_ref[j].value

        return image_output

    def symmetrizer(self, side='left'):
        """
        This function symmetrize the input image. One side of the image will be copied on the other side. We assume a
        RPI orientation.
        :param side: string 'left' or 'right'. Side that will be copied on the other side.
        :return:
        """
        image_output = Image(self.image_input, self.verbose)

        image_output[0:]

        """inspiration: (from atlas creation matlab script)
        temp_sum = temp_g + temp_d;
        temp_sum_flip = temp_sum(end:-1:1,:);
        temp_sym = (temp_sum + temp_sum_flip) / 2;

        temp_g(1:end / 2,:) = 0;
        temp_g(1 + end / 2:end,:) = temp_sym(1 + end / 2:end,:);
        temp_d(1:end / 2,:) = temp_sym(1:end / 2,:);
        temp_d(1 + end / 2:end,:) = 0;

        tractsHR
        {label_l}(:,:, num_slice_ref) = temp_g;
        tractsHR
        {label_r}(:,:, num_slice_ref) = temp_d;
        """

        return image_output

    def MSE(self, threshold_mse=0):
        """
        This function computes 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 round(coord.value) not in [round(coord_ref.value) for coord_ref in coordinates_ref]:
                sct.printv('ERROR: labels mismatch', 1, 'warning')
        for coord_ref in coordinates_ref:
            if round(coord_ref.value) not in [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 round(coord_ref.value) == round(coord.value):
                    result += (coord_ref.z - coord.z) ** 2
                    break
        result = math.sqrt(result / len(coordinates_input))
        sct.printv('MSE error in Z direction = ' + str(result) + ' mm')

        if result > threshold_mse:
            f = open(self.image_input.path + 'error_log_' + self.image_input.file_name + '.txt', 'w')
            f.write(
                'The labels error (MSE) between ' + self.image_input.file_name + ' and ' + self.image_ref.file_name + ' is: ' + str(
                    result))
            f.close()

        return result

    def create_label(self, add=False):
        """
        This function 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 not add:
            image_output.data *= 0

        # loop across labels
        for i, coord in enumerate(self.coordinates):
            # display info
            sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' +
                       str(coord.value), 1)
            image_output.data[coord.x, coord.y, coord.z] = coord.value

        return image_output

    @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 filter(lambda x: x.value == coord.value, coord_ref)]
            if symmetry:
                result_coord_ref = [coord for coord in coord_ref if filter(lambda x: x.value == coord.value, result_coord_input)]

        return result_coord_input, result_coord_ref

    def remove_label(self, symmetry=False):
        """
        This function compares 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 = Image(self.image_input, verbose=self.verbose)
        image_output.data *= 0  # put all voxels to 0

        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[coord.x, coord.y, coord.z] = int(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[coord.x, coord.y, coord.z] = int(round(coord.value))
            image_output_ref.setFileName(self.fname_output[1])
            image_output_ref.save('minimize_int')

            self.fname_output = self.fname_output[0]

        return image_output

    def extract_centerline(self):
        """
        This function write a text file with the coordinates of the centerline.
        The image is suppose to be RPI
        """
        coordinates_input = self.image_input.getNonZeroCoordinates(sorting='z')

        fo = open(self.fname_output, "wb")
        for coord in coordinates_input:
            line = (coord.x,coord.y, coord.z)
            fo.write("%i %i %i\n" % line)
        fo.close()

    def display_voxel(self):
        """
        This function displays 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='z')
        useful_notation = ''
        for coord in coordinates_input:
            print 'Position=(' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ') -- Value= ' + str(coord.value)
            if useful_notation != '':
                useful_notation = useful_notation + ':'
            useful_notation = useful_notation + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ',' + str(coord.value)
        print 'Useful notation:'
        print useful_notation
        return coordinates_input

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

        print "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:
                print coord.value

        print "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:
                print coord_ref.value

    def distance_interlabels(self, max_dist):
        """
        This function calculates 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 = math.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:
                print '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)