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()
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
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
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
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()
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
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])
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()
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()
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
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")
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
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')
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')
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
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)
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)
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
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')
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)
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
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)
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()
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)
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)
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)