def concat_data(fname_in, fname_out, dim): """ Concatenate data :param fname_in: list of file names. :param fname_out: :param dim: dimension: 0, 1, 2, 3. :return: none """ # create empty list list_data = [] # loop across files for i in range(len(fname_in)): # append data to list list_data.append(Image(fname_in[i]).data) # expand dimension of all elements in the list if necessary if dim > list_data[0].ndim-1: list_data = [expand_dims(i, dim) for i in list_data] # concatenate try: data_concat = concatenate(list_data, axis=dim) except Exception as e: sct.printv('\nERROR: Concatenation on line {}'.format(sys.exc_info()[-1].tb_lineno)+'\n'+str(e)+'\n', 1, 'error') # write file im = Image(fname_in[0]) im.data = data_concat im.setFileName(fname_out) im.save()
def update(self, param_user): # list_objects = param_user.split(',') for object in param_user: if len(object) < 2: sct.printv('ERROR: Wrong usage.', 1, type='error') obj = object.split('=') setattr(self, obj[0], obj[1])
def generate_warping_field(fname_dest, warp_x, warp_y, fname_warp='warping_field.nii.gz', verbose=1): """ Generate an ITK warping field :param fname_dest: :param warp_x: :param warp_y: :param fname_warp: :param verbose: :return: """ sct.printv('\nGenerate warping field...', verbose) # Get image dimensions # sct.printv('Get destination dimension', verbose) nx, ny, nz, nt, px, py, pz, pt = Image(fname_dest).dim # sct.printv(' matrix size: '+str(nx)+' x '+str(ny)+' x '+str(nz), verbose) # sct.printv(' voxel size: '+str(px)+'mm x '+str(py)+'mm x '+str(pz)+'mm', verbose) # initialize data_warp = np.zeros((nx, ny, nz, 1, 3)) # fill matrix data_warp[:, :, :, 0, 0] = -warp_x # need to invert due to ITK conventions data_warp[:, :, :, 0, 1] = -warp_y # need to invert due to ITK conventions # save warping field im_dest = load(fname_dest) hdr_dest = im_dest.get_header() hdr_warp = hdr_dest.copy() hdr_warp.set_intent('vector', (), '') hdr_warp.set_data_dtype('float32') img = Nifti1Image(data_warp, None, hdr_warp) save(img, fname_warp) sct.printv(' --> '+fname_warp, verbose)
def getNonZeroCoordinates(self, sorting=None, reverse_coord=False, coordValue=False): """ This function return all the non-zero coordinates that the image contains. Coordinate list can also be sorted by x, y, z, or the value with the parameter sorting='x', sorting='y', sorting='z' or sorting='value' If reverse_coord is True, coordinate are sorted from larger to smaller. """ from msct_types import Coordinate from sct_utils import printv n_dim = 1 if self.dim[3] == 1: n_dim = 3 else: n_dim = 4 if self.dim[2] == 1: n_dim = 2 try: if n_dim == 3: X, Y, Z = (self.data > 0).nonzero() list_coordinates = [Coordinate([X[i], Y[i], Z[i], self.data[X[i], Y[i], Z[i]]]) for i in range(0, len(X))] elif n_dim == 2: X, Y = (self.data > 0).nonzero() list_coordinates = [Coordinate([X[i], Y[i], self.data[X[i], Y[i]]]) for i in range(0, len(X))] except Exception, e: print 'ERROR', e printv('ERROR: Exception ' + str(e) + ' caught while geting non Zeros coordinates', 1, 'error')
def printTotalTime(self): hours, rem = divmod(self.time_list[-1], 3600) minutes, seconds = divmod(rem, 60) if self.is_started: sct.printv('Remaining time: {:0>2}:{:0>2}:{:05.2f}'.format(int(hours), int(minutes), seconds)) else: sct.printv('Total time: {:0>2}:{:0>2}:{:05.2f}'.format(int(hours), int(minutes), seconds))
def cubic_to_point(self): """ Calculate the center of mass of each group of labels and returns a file of same size with only a label by group at the center of mass of this group. It is to be used after applying homothetic warping field to a label file as the labels will be dilated. Be careful: this algorithm computes the center of mass of voxels with same value, if two groups of voxels with the same value are present but separated in space, this algorithm will compute the center of mass of the two groups together. :return: image_output """ # 0. Initialization of output image output_image = msct_image.zeros_like(self.image_input) # 1. Extraction of coordinates from all non-null voxels in the image. Coordinates are sorted by value. coordinates = self.image_input.getNonZeroCoordinates(sorting='value') # 2. Separate all coordinates into groups by value groups = dict() for coord in coordinates: if coord.value in groups: groups[coord.value].append(coord) else: groups[coord.value] = [coord] # 3. Compute the center of mass of each group of voxels and write them into the output image for value, list_coord in groups.items(): center_of_mass = sum(list_coord) / float(len(list_coord)) sct.printv("Value = " + str(center_of_mass.value) + " : (" + str(center_of_mass.x) + ", " + str(center_of_mass.y) + ", " + str(center_of_mass.z) + ") --> ( " + str(np.round(center_of_mass.x)) + ", " + str(np.round(center_of_mass.y)) + ", " + str(np.round(center_of_mass.z)) + ")", verbose=self.verbose) output_image.data[int(np.round(center_of_mass.x)), int(np.round(center_of_mass.y)), int(np.round(center_of_mass.z))] = center_of_mass.value return output_image
def remove_or_keep_labels(self, labels, action): """ Create or remove labels from self.image_input :param list(int): Labels to keep or remove :param str: 'remove': remove specified labels (i.e. set to zero), 'keep': keep specified labels and remove the others """ if action == 'keep': image_output = msct_image.zeros_like(self.image_input) elif action == 'remove': image_output = self.image_input.copy() coordinates_input = self.image_input.getNonZeroCoordinates() for labelNumber in labels: isInLabels = False for coord in coordinates_input: if labelNumber == coord.value: new_coord = coord isInLabels = True if isInLabels: if action == 'keep': image_output.data[int(new_coord.x), int(new_coord.y), int(new_coord.z)] = new_coord.value elif action == 'remove': image_output.data[int(new_coord.x), int(new_coord.y), int(new_coord.z)] = 0.0 else: sct.printv("WARNING: Label " + str(float(labelNumber)) + " not found in input image.", type='warning') return image_output
def load_level(list_slices_target, fname_level): verbose = 1 path_level, file_level, ext_level = extract_fname(fname_level) # ####### Check if the level file is an image or a text file # Level file is an image if ext_level in ['.nii', '.nii.gz']: im_level = Image(fname_level) im_level.change_orientation('IRP') list_level = [] list_med_level = [] for slice_level in im_level.data: try: # vertebral level of the slice l = np.mean(slice_level[slice_level > 0]) # median of the vertebral level of the slice: if all voxels are int, med will be an int. med = np.median(slice_level[slice_level > 0]) # change med in int if it is an int med = int(med) if int(med)==med else med except Exception, e: printv('WARNING: ' + str(e) + '\nNo level label found. Level will be set to 0 for this slice', verbose, 'warning') l = 0 med = 0 list_level.append(l) list_med_level.append(med) # if all median of level are int for all slices : consider level as int if all([isinstance(med, int) for med in list_med_level]): # level as int are placed in the middle of each vertebra (that's why there is a "+0.5") list_level = [int(round(l))+0.5 for l in list_level]
def create_label(self, add=False): """ Create an image with labels listed by the user. This method works only if the user inserted correct coordinates. self.coordinates is a list of coordinates (class in msct_types). a Coordinate contains x, y, z and value. If only one label is to be added, coordinates must be completed with '[]' examples: For one label: object_define=ProcessLabels( fname_label, coordinates=[coordi]) where coordi is a 'Coordinate' object from msct_types For two labels: object_define=ProcessLabels( fname_label, coordinates=[coordi1, coordi2]) where coordi1 and coordi2 are 'Coordinate' objects from msct_types """ image_output = self.image_input.copy() if add else msct_image.zeros_like(self.image_input) # loop across labels for i, coord in enumerate(self.coordinates): if len(image_output.data.shape) == 3: image_output.data[int(coord.x), int(coord.y), int(coord.z)] = coord.value elif len(image_output.data.shape) == 2: assert str(coord.z) == '0', "ERROR: 2D coordinates should have a Z value of 0. Z coordinate is :" + str(coord.z) image_output.data[int(coord.x), int(coord.y)] = coord.value else: sct.printv('ERROR: Data should be 2D or 3D. Current shape is: ' + str(image_output.data.shape), 1, 'error') # display info sct.printv('Label #' + str(i) + ': ' + str(coord.x) + ',' + str(coord.y) + ',' + str(coord.z) + ' --> ' + str(coord.value), 1) return image_output
def combine_matrix(param): # required fields # param.mat_2_combine # param.mat_final # param.verbose sct.printv("\nCombine matrices...", param.verbose) # list all mat files in source mat folder m2c_fnames = [ fname for fname in os.listdir(param.mat_2_combine) if os.path.isfile(os.path.join(param.mat_2_combine, fname)) ] # loop across files for fname in m2c_fnames: if os.path.isfile(os.path.join(param.mat_final, fname)): # read source matrix file = open(os.path.join(param.mat_2_combine, fname)) Matrix_m2c = np.loadtxt(file) file.close() # read destination matrix file = open(os.path.join(param.mat_final, fname)) Matrix_f = np.loadtxt(file) file.close() # initialize final matrix Matrix_final = np.identity(4) # multiplies rotation matrix (3x3) Matrix_final[0:3, 0:3] = Matrix_f[0:3, 0:3] * Matrix_m2c[0:3, 0:3] # add translations matrix (3x1) Matrix_final[0, 3] = Matrix_f[0, 3] + Matrix_m2c[0, 3] Matrix_final[1, 3] = Matrix_f[1, 3] + Matrix_m2c[1, 3] Matrix_final[2, 3] = Matrix_f[2, 3] + Matrix_m2c[2, 3] # write final matrix (overwrite destination) file = open(os.path.join(param.mat_final, fname), "w") np.savetxt(os.path.join(param.mat_final, fname), Matrix_final, fmt="%s", delimiter=" ", newline="\n") file.close()
def select_kept_modes(self, modes_to_ignore=0): """ select the modes to keep according the percentage of variability to keep (self.k) :param modes_to_ignore: if not 0 the specified number of first modes will be ignored :return kept_modes, kept_eigenvalues: list of the kept modes vectors and list of the kept eigenvalues """ kept_eigenvalues = [] s = sum([eig[0] for eig in self.eig_pairs]) sct.printv('\n ---> sum of eigenvalues : ' + str(s), self.verbose, 'normal') first = 1 start = modes_to_ignore kept_modes = [] for eig in self.eig_pairs[start:]: if first: kept_modes = np.asarray(eig[1]).reshape(self.N, 1) kept_eigenvalues.append(eig[0]) first = 0 else: if (sum(kept_eigenvalues) + eig[0])/s <= self.k: kept_eigenvalues.append(eig[0]) kept_modes = np.hstack((kept_modes, np.asarray(eig[1]).reshape(self.N, 1))) else: break sct.printv('kept eigenvalues (PCA space dimension) : ' + str(len(kept_eigenvalues)), self.verbose, 'normal') return kept_modes, kept_eigenvalues
def segmentation_pipeline(self): sct.printv('\nDoing target pre-processing ...', verbose=self.seg_param.verbose, type='normal') self.preprocessed = Preprocessing(self.target_fname, self.sc_seg_fname, tmp_dir=self.tmp_dir, t2_data=self.t2_data, level_fname=self.level_fname, denoising=self.seg_param.target_denoising) self.preprocessed.process() os.chdir(self.tmp_dir) if self.preprocessed.fname_level is not None: self.level_to_use = self.preprocessed.fname_level else: self.level_to_use = None sct.printv('\nDoing target gray matter segmentation ...', verbose=self.seg_param.verbose, type='normal') self.gm_seg = SupervisedSegmentationMethod(self.preprocessed.processed_target, self.level_to_use, self.model, gm_seg_param=self.seg_param) sct.printv('\nDoing result post-processing ...', verbose=self.seg_param.verbose, type='normal') self.post_processing() if self.ref_gm_seg_fname is not None: os.chdir('..') ref_gmseg = 'ref_gmseg.nii.gz' sct.run('cp ' + self.ref_gm_seg_fname + ' ' + self.tmp_dir + '/' + ref_gmseg) os.chdir(self.tmp_dir) sct.printv('Computing Dice coefficient and Hausdorff distance ...', verbose=self.seg_param.verbose, type='normal') self.dice_name, self.hausdorff_name = self.validation(ref_gmseg) if compute_ratio: sct.printv('\nComputing ratio GM/WM ...', verbose=self.seg_param.verbose, type='normal') self.ratio_name = self.compute_ratio(type=compute_ratio) os.chdir('..')
def concat_data(im_in_list, dim): """ Concatenate data :param im_in_list: list of images. :param dim: dimension: 0, 1, 2, 3. :return im_out: concatenated image """ # WARNING: calling concat_data in python instead of in command line causes a non understood issue (results are different with both options) from numpy import concatenate, expand_dims data_list = [im.data for im in im_in_list] # expand dimension of all elements in the list if necessary if dim > im_in_list[0].data.ndim-1: data_list = [expand_dims(dat, dim) for dat in data_list] # concatenate try: data_concat = concatenate(data_list, axis=dim) except Exception as e: printv('\nERROR: Concatenation on line {}'.format(sys.exc_info()[-1].tb_lineno)+'\n'+str(e)+'\n', 1, 'error') data_concat = None # write file im_out = im_in_list[0].copy() im_out.data = data_concat im_out.setFileName(im_out.file_name+'_concat'+im_out.ext) return im_out
def checkIfNifti(self, param): import os sct.printv("Check file existence...", 0) nii = False niigz = False param_tmp = str() if param.lower().endswith('.nii'): nii = os.path.isfile(param) niigz = os.path.isfile(param+'.gz') param_tmp = param[:-4] pass elif param.lower().endswith('.nii.gz'): niigz = os.path.isfile(param) nii = os.path.isfile(param[:-3]) param_tmp = param[:-7] pass else: sct.printv("ERROR : File is not a NIFTI image file. Exiting", type='error') if nii: return param_tmp+'.nii' elif niigz: return param_tmp+'.nii.gz' if nii and niigz: return param_tmp+'.nii.gz'
def update(self, paramreg_user): list_objects = paramreg_user.split(',') for object in list_objects: if len(object) < 2: sct.printv('Please check parameter -param (usage changed from previous version)', 1, type='error') obj = object.split('=') setattr(self, obj[0], obj[1])
def error(self, error=None): if error: self.generate(error) else: sct.printv(self.generate()) from sys import exit exit(0)
def main(args=None): if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_in = arguments['-bvec'] if '-o' in arguments: fname_out = arguments['-o'] else: fname_out = '' verbose = int(arguments['-v']) # get bvecs in proper orientation from dipy.io import read_bvals_bvecs bvals, bvecs = read_bvals_bvecs(None, fname_in) # # Transpose bvecs # printv('Transpose bvecs...', verbose) # # from numpy import transpose # bvecs = bvecs.transpose() # Write new file if fname_out == '': path_in, file_in, ext_in = extract_fname(fname_in) fname_out = path_in+file_in+ext_in fid = open(fname_out, 'w') for iLine in range(bvecs.shape[0]): fid.write(' '.join(str(i) for i in bvecs[iLine, :])+'\n') fid.close() # display message printv('Created file:\n--> '+fname_out+'\n', verbose, 'info')
def main(args=None): if args is None: args = sys.argv[1:] # Get parser parser = get_parser() arguments = parser.parse(args) # Set param arguments ad inputted by user fname_in = arguments["-i"] contrast = arguments["-c"] # Segmentation or Centerline line if '-s' in arguments: fname_seg = arguments['-s'] if not os.path.isfile(fname_seg): fname_seg = None sct.printv('WARNING: -s input file: "' + arguments['-s'] + '" does not exist.\nDetecting PMJ without using segmentation information', 1, 'warning') else: fname_seg = None # Output Folder if '-ofolder' in arguments: path_results = arguments["-ofolder"] if not os.path.isdir(path_results) and os.path.exists(path_results): sct.printv("ERROR output directory %s is not a valid directory" % path_results, 1, 'error') if not os.path.exists(path_results): os.makedirs(path_results) else: path_results = '.' path_qc = arguments.get("-qc", None) # Remove temp folder rm_tmp = bool(int(arguments.get("-r", 1))) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level # Initialize DetectPMJ detector = DetectPMJ(fname_im=fname_in, contrast=contrast, fname_seg=fname_seg, path_out=path_results, verbose=verbose) # run the extraction fname_out, tmp_dir = detector.apply() # Remove tmp_dir if rm_tmp: sct.rmtree(tmp_dir) # View results if fname_out is not None: if path_qc is not None: generate_qc(fname_in, fname_seg=fname_out, args=args, path_qc=os.path.abspath(path_qc), process='sct_detect_pmj') sct.display_viewer_syntax([fname_in, fname_out], colormaps=['gray', 'red'])
def checkIfNifti(self, param): import os sct.printv("Check file existence...", 0) nii = False niigz = False no_image = False param_tmp = str() if param.lower().endswith('.nii'): if self.check_file_exist and self.parser.check_file_exist: nii = os.path.isfile(param) niigz = os.path.isfile(param + '.gz') else: nii, niigz = True, False param_tmp = param[:-4] elif param.lower().endswith('.nii.gz'): if self.check_file_exist and self.parser.check_file_exist: niigz = os.path.isfile(param) nii = os.path.isfile(param[:-3]) else: nii, niigz = False, True param_tmp = param[:-7] elif param.lower() in self.list_no_image: no_image = True else: self.parser.usage.error("Option " + self.name + " file " + param + " is not a NIFTI image file. Exiting") if nii: return param_tmp + '.nii' elif niigz: return param_tmp + '.nii.gz' elif no_image: return param else: logger.debug('executed in {}'.format(os.getcwd())) self.parser.usage.error("Option " + self.name + " file " + param + " does not exist.")
def add_path_to_file(self, dictionary, path_to_add, input_file=True, output_file=False, do_not_add_path=[]): """ This function add a path in front of each value in a dictionary (provided by the parser) for option that are files or folders. This function can affect option files that represent input and/or output with "input_file" and output_file" parameters. The parameter path_to_add must contain the character "/" at its end. Output is the same dictionary as provided but modified with added path. :param dictionary: :param path_to_add: :param input_file: :param output_file: :param do_not_add_path: list of keys for which path should NOT be added. :return: """ for key, option in dictionary.iteritems(): # Check if option is present in this parser if key in self.options: # if key is listed in the do_not_add_path variable, do nothing if not key in do_not_add_path: # If input file is a list, we need to check what type of list it is. # If it contains files, it must be updated. if (input_file and self.options[key].type_value in Option.OPTION_PATH_INPUT) or (output_file and self.options[key].type_value in Option.OPTION_PATH_OUTPUT): if isinstance(self.options[key].type_value, list): for i, value in enumerate(option): option[i] = path_to_add + value dictionary[key] = option else: dictionary[key] = str(path_to_add) + str(option) else: sct.printv("ERROR: the option you provided is not contained in this parser. Please check the dictionary", verbose=1, type='error') return dictionary
def get_dimension(im_file, verbose=1): """ Get dimension from nibabel object. Manages 2D, 3D or 4D images. :return: nx, ny, nz, nt, px, py, pz, pt """ import nibabel.nifti1 # initialization nx, ny, nz, nt, px, py, pz, pt = 1, 1, 1, 1, 1, 1, 1, 1 if type(im_file) is nibabel.nifti1.Nifti1Image: header = im_file.header elif type(im_file) is Image: header = im_file.hdr else: header = None sct.printv('WARNING: the provided image file isn\'t a nibabel.nifti1.Nifti1Image instance nor a msct_image.Image instance', verbose, 'warning') nb_dims = len(header.get_data_shape()) if nb_dims == 2: nx, ny = header.get_data_shape() px, py = header.get_zooms() if nb_dims == 3: nx, ny, nz = header.get_data_shape() px, py, pz = header.get_zooms() if nb_dims == 4: nx, ny, nz, nt = header.get_data_shape() px, py, pz, pt = header.get_zooms() return nx, ny, nz, nt, px, py, pz, pt
def save(self, type=""): """ Write an image in a nifti file :param type: if not set, the image is saved in standard type if 'minimize', image space is minimize (2, 'uint8', np.uint8, "NIFTI_TYPE_UINT8"), (4, 'int16', np.int16, "NIFTI_TYPE_INT16"), (8, 'int32', np.int32, "NIFTI_TYPE_INT32"), (16, 'float32', np.float32, "NIFTI_TYPE_FLOAT32"), (32, 'complex64', np.complex64, "NIFTI_TYPE_COMPLEX64"), (64, 'float64', np.float64, "NIFTI_TYPE_FLOAT64"), (256, 'int8', np.int8, "NIFTI_TYPE_INT8"), (512, 'uint16', np.uint16, "NIFTI_TYPE_UINT16"), (768, 'uint32', np.uint32, "NIFTI_TYPE_UINT32"), (1024,'int64', np.int64, "NIFTI_TYPE_INT64"), (1280, 'uint64', np.uint64, "NIFTI_TYPE_UINT64"), (1536, 'float128', _float128t, "NIFTI_TYPE_FLOAT128"), (1792, 'complex128', np.complex128, "NIFTI_TYPE_COMPLEX128"), (2048, 'complex256', _complex256t, "NIFTI_TYPE_COMPLEX256"), """ from nibabel import Nifti1Image, save from sct_utils import printv if type != "": self.changeType(type) self.hdr.set_data_shape(self.data.shape) img = Nifti1Image(self.data, None, self.hdr) printv("saving " + self.path + self.file_name + self.ext + "\n", verbose=self.verbose, type="normal") save(img, self.path + self.file_name + self.ext)
def warp_label(path_label, folder_label, file_label, fname_src, fname_transfo, path_out): """ Warp label files according to info_label.txt file :param path_label: :param folder_label: :param file_label: :param fname_src: :param fname_transfo: :param path_out: :return: """ # read label file and check if file exists sct.printv('\nRead label file...', param.verbose) try: template_label_ids, template_label_names, template_label_file, combined_labels_ids, combined_labels_names, combined_labels_id_groups, clusters_apriori = read_label_file(path_label + folder_label, file_label) except Exception as error: sct.printv('\nWARNING: Cannot warp label '+folder_label+': '+str(error), 1, 'warning') # raise # try: # template_label_ids, template_label_names, template_label_file, combined_labels_ids, combined_labels_names, combined_labels_id_groups = read_label_file(path_label+folder_label, file_label) # except Exception: # import traceback # sct.printv('\nERROR: ' + traceback.format_exc(), 1, 'error') else: # create output folder sct.run('mkdir '+path_out+folder_label, param.verbose) # Warp label for i in xrange(0, len(template_label_file)): fname_label = path_label+folder_label+template_label_file[i] # check if file exists # sct.check_file_exist(fname_label) # apply transfo sct.run('sct_apply_transfo -i '+fname_label+' -o '+path_out+folder_label+template_label_file[i] +' -d '+fname_src+' -w '+fname_transfo+' -x '+get_interp(template_label_file[i]), param.verbose) # Copy list.txt sct.run('cp '+path_label+folder_label+param.file_info_label+' '+path_out+folder_label, 0)
def do_loocv(directory): original_path = os.path.abspath('.') os.chdir(directory) dic_3d = None dic_by_slice = None for dir_name in os.listdir('.'): if os.path.isdir(dir_name): if '3d' in dir_name.lower(): dic_3d = dir_name if 'by_slice' in dir_name.lower(): dic_by_slice = dir_name if dic_3d is None or dic_by_slice is None: sct.printv('WARNING: dictionaries not in the loocv folder ...', 1, 'warning') else: denoising = factors_levels['denoising'][exp_plan[directory][factors['denoising']]] reg = factors_levels['reg'][exp_plan[directory][factors['reg']]] metric = factors_levels['metric'][exp_plan[directory][factors['metric']]] gamma = factors_levels['gamma'][exp_plan[directory][factors['gamma']]] eq = factors_levels['eq'][exp_plan[directory][factors['eq']]] mode_weight = factors_levels['mode_weight'][exp_plan[directory][factors['mode_weight']]] w_label_fus = factors_levels['weighted_label_fusion'][exp_plan[directory][factors['weighted_label_fusion']]] leave_one_out_by_subject(dic_by_slice, dic_3d, denoising=denoising, reg=reg, metric=metric, use_levels=bool(gamma), weight=gamma, eq=eq, mode_weighted_sim=mode_weight, weighted_label_fusion=w_label_fus) os.chdir(original_path)
def Metric_Images(imageA, imageB, type=''): data_A_list = load(imageA).get_data().tolist() data_B_list = load(imageB).get_data().tolist() # Define both list of intensity list_A = [] list_B = [] for i in range(len(data_A_list)): list_A = list_A + data_A_list[i] list_B = list_B + data_B_list[i] # Calculate metric depending on the type if type == 'MeanSquares': result_metric = 1.0 / (len(list_A)) * np.sum(np.array([list_A[i][0] - list_B[i][0] for i in range(len(list_A))])**2) #result_metric = 1/(len(list_A)) * np.sum(np.array(list_A - list_B)**2) if type == 'Correlation': result_metric = 1.0 / (len(list_A)) * np.sum(np.absolute(np.array([list_A[i][0] - list_B[i][0] for i in range(len(list_A))]))) if type == 'MI': sct.printv('\nto do: MI') # Return results sct.printv('\nResult of metric is: ' + str(result_metric)) return result_metric
def _measure_volume(self, im_data, p_lst, idx): for zz in range(im_data.shape[2]): self.volumes[zz, idx - 1] = np.sum(im_data[:, :, zz]) * p_lst[0] * p_lst[1] * p_lst[2] vol_tot_cur = np.sum(self.volumes[:, idx - 1]) self.measure_pd.loc[idx, 'volume [mm3]'] = vol_tot_cur printv(' Volume : ' + str(np.round(vol_tot_cur, 2)) + ' mm^3', self.verbose, type='info')
def ifolder2tmp(self): # copy input image if self.fname_mask is not None: sct.copy(self.fname_mask, self.tmp_dir) self.fname_mask = ''.join(extract_fname(self.fname_mask)[1:]) else: printv('ERROR: No input image', self.verbose, 'error') # copy seg image if self.fname_sc is not None: sct.copy(self.fname_sc, self.tmp_dir) self.fname_sc = ''.join(extract_fname(self.fname_sc)[1:]) # copy ref image if self.fname_ref is not None: sct.copy(self.fname_ref, self.tmp_dir) self.fname_ref = ''.join(extract_fname(self.fname_ref)[1:]) # copy registered template if self.path_template is not None: sct.copy(self.path_levels, self.tmp_dir) self.path_levels = ''.join(extract_fname(self.path_levels)[1:]) self.atlas_roi_lst = [] for fname_atlas_roi in os.listdir(self.path_atlas): if fname_atlas_roi.endswith('.nii.gz'): tract_id = int(fname_atlas_roi.split('_')[-1].split('.nii.gz')[0]) if tract_id < 36: # Not interested in CSF sct.copy(os.path.join(self.path_atlas, fname_atlas_roi), self.tmp_dir) self.atlas_roi_lst.append(fname_atlas_roi) os.chdir(self.tmp_dir) # go to tmp directory
def main(args = None): dim_list = ['x', 'y', 'z', 't'] if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_in = arguments["-i"] fname_out = arguments["-o"] verbose = int(arguments['-v']) # Build fname_out if fname_out == '': path_in, file_in, ext_in = extract_fname(fname_in) fname_out = path_in+file_in+'_mean'+ext_in # Open file. nii = Image(fname_in) data = nii.data # run command if '-otsu' in arguments: param = arguments['-otsu'] data_out = otsu(data, param) elif '-otsu_adap' in arguments: param = arguments['-otsu_adap'] data_out = otsu_adap(data, param[0], param[1]) elif '-otsu_median' in arguments: param = arguments['-otsu_median'] data_out = otsu_median(data, param[0], param[1]) elif '-thr' in arguments: param = arguments['-thr'] data_out = threshold(data, param) elif '-percent' in arguments: param = arguments['-percent'] data_out = perc(data, param) elif '-mean' in arguments: dim = dim_list.index(arguments['-mean']) data_out = compute_mean(data, dim) elif '-std' in arguments: dim = dim_list.index(arguments['-std']) data_out = compute_std(data, dim) elif '-dilate' in arguments: data_out = dilate(data, arguments['-dilate']) elif '-erode' in arguments: data_out = erode(data, arguments['-dilate']) else: printv('No process applied.', 1, 'warning') return # Write output nii.data = data_out nii.setFileName(fname_out) nii.save() # display message printv('Created file:\n--> '+fname_out+'\n', verbose, 'info')
def orientation(im, ori=None, set=False, get=False, set_data=False, verbose=1, fname_out=''): verbose = 0 if get else verbose printv('\nGet dimensions of data...', verbose) nx, ny, nz, nt, px, py, pz, pt = get_dimension(im) printv(str(nx) + ' x ' + str(ny) + ' x ' + str(nz)+ ' x ' + str(nt), verbose) # if data are 2d or 3d, get orientation from header using fslhd if (nz == 1 or nt==1) and len(im.data.shape)<5: if get: try: printv('\nGet orientation...', verbose) im_out = None ori = get_orientation(im) except Exception, e: printv('ERROR: an error occurred: \n'+str(e), verbose,'error') return ori elif set: # set orientation printv('\nChange orientation...', verbose) im_out = set_orientation(im, ori) elif set_data: im_out = set_orientation(im, ori, True) else: im_out = None
def main(args=None): if args is None: args = sys.argv[1:] param = Param() # Check input parameters parser = get_parser() arguments = parser.parse(args) param.fname_data = os.path.abspath(arguments['-i']) if '-p' in arguments: param.process = arguments['-p'] if param.process[0] not in param.process_list: sct.printv(parser.usage.generate(error='ERROR: Process ' + param.process[0] + ' is not recognized.')) if '-size' in arguments: param.size = arguments['-size'] if '-f' in arguments: param.shape = arguments['-f'] if '-o' in arguments: param.fname_out = os.path.abspath(arguments['-o']) if '-r' in arguments: param.remove_temp_files = int(arguments['-r']) param.verbose = int(arguments.get('-v')) sct.init_sct(log_level=param.verbose, update=True) # Update log level # run main program create_mask(param)
choices=(0, 1, 2), default = 1) return parser ######################################################################################################################## # ------------------------------------------------------ MAIN ------------------------------------------------------- # ######################################################################################################################## if __name__ == "__main__": init_sct() param = Param() input_fname = None if param.debug: sct.printv('\n*** WARNING: DEBUG MODE ON ***\n') else: param_default = Param() parser = get_parser() arguments = parser.parse_args(args=None if sys.argv[1:] else ['--help']) input_fname = arguments.i input_second_fname = '' output_fname = 'hausdorff_distance.txt' resample_to = 0.1 if arguments.d is not None: input_second_fname = arguments.d if arguments.thinning is not None: param.thinning = bool(arguments.thinning) if arguments.resampling is not None: resample_to = arguments.resampling
def vertebral_detection(fname, fname_seg, contrast, param, init_disc, verbose=1, path_template='', path_output='../', scale_dist=1.): """ Find intervertebral discs in straightened image using template matching :param fname: file name of straigthened spinal cord :param fname_seg: file name of straigthened spinal cord segmentation :param contrast: t1 or t2 :param param: advanced parameters :param init_disc: :param verbose: :param path_template: :param path_output: output path for verbose=2 pictures :param scale_dist: float: Scaling factor to adjust average distance between two adjacent intervertebral discs :return: """ sct.printv('\nLook for template...', verbose) sct.printv('Path template: ' + path_template, verbose) # adjust file names if MNI-Poly-AMU template is used (by default: PAM50) fname_level = get_file_label( os.path.join(path_template, 'template'), id_label=7, output='filewithpath' ) # label = spinal cord mask with discrete vertebral levels id_label_dct = {'T1': 0, 'T2': 1, 'T2S': 2} fname_template = get_file_label( os.path.join(path_template, 'template'), id_label=id_label_dct[contrast.upper()], output='filewithpath') # label = *-weighted template # 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 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(np.round(nx / 2)) # direction RL yc = int(np.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(np.round(nxt / 2)) # direction RL yct = int(np.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 = list(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 # Update distance with scaling factor list_distance_template = [i * scale_dist for i in list_distance_template] sct.printv( 'Distances between discs (in voxel): ' + str(list_distance_template), verbose) # display init disc if verbose == 2: from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas from matplotlib.figure import Figure fig_disc = Figure() FigureCanvas(fig_disc) ax_disc = fig_disc.add_subplot(111) # ax_disc = fig_disc.add_axes((0, 0, 1, 1)) # 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 ax_disc.matshow(data_display, cmap='gray', clim=[percmin, percmax], origin='lower') ax_disc.set_title('Anatomical image') # ax.autoscale(enable=False) # to prevent autoscale of axis when displaying plot ax_disc.scatter(yc + param.shift_AP_visu, init_disc[0], c='yellow', s=10) ax_disc.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=7) # FIND DISCS # =========================================================================== sct.printv('\nDetect intervertebral discs...', verbose) # assign initial z and disc current_z = init_disc[0] current_disc = init_disc[1] # create list for z and disc list_disc_z = [] list_disc_value = [] zrange = list(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: ax_disc.scatter(yc + param.shift_AP_visu, current_z, c='yellow', s=10) ax_disc.text(yc + param.shift_AP_visu + 4, current_z, str(current_disc) + '/' + str(current_disc + 1), verticalalignment='center', horizontalalignment='left', color='yellow', fontsize=7) # 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(np.round(list_distance_template[i] * correcting_factor)) for i in range(len(list_distance_template)) ] # 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: fig_disc.savefig('fig_label_discs.png') # 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)
def compute_corr_3d(src, target, x, xshift, xsize, y, yshift, ysize, z, zshift, zsize, xtarget, ytarget, ztarget, zrange, verbose, save_suffix, gaussian_std, path_output): """ FIXME doc Find z that maximizes correlation between src and target 3d data. :param src: 3d source data :param target: 3d target data :param x: :param xshift: :param xsize: :param y: :param yshift: :param ysize: :param z: :param zshift: :param zsize: :param xtarget: :param ytarget: :param ztarget: :param zrange: :param verbose: :param save_suffix: :param gaussian_std: :return: """ # parameters thr_corr = 0.2 # disc correlation threshold. Below this value, use template distance. # get dimensions from src nx, ny, nz = src.shape # Get pattern from template pattern = target[xtarget - xsize:xtarget + xsize + 1, ytarget + yshift - ysize:ytarget + yshift + ysize + 1, ztarget + zshift - zsize:ztarget + zshift + zsize + 1] pattern1d = pattern.ravel() # initializations I_corr = np.zeros(len(zrange)) allzeros = 0 # current_z = 0 ind_I = 0 # loop across range of z defined by src for iz in zrange: # if pattern extends towards the top part of the image, then crop and pad with zeros if z + iz + zsize + 1 > nz: # sct.printv('iz='+str(iz)+': padding on top') padding_size = z + iz + zsize + 1 - nz data_chunk3d = src[x - xsize:x + xsize + 1, y + yshift - ysize:y + yshift + ysize + 1, z + iz - zsize:z + iz + zsize + 1 - padding_size] data_chunk3d = np.pad(data_chunk3d, ((0, 0), (0, 0), (0, padding_size)), 'constant', constant_values=0) # if pattern extends towards bottom part of the image, then crop and pad with zeros elif z + iz - zsize < 0: # sct.printv('iz='+str(iz)+': padding at bottom') padding_size = abs(iz - zsize) data_chunk3d = src[x - xsize:x + xsize + 1, y + yshift - ysize:y + yshift + ysize + 1, z + iz - zsize + padding_size:z + iz + zsize + 1] data_chunk3d = np.pad(data_chunk3d, ((0, 0), (0, 0), (padding_size, 0)), 'constant', constant_values=0) else: data_chunk3d = src[x - xsize:x + xsize + 1, y + yshift - ysize:y + yshift + ysize + 1, z + iz - zsize:z + iz + zsize + 1] # convert subject pattern to 1d data_chunk1d = data_chunk3d.ravel() # check if data_chunk1d contains at least one non-zero value if (data_chunk1d.size == pattern1d.size) and np.any(data_chunk1d): I_corr[ind_I] = mutual_information(data_chunk1d, pattern1d, nbins=16, normalized=False) else: allzeros = 1 ind_I = ind_I + 1 # ind_y = ind_y + 1 if allzeros: sct.printv( '.. WARNING: Data contained zero. We probably hit the edge of the image.', verbose) # adjust correlation with Gaussian function centered at the right edge of the curve (most rostral point of FOV) from scipy.signal import gaussian gaussian_window = gaussian(len(I_corr) * 2, std=len(I_corr) * gaussian_std) I_corr_gauss = np.multiply(I_corr, gaussian_window[0:len(I_corr)]) # Find global maximum if np.any(I_corr_gauss): # if I_corr contains at least a non-zero value ind_peak = [ i for i in range(len(I_corr_gauss)) if I_corr_gauss[i] == max(I_corr_gauss) ][0] # index of max along z sct.printv( '.. Peak found: z=' + str(zrange[ind_peak]) + ' (correlation = ' + str(I_corr_gauss[ind_peak]) + ')', verbose) # check if correlation is high enough if I_corr_gauss[ind_peak] < thr_corr: sct.printv( '.. WARNING: Correlation is too low. Using adjusted template distance.', verbose) ind_peak = zrange.index(0) # approx_distance_to_next_disc else: # if I_corr contains only zeros sct.printv( '.. WARNING: Correlation vector only contains zeros. Using adjusted template distance.', verbose) ind_peak = zrange.index(0) # approx_distance_to_next_disc # display patterns and correlation if verbose == 2: from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas from matplotlib.figure import Figure fig = Figure(figsize=(15, 7)) FigureCanvas(fig) # display template pattern ax = fig.add_subplot(131) ax.imshow(np.flipud(np.mean(pattern[:, :, :], axis=0).transpose()), origin='upper', cmap='gray', interpolation='none') ax.set_title('Template pattern') # display subject pattern at best z ax = fig.add_subplot(132) iz = zrange[ind_peak] data_chunk3d = src[x - xsize:x + xsize + 1, y + yshift - ysize:y + yshift + ysize + 1, z + iz - zsize:z + iz + zsize + 1] ax.imshow(np.flipud( np.mean(data_chunk3d[:, :, :], axis=0).transpose()), origin='upper', cmap='gray', clim=[0, 800], interpolation='none') ax.set_title('Subject at iz=' + str(iz)) # display correlation curve ax = fig.add_subplot(133) ax.plot(zrange, I_corr) ax.plot(zrange, I_corr_gauss, 'black', linestyle='dashed') ax.legend(['I_corr', 'I_corr_gauss']) ax.set_title('Mutual Info, gaussian_std=' + str(gaussian_std)) ax.plot(zrange[ind_peak], I_corr_gauss[ind_peak], 'ro') ax.axvline(x=zrange.index(0), linewidth=1, color='black', linestyle='dashed') ax.axhline(y=thr_corr, linewidth=1, color='r', linestyle='dashed') ax.grid() # save figure fig.savefig('fig_pattern' + save_suffix + '.png') # return z-origin (z) + z-displacement minus zshift (to account for non-centered disc) return z + zrange[ind_peak] - zshift
def main(args=None): # initializations initz = '' initcenter = '' fname_initlabel = '' file_labelz = 'labelz.nii.gz' param = Param() # check user arguments if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) fname_in = os.path.abspath(arguments["-i"]) fname_seg = os.path.abspath(arguments['-s']) contrast = arguments['-c'] path_template = os.path.abspath(arguments['-t']) scale_dist = arguments['-scale-dist'] if '-ofolder' in arguments: path_output = arguments['-ofolder'] else: path_output = os.curdir param.path_qc = arguments.get("-qc", None) if '-discfile' in arguments: fname_disc = os.path.abspath(arguments['-discfile']) else: fname_disc = None if '-initz' in arguments: initz = arguments['-initz'] if '-initcenter' in arguments: initcenter = arguments['-initcenter'] # if user provided text file, parse and overwrite arguments if '-initfile' in arguments: file = open(arguments['-initfile'], 'r') initfile = ' ' + file.read().replace('\n', '') arg_initfile = initfile.split(' ') for idx_arg, arg in enumerate(arg_initfile): if arg == '-initz': initz = [int(x) for x in arg_initfile[idx_arg + 1].split(',')] if arg == '-initcenter': initcenter = int(arg_initfile[idx_arg + 1]) if '-initlabel' in arguments: # get absolute path of label fname_initlabel = os.path.abspath(arguments['-initlabel']) if '-param' in arguments: param.update(arguments['-param'][0]) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level remove_temp_files = int(arguments['-r']) denoise = int(arguments['-denoise']) laplacian = int(arguments['-laplacian']) path_tmp = sct.tmp_create(basename="label_vertebrae", verbose=verbose) # Copying input data to tmp folder sct.printv('\nCopying input data to tmp folder...', verbose) Image(fname_in).save(os.path.join(path_tmp, "data.nii")) Image(fname_seg).save(os.path.join(path_tmp, "segmentation.nii")) # Go go temp folder curdir = os.getcwd() os.chdir(path_tmp) # Straighten spinal cord sct.printv('\nStraighten spinal cord...', verbose) # check if warp_curve2straight and warp_straight2curve already exist (i.e. no need to do it another time) cache_sig = sct.cache_signature(input_files=[fname_in, fname_seg], ) cachefile = os.path.join(curdir, "straightening.cache") if sct.cache_valid(cachefile, cache_sig) and os.path.isfile( os.path.join( curdir, "warp_curve2straight.nii.gz")) and os.path.isfile( os.path.join( curdir, "warp_straight2curve.nii.gz")) and os.path.isfile( os.path.join(curdir, "straight_ref.nii.gz")): # if they exist, copy them into current folder sct.printv('Reusing existing warping field which seems to be valid', verbose, 'warning') sct.copy(os.path.join(curdir, "warp_curve2straight.nii.gz"), 'warp_curve2straight.nii.gz') sct.copy(os.path.join(curdir, "warp_straight2curve.nii.gz"), 'warp_straight2curve.nii.gz') sct.copy(os.path.join(curdir, "straight_ref.nii.gz"), 'straight_ref.nii.gz') # apply straightening s, o = sct.run([ 'sct_apply_transfo', '-i', 'data.nii', '-w', 'warp_curve2straight.nii.gz', '-d', 'straight_ref.nii.gz', '-o', 'data_straight.nii' ]) else: sct_straighten_spinalcord.main(args=[ '-i', 'data.nii', '-s', 'segmentation.nii', '-r', str(remove_temp_files), '-v', str(verbose), ]) sct.cache_save(cachefile, cache_sig) # resample to 0.5mm isotropic to match template resolution sct.printv('\nResample to 0.5mm isotropic...', verbose) s, o = sct.run([ 'sct_resample', '-i', 'data_straight.nii', '-mm', '0.5x0.5x0.5', '-x', 'linear', '-o', 'data_straightr.nii' ], verbose=verbose) # Apply straightening to segmentation # N.B. Output is RPI sct.printv('\nApply straightening to segmentation...', verbose) sct.run( 'isct_antsApplyTransforms -d 3 -i %s -r %s -t %s -o %s -n %s' % ('segmentation.nii', 'data_straightr.nii', 'warp_curve2straight.nii.gz', 'segmentation_straight.nii', 'Linear'), verbose=verbose, is_sct_binary=True, ) # Threshold segmentation at 0.5 sct.run([ 'sct_maths', '-i', 'segmentation_straight.nii', '-thr', '0.5', '-o', 'segmentation_straight.nii' ], verbose) # If disc label file is provided, label vertebrae using that file instead of automatically if fname_disc: # Apply straightening to disc-label sct.printv('\nApply straightening to disc labels...', verbose) sct.run( 'isct_antsApplyTransforms -d 3 -i %s -r %s -t %s -o %s -n %s' % (fname_disc, 'data_straightr.nii', 'warp_curve2straight.nii.gz', 'labeldisc_straight.nii.gz', 'NearestNeighbor'), verbose=verbose, is_sct_binary=True, ) label_vert('segmentation_straight.nii', 'labeldisc_straight.nii.gz', verbose=1) else: # create label to identify disc sct.printv('\nCreate label to identify disc...', verbose) fname_labelz = os.path.join(path_tmp, file_labelz) if initz or initcenter: if initcenter: # find z centered in FOV nii = Image('segmentation.nii').change_orientation("RPI") nx, ny, nz, nt, px, py, pz, pt = nii.dim # Get dimensions z_center = int(np.round(nz / 2)) # get z_center initz = [z_center, initcenter] # create single label and output as labels.nii.gz label = ProcessLabels( 'segmentation.nii', fname_output='tmp.labelz.nii.gz', coordinates=['{},{}'.format(initz[0], initz[1])]) im_label = label.process('create-seg') im_label.data = sct_maths.dilate( im_label.data, [3]) # TODO: create a dilation method specific to labels, # which does not apply a convolution across all voxels (highly inneficient) im_label.save(fname_labelz) elif fname_initlabel: import sct_label_utils # subtract "1" to label value because due to legacy, in this code the disc C2-C3 has value "2", whereas in the # recent version of SCT it is defined as "3". Therefore, when asking the user to define a label, we point to the # new definition of labels (i.e., C2-C3 = 3). sct_label_utils.main( ['-i', fname_initlabel, '-add', '-1', '-o', fname_labelz]) else: # automatically finds C2-C3 disc im_data = Image('data.nii') im_seg = Image('segmentation.nii') if not remove_temp_files: # because verbose is here also used for keeping temp files verbose_detect_c2c3 = 2 else: verbose_detect_c2c3 = 0 im_label_c2c3 = detect_c2c3(im_data, im_seg, contrast, verbose=verbose_detect_c2c3) ind_label = np.where(im_label_c2c3.data) if not np.size(ind_label) == 0: # subtract "1" to label value because due to legacy, in this code the disc C2-C3 has value "2", whereas in the # recent version of SCT it is defined as "3". im_label_c2c3.data[ind_label] = 2 else: sct.printv( 'Automatic C2-C3 detection failed. Please provide manual label with sct_label_utils', 1, 'error') sys.exit() im_label_c2c3.save(fname_labelz) # dilate label so it is not lost when applying warping sct_maths.main( ['-i', fname_labelz, '-dilate', '3', '-o', fname_labelz]) # Apply straightening to z-label sct.printv('\nAnd apply straightening to label...', verbose) sct.run( 'isct_antsApplyTransforms -d 3 -i %s -r %s -t %s -o %s -n %s' % (file_labelz, 'data_straightr.nii', 'warp_curve2straight.nii.gz', 'labelz_straight.nii.gz', 'NearestNeighbor'), verbose=verbose, is_sct_binary=True, ) # get z value and disk value to initialize labeling sct.printv('\nGet z and disc values from straight label...', verbose) init_disc = get_z_and_disc_values_from_label('labelz_straight.nii.gz') sct.printv('.. ' + str(init_disc), verbose) # denoise data if denoise: sct.printv('\nDenoise data...', verbose) sct.run([ 'sct_maths', '-i', 'data_straightr.nii', '-denoise', 'h=0.05', '-o', 'data_straightr.nii' ], verbose) # apply laplacian filtering if laplacian: sct.printv('\nApply Laplacian filter...', verbose) sct.run([ 'sct_maths', '-i', 'data_straightr.nii', '-laplacian', '1', '-o', 'data_straightr.nii' ], verbose) # detect vertebral levels on straight spinal cord vertebral_detection('data_straightr.nii', 'segmentation_straight.nii', contrast, param, init_disc=init_disc, verbose=verbose, path_template=path_template, path_output=path_output, scale_dist=scale_dist) # un-straighten labeled spinal cord sct.printv('\nUn-straighten labeling...', verbose) sct.run( 'isct_antsApplyTransforms -d 3 -i %s -r %s -t %s -o %s -n %s' % ('segmentation_straight_labeled.nii', 'segmentation.nii', 'warp_straight2curve.nii.gz', 'segmentation_labeled.nii', 'NearestNeighbor'), verbose=verbose, is_sct_binary=True, ) # Clean labeled segmentation sct.printv( '\nClean labeled segmentation (correct interpolation errors)...', verbose) clean_labeled_segmentation('segmentation_labeled.nii', 'segmentation.nii', 'segmentation_labeled.nii') # label discs sct.printv('\nLabel discs...', verbose) label_discs('segmentation_labeled.nii', verbose=verbose) # come back os.chdir(curdir) # Generate output files path_seg, file_seg, ext_seg = sct.extract_fname(fname_seg) fname_seg_labeled = os.path.join(path_output, file_seg + '_labeled' + ext_seg) sct.printv('\nGenerate output files...', verbose) sct.generate_output_file( os.path.join(path_tmp, "segmentation_labeled.nii"), fname_seg_labeled) sct.generate_output_file( os.path.join(path_tmp, "segmentation_labeled_disc.nii"), os.path.join(path_output, file_seg + '_labeled_discs' + ext_seg)) # copy straightening files in case subsequent SCT functions need them sct.generate_output_file( os.path.join(path_tmp, "warp_curve2straight.nii.gz"), os.path.join(path_output, "warp_curve2straight.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "warp_straight2curve.nii.gz"), os.path.join(path_output, "warp_straight2curve.nii.gz"), verbose) sct.generate_output_file(os.path.join(path_tmp, "straight_ref.nii.gz"), os.path.join(path_output, "straight_ref.nii.gz"), verbose) # Remove temporary files if remove_temp_files == 1: sct.printv('\nRemove temporary files...', verbose) sct.rmtree(path_tmp) # Generate QC report if param.path_qc is not None: path_qc = os.path.abspath(param.path_qc) qc_dataset = arguments.get("-qc-dataset", None) qc_subject = arguments.get("-qc-subject", None) labeled_seg_file = os.path.join(path_output, file_seg + '_labeled' + ext_seg) generate_qc(fname_in, fname_seg=labeled_seg_file, args=args, path_qc=os.path.abspath(path_qc), dataset=qc_dataset, subject=qc_subject, process='sct_label_vertebrae') sct.display_viewer_syntax([fname_in, fname_seg_labeled], colormaps=['', 'subcortical'], opacities=['1', '0.5'])
def main(): # Initialization fname_warp_list = '' # list of warping fields fname_dest = '' # destination image (fix) fname_warp_final = '' # concatenated transformations verbose = 1 # Parameters for debug mode if param.debug: sct.printv('\n*** WARNING: DEBUG MODE ON ***\n') path_sct_data = os.environ.get( "SCT_TESTING_DATA_DIR", os.path.join(os.path.dirname(os.path.dirname(__file__))), "testing_data") fname_warp_list = os.path.join( path_sct_data, 't2', 'warp_template2anat.nii.gz') + '-' + os.path.join( path_sct_data, 'mt', 'warp_template2mt.nii.gz') fname_dest = os.path.join(path_sct_data, 'mt', 'mtr.nii.gz') verbose = 1 else: # Check input parameters parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_dest = arguments['-d'] fname_warp_list = arguments['-w'] if '-o' in arguments: fname_warp_final = arguments['-o'] verbose = int(arguments['-v']) # Parse list of warping fields sct.printv('\nParse list of transformations...', verbose) use_inverse = [] fname_warp_list_invert = [] for i in range(len(fname_warp_list)): # Check if inverse matrix is specified with '-' at the beginning of file name if fname_warp_list[i].find('-') == 0: use_inverse.append('-i') fname_warp_list[i] = fname_warp_list[i][1:] # remove '-' fname_warp_list_invert += [[use_inverse[i], fname_warp_list[i]]] else: use_inverse.append('') fname_warp_list_invert += [[fname_warp_list[i]]] sct.printv( ' Transfo #' + str(i) + ': ' + use_inverse[i] + fname_warp_list[i], verbose) # Check file existence sct.printv('\nCheck file existence...', verbose) sct.check_file_exist(fname_dest, verbose) for i in range(len(fname_warp_list)): sct.check_file_exist(fname_warp_list[i], verbose) # Get output folder and file name if fname_warp_final == '': path_out, file_out, ext_out = sct.extract_fname(param.fname_warp_final) else: path_out, file_out, ext_out = sct.extract_fname(fname_warp_final) # Check dimension of destination data (cf. issue #1419, #1429) im_dest = Image(fname_dest) if im_dest.dim[2] == 1: dimensionality = '2' else: dimensionality = '3' # Concatenate warping fields sct.printv('\nConcatenate warping fields...', verbose) # N.B. Here we take the inverse of the warp list fname_warp_list_invert.reverse() fname_warp_list_invert = functools.reduce(lambda x, y: x + y, fname_warp_list_invert) cmd = [ 'isct_ComposeMultiTransform', dimensionality, 'warp_final' + ext_out, '-R', fname_dest ] + fname_warp_list_invert status, output = sct.run(cmd, verbose=verbose) # check if output was generated if not os.path.isfile('warp_final' + ext_out): sct.printv('ERROR: Warping field was not generated.\n' + output, 1, 'error') # Generate output files sct.printv('\nGenerate output files...', verbose) sct.generate_output_file('warp_final' + ext_out, os.path.join(path_out, file_out + ext_out))
def compute_properties_along_centerline(fname_seg_image, property_list, fname_disks_image=None, smooth_factor=5.0, interpolation_mode=0, remove_temp_files=1, verbose=1): # Check list of properties # If diameters is in the list, compute major and minor axis length and check orientation compute_diameters = False property_list_local = list(property_list) if 'diameters' in property_list_local: compute_diameters = True property_list_local.remove('diameters') property_list_local.append('major_axis_length') property_list_local.append('minor_axis_length') property_list_local.append('orientation') # TODO: make sure fname_segmentation and fname_disks are in the same space # create temporary folder and copying data sct.printv('\nCreate temporary folder...', verbose) path_tmp = sct.slash_at_the_end('tmp.' + time.strftime("%y%m%d%H%M%S") + '_' + str(randint(1, 1000000)), 1) sct.run('mkdir ' + path_tmp, verbose) sct.run('cp ' + fname_seg_image + ' ' + path_tmp) if fname_disks_image is not None: sct.run('cp ' + fname_disks_image + ' ' + path_tmp) # go to tmp folder os.chdir(path_tmp) fname_segmentation = os.path.abspath(fname_seg_image) path_data, file_data, ext_data = sct.extract_fname(fname_segmentation) # Change orientation of the input centerline into RPI sct.printv('\nOrient centerline to RPI orientation...', verbose) im_seg = Image(file_data + ext_data) fname_segmentation_orient = 'segmentation_rpi' + ext_data image = set_orientation(im_seg, 'RPI') image.setFileName(fname_segmentation_orient) image.save() # Initiating some variables nx, ny, nz, nt, px, py, pz, pt = image.dim resolution = 0.5 properties = {key: [] for key in property_list_local} properties['incremental_length'] = [] properties['distance_from_C1'] = [] properties['vertebral_level'] = [] properties['z_slice'] = [] # compute the spinal cord centerline based on the spinal cord segmentation number_of_points = 5 * nz x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv = smooth_centerline(fname_segmentation_orient, algo_fitting='nurbs', verbose=verbose, nurbs_pts_number=number_of_points, all_slices=False, phys_coordinates=True, remove_outliers=True) centerline = Centerline(x_centerline_fit, y_centerline_fit, z_centerline, x_centerline_deriv, y_centerline_deriv, z_centerline_deriv) # Compute vertebral distribution along centerline based on position of intervertebral disks if fname_disks_image is not None: fname_disks = os.path.abspath(fname_disks_image) path_data, file_data, ext_data = sct.extract_fname(fname_disks) im_disks = Image(file_data + ext_data) fname_disks_orient = 'disks_rpi' + ext_data image_disks = set_orientation(im_disks, 'RPI') image_disks.setFileName(fname_disks_orient) image_disks.save() image_disks = Image(fname_disks_orient) coord = image_disks.getNonZeroCoordinates(sorting='z', reverse_coord=True) coord_physical = [] for c in coord: c_p = image_disks.transfo_pix2phys([[c.x, c.y, c.z]])[0] c_p.append(c.value) coord_physical.append(c_p) centerline.compute_vertebral_distribution(coord_physical) sct.printv('Computing spinal cord shape along the spinal cord...') timer_properties = sct.Timer(number_of_iteration=centerline.number_of_points) timer_properties.start() # Extracting patches perpendicular to the spinal cord and computing spinal cord shape for index in range(centerline.number_of_points): value_out = -5.0 current_patch = centerline.extract_perpendicular_square(image, index, resolution=resolution, interpolation_mode=interpolation_mode, border='constant', cval=value_out) # check for pixels close to the spinal cord segmentation that are out of the image from skimage.morphology import dilation patch_zero = np.copy(current_patch) patch_zero[patch_zero == value_out] = 0.0 patch_borders = dilation(patch_zero) - patch_zero if np.count_nonzero(patch_borders + current_patch == value_out + 1.0) != 0: continue sc_properties = properties2d(patch_zero, [resolution, resolution]) if sc_properties is not None: properties['incremental_length'].append(centerline.incremental_length[index]) if fname_disks_image is not None: properties['distance_from_C1'].append(centerline.dist_points[index]) properties['vertebral_level'].append(centerline.l_points[index]) properties['z_slice'].append(image.transfo_phys2pix([centerline.points[index]])[0][2]) for property_name in property_list_local: properties[property_name].append(sc_properties[property_name]) timer_properties.add_iteration() timer_properties.stop() # Adding centerline to the properties for later use properties['centerline'] = centerline # We assume that the major axis is in the right-left direction # this script checks the orientation of the spinal cord and invert axis if necessary to make sure the major axis is right-left if compute_diameters: diameter_major = properties['major_axis_length'] diameter_minor = properties['minor_axis_length'] orientation = properties['orientation'] for i, orientation_item in enumerate(orientation): if -45.0 < orientation_item < 45.0: continue else: temp = diameter_minor[i] properties['minor_axis_length'][i] = diameter_major[i] properties['major_axis_length'][i] = temp properties['RL_diameter'] = properties['major_axis_length'] properties['AP_diameter'] = properties['minor_axis_length'] del properties['major_axis_length'] del properties['minor_axis_length'] # smooth the spinal cord shape with a gaussian kernel if required # TODO: not all properties can be smoothed if smooth_factor != 0.0: # smooth_factor is in mm import scipy window = scipy.signal.hann(smooth_factor / np.mean(centerline.progressive_length)) for property_name in property_list_local: properties[property_name] = scipy.signal.convolve(properties[property_name], window, mode='same') / np.sum(window) if compute_diameters: property_list_local.remove('major_axis_length') property_list_local.remove('minor_axis_length') property_list_local.append('RL_diameter') property_list_local.append('AP_diameter') property_list = property_list_local # Display properties on the referential space. Requires intervertebral disks if verbose == 2: x_increment = 'distance_from_C1' if fname_disks_image is None: x_increment = 'incremental_length' # Display the image and plot all contours found fig, axes = plt.subplots(len(property_list_local), sharex=True, sharey=False) for k, property_name in enumerate(property_list_local): axes[k].plot(properties[x_increment], properties[property_name]) axes[k].set_ylabel(property_name) if fname_disks_image is not None: properties['distance_disk_from_C1'] = centerline.distance_from_C1label # distance between each disk and C1 (or first disk) xlabel_disks = [centerline.convert_vertlabel2disklabel[label] for label in properties['distance_disk_from_C1']] xtick_disks = [properties['distance_disk_from_C1'][label] for label in properties['distance_disk_from_C1']] plt.xticks(xtick_disks, xlabel_disks, rotation=30) else: axes[-1].set_xlabel('Position along the spinal cord (in mm)') plt.show() # Removing temporary folder os.chdir('..') shutil.rmtree(path_tmp, ignore_errors=True) return property_list, properties
def __init__(self, im1, im2=None, param=None): self.im1 = im1 self.im2 = im2 self.dim_im = len(self.im1.data.shape) self.dim_pix = 0 self.distances = None self.res = '' self.param = param self.dist1_distribution = None self.dist2_distribution = None if self.dim_im == 3: self.orientation1 = self.im1.orientation if self.orientation1 != 'IRP': self.im1.change_orientation('IRP', generate_path=True) if self.im2 is not None: self.orientation2 = self.im2.orientation if self.orientation2 != 'IRP': self.im2.change_orientation('IRP', generate_path=True) if self.param.thinning: self.thinning1 = Thinning(self.im1, self.param.verbose) self.thinning1.thinned_image.save() if self.im2 is not None: self.thinning2 = Thinning(self.im2, self.param.verbose) self.thinning2.thinned_image.save() if self.dim_im == 2 and self.im2 is not None: self.compute_dist_2im_2d() if self.dim_im == 3: if self.im2 is None: self.compute_dist_1im_3d() else: self.compute_dist_2im_3d() if self.dim_im == 2 and self.distances is not None: self.dist1_distribution = self.distances.min_distances_1[np.nonzero(self.distances.min_distances_1)] self.dist2_distribution = self.distances.min_distances_2[np.nonzero(self.distances.min_distances_2)] if self.dim_im == 3: self.dist1_distribution = [] self.dist2_distribution = [] for d in self.distances: if np.nonzero(d.min_distances_1)[0].size: # Exist non zero values self.dist1_distribution.append(d.min_distances_1[np.nonzero(d.min_distances_1)]) else: # all values are zero self.dist1_distribution.append(0) if np.nonzero(d.min_distances_2)[0].size: # Exist non zero values self.dist2_distribution.append(d.min_distances_2[np.nonzero(d.min_distances_2)]) else: # all values are zero self.dist2_distribution.append(0) self.res = 'Hausdorff\'s distance - First relative Hausdorff\'s distance median - Second relative Hausdorff\'s distance median(all in mm)\n' for i, d in enumerate(self.distances): med1 = np.median(self.dist1_distribution[i]) med2 = np.median(self.dist2_distribution[i]) if self.im2 is None: self.res += 'Slice ' + str(i) + ' - slice ' + str(i + 1) + ': ' + str(d.H * self.dim_pix) + ' - ' + str(med1 * self.dim_pix) + ' - ' + str(med2 * self.dim_pix) + ' \n' else: self.res += 'Slice ' + str(i) + ': ' + str(d.H * self.dim_pix) + ' - ' + str(med1 * self.dim_pix) + ' - ' + str(med2 * self.dim_pix) + ' \n' sct.printv('-----------------------------------------------------------------------------\n' + self.res, self.param.verbose, 'normal') if self.param.verbose == 2: self.show_results()
def main(args = None): dim_list = ['x', 'y', 'z', 't'] if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) fname_in = arguments["-i"] fname_out = arguments["-o"] verbose = int(arguments['-v']) if '-type' in arguments: output_type = arguments['-type'] else: output_type = None # Open file(s) im = Image(fname_in) data = im.data # 3d or 4d numpy array dim = im.dim # run command if '-otsu' in arguments: param = arguments['-otsu'] data_out = otsu(data, param) elif '-otsu_adap' in arguments: param = arguments['-otsu_adap'] data_out = otsu_adap(data, param[0], param[1]) elif '-otsu_median' in arguments: param = arguments['-otsu_median'] data_out = otsu_median(data, param[0], param[1]) elif '-thr' in arguments: param = arguments['-thr'] data_out = threshold(data, param) elif '-percent' in arguments: param = arguments['-percent'] data_out = perc(data, param) elif '-bin' in arguments: bin_thr = arguments['-bin'] data_out = binarise(data, bin_thr=bin_thr) elif '-add' in arguments: from numpy import sum data2 = get_data_or_scalar(arguments["-add"], data) data_concat = concatenate_along_4th_dimension(data, data2) data_out = sum(data_concat, axis=3) elif '-sub' in arguments: data2 = get_data_or_scalar(arguments['-sub'], data) data_out = data - data2 elif "-laplacian" in arguments: sigmas = arguments["-laplacian"] if len(sigmas) == 1: sigmas = [sigmas for i in range(len(data.shape))] elif len(sigmas) != len(data.shape): printv(parser.usage.generate(error='ERROR: -laplacian need the same number of inputs as the number of image dimension OR only one input')) # adjust sigma based on voxel size sigmas = [sigmas[i] / dim[i + 4] for i in range(3)] # smooth data data_out = laplacian(data, sigmas) elif '-mul' in arguments: from numpy import prod data2 = get_data_or_scalar(arguments["-mul"], data) data_concat = concatenate_along_4th_dimension(data, data2) data_out = prod(data_concat, axis=3) elif '-div' in arguments: from numpy import divide data2 = get_data_or_scalar(arguments["-div"], data) data_out = divide(data, data2) elif '-mean' in arguments: from numpy import mean dim = dim_list.index(arguments['-mean']) if dim + 1 > len(np.shape(data)): # in case input volume is 3d and dim=t data = data[..., np.newaxis] data_out = mean(data, dim) elif '-rms' in arguments: from numpy import mean, sqrt, square dim = dim_list.index(arguments['-rms']) if dim + 1 > len(np.shape(data)): # in case input volume is 3d and dim=t data = data[..., np.newaxis] data_out = sqrt(mean(square(data.astype(float)), dim)) elif '-std' in arguments: from numpy import std dim = dim_list.index(arguments['-std']) if dim + 1 > len(np.shape(data)): # in case input volume is 3d and dim=t data = data[..., np.newaxis] data_out = std(data, dim, ddof=1) elif "-smooth" in arguments: sigmas = arguments["-smooth"] if len(sigmas) == 1: sigmas = [sigmas[0] for i in range(len(data.shape))] elif len(sigmas) != len(data.shape): printv(parser.usage.generate(error='ERROR: -smooth need the same number of inputs as the number of image dimension OR only one input')) # adjust sigma based on voxel size sigmas = [sigmas[i] / dim[i + 4] for i in range(3)] # smooth data data_out = smooth(data, sigmas) elif '-dilate' in arguments: data_out = dilate(data, arguments['-dilate']) elif '-erode' in arguments: data_out = erode(data, arguments['-erode']) elif '-denoise' in arguments: # parse denoising arguments p, b = 1, 5 # default arguments list_denoise = arguments['-denoise'] for i in list_denoise: if 'p' in i: p = int(i.split('=')[1]) if 'b' in i: b = int(i.split('=')[1]) data_out = denoise_nlmeans(data, patch_radius=p, block_radius=b) elif '-symmetrize' in arguments: data_out = (data + data[list(range(data.shape[0] - 1, -1, -1)), :, :]) / float(2) elif '-mi' in arguments: # input 1 = from flag -i --> im # input 2 = from flag -mi im_2 = Image(arguments['-mi']) compute_similarity(im.data, im_2.data, fname_out, metric='mi', verbose=verbose) data_out = None elif '-minorm' in arguments: im_2 = Image(arguments['-minorm']) compute_similarity(im.data, im_2.data, fname_out, metric='minorm', verbose=verbose) data_out = None elif '-corr' in arguments: # input 1 = from flag -i --> im # input 2 = from flag -mi im_2 = Image(arguments['-corr']) compute_similarity(im.data, im_2.data, fname_out, metric='corr', verbose=verbose) data_out = None # if no flag is set else: data_out = None printv(parser.usage.generate(error='ERROR: you need to specify an operation to do on the input image')) if data_out is not None: # Write output nii_out = Image(fname_in) # use header of input file nii_out.data = data_out nii_out.save(fname_out, dtype=output_type) # TODO: case of multiple outputs # assert len(data_out) == n_out # if n_in == n_out: # for im_in, d_out, fn_out in zip(nii, data_out, fname_out): # im_in.data = d_out # im_in.absolutepath = fn_out # if "-w" in arguments: # im_in.hdr.set_intent('vector', (), '') # im_in.save() # elif n_out == 1: # nii[0].data = data_out[0] # nii[0].absolutepath = fname_out[0] # if "-w" in arguments: # nii[0].hdr.set_intent('vector', (), '') # nii[0].save() # elif n_out > n_in: # for dat_out, name_out in zip(data_out, fname_out): # im_out = nii[0].copy() # im_out.data = dat_out # im_out.absolutepath = name_out # if "-w" in arguments: # im_out.hdr.set_intent('vector', (), '') # im_out.save() # else: # printv(parser.usage.generate(error='ERROR: not the correct numbers of inputs and outputs')) # display message if data_out is not None: sct.display_viewer_syntax([fname_out], verbose=verbose) else: printv('\nDone! File created: ' + fname_out, verbose, 'info')
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 = change_type(im, "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, batch_size=BATCH_SIZE) # 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]) if not np.any(data): sct.log.error( '\nSpinal cord was not detected using "-centerline cnn". Please try another "-centerline" method.\n' ) sys.exit(1) im_out.data = data im_out.save(filename_out) 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 main(args = None): orientation = '' change_header = '' fname_out = '' if not args: args = sys.argv[1:] # Building the command, do sanity checks parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_in = arguments['-i'] if '-o' in arguments: fname_out = arguments['-o'] if '-s' in arguments: orientation = arguments['-s'] if '-a' in arguments: change_header = arguments['-a'] remove_tmp_files = int(arguments['-r']) verbose = int(arguments['-v']) inversion = False # change orientation # create temporary folder sct.printv('\nCreate temporary folder...', verbose) path_tmp = 'tmp.'+time.strftime("%y%m%d%H%M%S/") status, output = sct.run('mkdir '+path_tmp, verbose) # copy file in temp folder sct.printv('\nCopy files to tmp folder...', verbose) convert(fname_in, path_tmp+'data.nii') # go to temp folder os.chdir(path_tmp) # Get dimensions of data sct.printv('\nGet dimensions of data...', verbose) nx, ny, nz, nt, px, py, pz, pt = Image('data.nii').dim sct.printv(str(nx) + ' x ' + str(ny) + ' x ' + str(nz)+ ' x ' + str(nt), verbose) # if data are 3d, directly set or get orientation if nt == 1: if orientation != '': # set orientation sct.printv('\nChange orientation...', verbose) if change_header == '': set_orientation('data.nii', orientation, 'data_orient.nii') else: set_orientation('data.nii', change_header, 'data_orient.nii', True) else: # get orientation sct.printv('\nGet orientation...', verbose) print get_orientation('data.nii') else: # split along T dimension sct.printv('\nSplit along T dimension...', verbose) from sct_split_data import split_data split_data('data.nii', 3, '_T') if orientation != '': # set orientation sct.printv('\nChange orientation...', verbose) for it in range(nt): file_data_split = 'data_T'+str(it).zfill(4)+'.nii' file_data_split_orient = 'data_orient_T'+str(it).zfill(4)+'.nii' set_orientation(file_data_split, orientation, file_data_split_orient) # Merge files back sct.printv('\nMerge file back...', verbose) from sct_concat_data import concat_data from glob import glob concat_data(glob('data_orient_T*.nii'), 'data_orient.nii', dim=3) else: sct.printv('\nGet orientation...', verbose) print get_orientation('data_T0000.nii') # come back to parent folder os.chdir('..') # Generate output files if orientation != '': # Build fname_out if fname_out == '': path_data, file_data, ext_data = sct.extract_fname(fname_in) fname_out = path_data+file_data+'_'+orientation+ext_data sct.printv('\nGenerate output files...', verbose) sct.generate_output_file(path_tmp+'data_orient.nii', fname_out) # Remove temporary files if remove_tmp_files == 1: sct.printv('\nRemove temporary files...', verbose) sct.run('rm -rf '+path_tmp, verbose)
def spline(folder_mat, nt, nz, verbose, index_b0=[], graph=0): # get path of the toolbox status, path_sct = commands.getstatusoutput('echo $SCT_DIR') # append path that contains scripts, to be able to load modules sys.path.append(path_sct + '/scripts') import sct_utils as sct sct.printv( '\n\n\n------------------------------------------------------------------------------', verbose) sct.printv('Spline Regularization along T: Smoothing Patient Motion...', verbose) file_mat = [[[] for i in range(nz)] for i in range(nt)] for it in range(nt): for iz in range(nz): file_mat[it][iz] = folder_mat + 'mat.T' + str(it) + '_Z' + str( iz) + '.txt' # Copying the existing Matrices to another folder old_mat = folder_mat + 'old/' if not os.path.exists(old_mat): os.makedirs(old_mat) cmd = 'cp ' + folder_mat + '*.txt ' + old_mat status, output = sct.run(cmd, verbose) sct.printv('\nloading matrices...', verbose) X = [[[] for i in range(nt)] for i in range(nz)] Y = [[[] for i in range(nt)] for i in range(nz)] X_smooth = [[[] for i in range(nt)] for i in range(nz)] Y_smooth = [[[] for i in range(nt)] for i in range(nz)] for iz in range(nz): for it in range(nt): file = open(file_mat[it][iz]) Matrix = np.loadtxt(file) file.close() X[iz][it] = Matrix[0, 3] Y[iz][it] = Matrix[1, 3] # Generate motion splines sct.printv('\nGenerate motion splines...', verbose) T = np.arange(nt) if graph: import pylab as pl for iz in range(nz): # frequency = scipy.fftpack.fftfreq(len(X[iz][:]), d=1) # spectrum = np.abs(scipy.fftpack.fft(X[iz][:], n=None, axis=-1, overwrite_x=False)) # Wn = np.amax(frequency)/10 # N = 5 #Order of the filter # b, a = scipy.signal.iirfilter(N, Wn, rp=None, rs=None, btype='low', analog=False, ftype='butter', output='ba') # X_smooth[iz][:] = scipy.signal.filtfilt(b, a, X[iz][:], axis=-1, padtype=None) spline = scipy.interpolate.UnivariateSpline(T, X[iz][:], w=None, bbox=[None, None], k=3, s=None) X_smooth[iz][:] = spline(T) if graph: pl.plot(T, X_smooth[iz][:], label='spline_smoothing') pl.plot(T, X[iz][:], marker='*', linestyle='None', label='original_val') if len(index_b0) != 0: T_b0 = [T[i_b0] for i_b0 in index_b0] X_b0 = [X[iz][i_b0] for i_b0 in index_b0] pl.plot(T_b0, X_b0, marker='D', linestyle='None', color='k', label='b=0') pl.title('X') pl.grid() pl.legend() pl.show() # frequency = scipy.fftpack.fftfreq(len(Y[iz][:]), d=1) # spectrum = np.abs(scipy.fftpack.fft(Y[iz][:], n=None, axis=-1, overwrite_x=False)) # Wn = np.amax(frequency)/10 # N = 5 #Order of the filter # b, a = scipy.signal.iirfilter(N, Wn, rp=None, rs=None, btype='low', analog=False, ftype='butter', output='ba') # Y_smooth[iz][:] = scipy.signal.filtfilt(b, a, Y[iz][:], axis=-1, padtype=None) spline = scipy.interpolate.UnivariateSpline(T, Y[iz][:], w=None, bbox=[None, None], k=3, s=None) Y_smooth[iz][:] = spline(T) if graph: pl.plot(T, Y_smooth[iz][:], label='spline_smoothing') pl.plot(T, Y[iz][:], marker='*', linestyle='None', label='original_val') if len(index_b0) != 0: T_b0 = [T[i_b0] for i_b0 in index_b0] Y_b0 = [Y[iz][i_b0] for i_b0 in index_b0] pl.plot(T_b0, Y_b0, marker='D', linestyle='None', color='k', label='b=0') pl.title('Y') pl.grid() pl.legend() pl.show() # Storing the final Matrices sct.printv('\nStoring the final Matrices...', verbose) for iz in range(nz): for it in range(nt): file = open(file_mat[it][iz]) Matrix = np.loadtxt(file) file.close() Matrix[0, 3] = X_smooth[iz][it] Matrix[1, 3] = Y_smooth[iz][it] file = open(file_mat[it][iz], 'w') np.savetxt(file_mat[it][iz], Matrix, fmt="%s", delimiter=' ', newline='\n') file.close() sct.printv('\n...Done. Patient motion has been smoothed', verbose) sct.printv( '------------------------------------------------------------------------------\n', verbose)
def main(): """Main function.""" parser = get_parser() args = parser.parse_args(args=None if sys.argv[1:] else ['--help']) fname_image = args.i contrast_type = args.c ctr_algo = args.centerline brain_bool = bool(args.brain) if args.brain is None and contrast_type in ['t2s', 't2_ax']: brain_bool = False output_folder = args.ofolder if ctr_algo == 'file' and args.file_centerline is None: sct.printv( 'Please use the flag -file_centerline to indicate the centerline filename.', 1, 'error') sys.exit(1) if args.file_centerline is not None: manual_centerline_fname = args.file_centerline ctr_algo = 'file' else: manual_centerline_fname = None remove_temp_files = args.r verbose = args.v sct.init_sct(log_level=verbose, update=True) # Update log level algo_config_stg = '\nMethod:' algo_config_stg += '\n\tCenterline algorithm: ' + str(ctr_algo) algo_config_stg += '\n\tAssumes brain section included in the image: ' + str( brain_bool) + '\n' sct.printv(algo_config_stg) # Segment image from spinalcordtoolbox.image import Image from spinalcordtoolbox.deepseg_lesion.core import deep_segmentation_MSlesion im_image = Image(fname_image) im_seg, im_labels_viewer, im_ctr = deep_segmentation_MSlesion( im_image, contrast_type, ctr_algo=ctr_algo, ctr_file=manual_centerline_fname, brain_bool=brain_bool, remove_temp_files=remove_temp_files, verbose=verbose) # Save segmentation fname_seg = os.path.abspath( os.path.join( output_folder, sct.extract_fname(fname_image)[1] + '_lesionseg' + sct.extract_fname(fname_image)[2])) im_seg.save(fname_seg) if ctr_algo == 'viewer': # Save labels fname_labels = os.path.abspath( os.path.join( output_folder, sct.extract_fname(fname_image)[1] + '_labels-centerline' + sct.extract_fname(fname_image)[2])) im_labels_viewer.save(fname_labels) if verbose == 2: # Save ctr fname_ctr = os.path.abspath( os.path.join( output_folder, sct.extract_fname(fname_image)[1] + '_centerline' + sct.extract_fname(fname_image)[2])) im_ctr.save(fname_ctr) sct.display_viewer_syntax([fname_image, fname_seg], colormaps=['gray', 'red'], opacities=['', '0.7'])
def resample(): fsloutput = 'export FSLOUTPUTTYPE=NIFTI; ' # for faster processing, all outputs are in NIFTI ext = '.nii' # display usage if a mandatory argument is not provided if param.fname_data == '' or param.factor == '': sct.printv('\nERROR: All mandatory arguments are not provided. See usage (add -h).\n', 1, 'error') # check existence of input files sct.printv('\nCheck existence of input files...', param.verbose) sct.check_file_exist(param.fname_data, param.verbose) # extract resampling factor sct.printv('\nParse resampling factor...', param.verbose) factor_split = param.factor.split('x') factor = [float(factor_split[i]) for i in range(len(factor_split))] # check if it has three values if not len(factor) == 3: sct.printv('\nERROR: factor should have three dimensions. E.g., 2x2x1.\n', 1, 'error') else: fx, fy, fz = [float(factor_split[i]) for i in range(len(factor_split))] # check interpolation if param.interpolation not in ['NearestNeighbor','Linear','Cubic','Sinc','Gaussian']: sct.printv('\nERROR: interpolation should be one of those:NearestNeighbor|Linear|Cubic|Sinc|Gaussian.\n', 1, 'error') # display input parameters sct.printv('\nInput parameters:', param.verbose) sct.printv(' data ..................'+param.fname_data, param.verbose) sct.printv(' resampling factor .....'+param.factor, param.verbose) # Extract path/file/extension path_data, file_data, ext_data = sct.extract_fname(param.fname_data) path_out, file_out, ext_out = '', file_data, ext_data # create temporary folder sct.printv('\nCreate temporary folder...', param.verbose) path_tmp = sct.slash_at_the_end('tmp.'+time.strftime("%y%m%d%H%M%S"), 1) sct.run('mkdir '+path_tmp, param.verbose) # Copying input data to tmp folder and convert to nii # NB: cannot use c3d here because c3d cannot convert 4D data. sct.printv('\nCopying input data to tmp folder and convert to nii...', param.verbose) sct.run('cp '+param.fname_data+' '+path_tmp+'data'+ext_data, param.verbose) # go to tmp folder os.chdir(path_tmp) # convert to nii format convert('data'+ext_data, 'data.nii') # Get dimensions of data sct.printv('\nGet dimensions of data...', param.verbose) nx, ny, nz, nt, px, py, pz, pt = Image('data.nii').dim sct.printv(' ' + str(nx) + ' x ' + str(ny) + ' x ' + str(nz)+ ' x ' + str(nt), param.verbose) dim = 4 # by default, will be adjusted later if nt == 1: dim = 3 if nz == 1: dim = 2 sct.run('ERROR (sct_resample): Dimension of input data is different from 3 or 4. Exit program', param.verbose, 'error') # Calculate new dimensions sct.printv('\nCalculate new dimensions...', param.verbose) nx_new = int(round(nx*fx)) ny_new = int(round(ny*fy)) nz_new = int(round(nz*fz)) sct.printv(' ' + str(nx_new) + ' x ' + str(ny_new) + ' x ' + str(nz_new)+ ' x ' + str(nt), param.verbose) # if dim=4, split data if dim == 4: # Split into T dimension sct.printv('\nSplit along T dimension...', param.verbose) from sct_split_data import split_data split_data('data.nii', 3, '_T') elif dim == 3: # rename file to have compatible code with 4d status, output = sct.run('cp data.nii data_T0000.nii', param.verbose) for it in range(nt): # identify current volume file_data_splitT = 'data_T'+str(it).zfill(4) file_data_splitT_resample = file_data_splitT+'r' # resample volume sct.printv(('\nResample volume '+str((it+1))+'/'+str(nt)+':'), param.verbose) sct.run('isct_c3d '+file_data_splitT+ext+' -interpolation '+param.interpolation+' -resample '+str(nx_new)+'x'+str(ny_new)+'x'+str(nz_new)+'vox -o '+file_data_splitT_resample+ext) # pad data (for ANTs) # # TODO: check if need to pad also for the estimate_and_apply # if program == 'ants' and todo == 'estimate' and slicewise == 0: # sct.run('isct_c3d '+file_data_splitT_num[it]+' -pad 0x0x3vox 0x0x3vox 0 -o '+file_data_splitT_num[it]+'_pad.nii') # file_data_splitT_num[it] = file_data_splitT_num[it]+'_pad' # merge data back along T file_data_resample = file_data+param.file_suffix sct.printv('\nMerge data back along T...', param.verbose) from sct_concat_data import concat_data import glob concat_data(glob.glob('data_T*r.nii'), file_data_resample, dim=3) # come back to parent folder os.chdir('..') # Generate output files sct.printv('\nGenerate output files...', param.verbose) if not param.fname_out: param.fname_out = path_out+file_out+param.file_suffix+ext_out sct.generate_output_file(path_tmp+file_data_resample+ext, param.fname_out) # Remove temporary files if param.remove_tmp_files == 1: print('\nRemove temporary files...') sct.run('rm -rf '+path_tmp, param.verbose) # to view results sct.printv('\nDone! To view results, type:', param.verbose) sct.printv('fslview '+param.fname_out+' &', param.verbose, 'info') print
def moco(param): # retrieve parameters fsloutput = 'export FSLOUTPUTTYPE=NIFTI; ' # for faster processing, all outputs are in NIFTI file_data = param.file_data file_target = param.file_target folder_mat = sct.slash_at_the_end(param.mat_moco, 1) # output folder of mat file todo = param.todo suffix = param.suffix #file_schedule = param.file_schedule verbose = param.verbose ext = '.nii' # get path of the toolbox status, path_sct = commands.getstatusoutput('echo $SCT_DIR') # sct.printv(arguments) sct.printv('\nInput parameters:', param.verbose) sct.printv(' Input file ............' + file_data, param.verbose) sct.printv(' Reference file ........' + file_target, param.verbose) sct.printv(' Polynomial degree .....' + param.poly, param.verbose) sct.printv(' Smoothing kernel ......' + param.smooth, param.verbose) sct.printv(' Gradient step .........' + param.gradStep, param.verbose) sct.printv(' Metric ................' + param.metric, param.verbose) sct.printv(' Sampling ..............' + param.sampling, param.verbose) sct.printv(' Todo ..................' + todo, param.verbose) sct.printv(' Mask .................' + param.fname_mask, param.verbose) sct.printv(' Output mat folder .....' + folder_mat, param.verbose) # create folder for mat files sct.create_folder(folder_mat) # Get size of data sct.printv('\nGet dimensions data...', verbose) data_im = Image(file_data + ext) nx, ny, nz, nt, px, py, pz, pt = data_im.dim sct.printv(('.. ' + str(nx) + ' x ' + str(ny) + ' x ' + str(nz) + ' x ' + str(nt)), verbose) # copy file_target to a temporary file sct.printv('\nCopy file_target to a temporary file...', verbose) sct.run('cp ' + file_target + ext + ' target.nii') file_target = 'target' # Split data along T dimension sct.printv('\nSplit data along T dimension...', verbose) data_split_list = split_data(data_im, dim=3) for im in data_split_list: im.save() file_data_splitT = file_data + '_T' # Motion correction: initialization index = np.arange(nt) file_data_splitT_num = [] file_data_splitT_moco_num = [] failed_transfo = [0 for i in range(nt)] file_mat = [[] for i in range(nt)] # Motion correction: Loop across T for indice_index in range(nt): # create indices and display stuff it = index[indice_index] file_data_splitT_num.append(file_data_splitT + str(it).zfill(4)) file_data_splitT_moco_num.append(file_data + suffix + '_T' + str(it).zfill(4)) sct.printv(('\nVolume ' + str((it)) + '/' + str(nt - 1) + ':'), verbose) file_mat[it] = folder_mat + 'mat.T' + str(it) # run 3D registration failed_transfo[it] = register(param, file_data_splitT_num[it], file_target, file_mat[it], file_data_splitT_moco_num[it]) # average registered volume with target image # N.B. use weighted averaging: (target * nb_it + moco) / (nb_it + 1) if param.iterative_averaging and indice_index < 10 and failed_transfo[ it] == 0 and not param.todo == 'apply': sct.run('sct_maths -i ' + file_target + ext + ' -mul ' + str(indice_index + 1) + ' -o ' + file_target + ext) sct.run('sct_maths -i ' + file_target + ext + ' -add ' + file_data_splitT_moco_num[it] + ext + ' -o ' + file_target + ext) sct.run('sct_maths -i ' + file_target + ext + ' -div ' + str(indice_index + 2) + ' -o ' + file_target + ext) # Replace failed transformation with the closest good one sct.printv(('\nReplace failed transformations...'), verbose) fT = [i for i, j in enumerate(failed_transfo) if j == 1] gT = [i for i, j in enumerate(failed_transfo) if j == 0] for it in range(len(fT)): abs_dist = [abs(gT[i] - fT[it]) for i in range(len(gT))] if not abs_dist == []: index_good = abs_dist.index(min(abs_dist)) sct.printv( ' transfo #' + str(fT[it]) + ' --> use transfo #' + str(gT[index_good]), verbose) # copy transformation sct.run('cp ' + file_mat[gT[index_good]] + 'Warp.nii.gz' + ' ' + file_mat[fT[it]] + 'Warp.nii.gz') # apply transformation sct.run( 'sct_apply_transfo -i ' + file_data_splitT_num[fT[it]] + '.nii -d ' + file_target + '.nii -w ' + file_mat[fT[it]] + 'Warp.nii.gz' + ' -o ' + file_data_splitT_moco_num[fT[it]] + '.nii' + ' -x ' + param.interp, verbose) else: # exit program if no transformation exists. sct.printv( '\nERROR in ' + os.path.basename(__file__) + ': No good transformation exist. Exit program.\n', verbose, 'error') sys.exit(2) # Merge data along T file_data_moco = file_data + suffix if todo != 'estimate': sct.printv('\nMerge data back along T...', verbose) from sct_image import concat_data # im_list = [] fname_list = [] for indice_index in range(len(index)): # im_list.append(Image(file_data_splitT_moco_num[indice_index] + ext)) fname_list.append(file_data_splitT_moco_num[indice_index] + ext) im_out = concat_data(fname_list, 3) im_out.setFileName(file_data_moco + ext) im_out.save() # delete file target.nii (to avoid conflict if this function is run another time) sct.printv('\nRemove temporary file...', verbose) # os.remove('target.nii') sct.run('rm target.nii')
def main(): # Initialization to defaults parameters fname_data = '' # data is empty by default path_label = '' # empty by default method = param.method # extraction mode by default labels_of_interest = param.labels_of_interest slices_of_interest = param.slices_of_interest vertebral_levels = param.vertebral_levels average_all_labels = param.average_all_labels fname_output = param.fname_output fname_vertebral_labeling = param.fname_vertebral_labeling fname_normalizing_label = '' # optional then default is empty normalization_method = '' # optional then default is empty actual_vert_levels = None # variable used in case the vertebral levels asked by the user don't correspond exactly to the vertebral levels available in the metric data warning_vert_levels = None # variable used to warn the user in case the vertebral levels he asked don't correspond exactly to the vertebral levels available in the metric data verbose = param.verbose flag_h = 0 ml_clusters = param.ml_clusters adv_param = param.adv_param adv_param_user = '' # Parameters for debug mode if param.debug: print '\n*** WARNING: DEBUG MODE ON ***\n' status, path_sct_data = commands.getstatusoutput( 'echo $SCT_TESTING_DATA_DIR') fname_data = '/Users/julien/data/temp/sct_example_data/mt/mtr.nii.gz' path_label = '/Users/julien/data/temp/sct_example_data/mt/label/atlas/' method = 'map' ml_clusters = '0:29,30,31' labels_of_interest = '0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29' slices_of_interest = '' vertebral_levels = '' average_all_labels = 1 fname_normalizing_label = '' #path_sct+'/testing/data/errsm_23/mt/label/template/MNI-Poly-AMU_CSF.nii.gz' normalization_method = '' #'whole' else: # Check input parameters try: opts, args = getopt.getopt( sys.argv[1:], 'haf:i:l:m:n:o:p:v:w:z:') # define flags except getopt.GetoptError as err: # check if the arguments are defined print str(err) # error usage() # display usage if not opts: usage() for opt, arg in opts: # explore flags if opt in '-a': average_all_labels = 1 elif opt in '-f': path_label = os.path.abspath(arg) # save path of labels folder elif opt == '-h': # help option flag_h = 1 elif opt in '-i': fname_data = arg elif opt in '-l': labels_of_interest = arg elif opt in '-m': # method for metric extraction method = arg elif opt in '-n': # filename of the label by which the user wants to normalize fname_normalizing_label = arg elif opt in '-o': # output option fname_output = arg # fname of output file elif opt in '-p': adv_param_user = arg elif opt in '-v': # vertebral levels option, if the user wants to average the metric across specific vertebral levels vertebral_levels = arg elif opt in '-w': # method used for the normalization by the metric estimation into the normalizing label (see flag -n): 'sbs' for slice-by-slice or 'whole' for normalization after estimation in the whole labels normalization_method = arg elif opt in '-z': # slices numbers option slices_of_interest = arg # save labels numbers # Display usage with tract parameters by default in case files aren't chosen in arguments inputs if fname_data == '' or path_label == '' or flag_h: param.path_label = path_label usage() # Check existence of data file sct.printv('\ncheck existence of input files...', verbose) sct.check_file_exist(fname_data) sct.check_folder_exist(path_label) if fname_normalizing_label: sct.check_folder_exist(fname_normalizing_label) # add slash at the end path_label = sct.slash_at_the_end(path_label, 1) # Find path to the vertebral labeling file if vertebral levels were specified by the user if vertebral_levels: if slices_of_interest: # impossible to select BOTH specific slices and specific vertebral levels print '\nERROR: You cannot select BOTH vertebral levels AND slice numbers.' usage() else: fname_vertebral_labeling_list = sct.find_file_within_folder( fname_vertebral_labeling, path_label + '..') if len(fname_vertebral_labeling_list) > 1: print color.red + 'ERROR: More than one file named \'' + fname_vertebral_labeling + ' were found in ' + path_label + '. Exit program.' + color.end sys.exit(2) elif len(fname_vertebral_labeling_list) == 0: print color.red + 'ERROR: No file named \'' + fname_vertebral_labeling + ' were found in ' + path_label + '. Exit program.' + color.end sys.exit(2) else: fname_vertebral_labeling = os.path.abspath( fname_vertebral_labeling_list[0]) # Check input parameters check_method(method, fname_normalizing_label, normalization_method) # parse argument for param if not adv_param_user == '': adv_param = adv_param_user.replace(' ', '').split( ',') # remove spaces and parse with comma del adv_param_user # clean variable # TODO: check integrity of input # Extract label info label_id, label_name, label_file = read_label_file(path_label, param.file_info_label) nb_labels_total = len(label_id) # check consistency of label input parameter. label_id_user, average_all_labels = check_labels( labels_of_interest, nb_labels_total, average_all_labels, method) # If 'labels_of_interest' is empty, then # 'label_id_user' contains the index of all labels in the file info_label.txt # print parameters print '\nChecked parameters:' print ' data ...................... ' + fname_data print ' folder label .............. ' + path_label print ' selected labels ........... ' + str(label_id_user) print ' estimation method ......... ' + method print ' slices of interest ........ ' + slices_of_interest print ' vertebral levels .......... ' + vertebral_levels print ' vertebral labeling file.... ' + fname_vertebral_labeling print ' advanced parameters ....... ' + str(adv_param) # Check if the orientation of the data is RPI orientation_data = get_orientation(fname_data) # If orientation is not RPI, change to RPI if orientation_data != 'RPI': sct.printv( '\nCreate temporary folder to change the orientation of the NIFTI files into RPI...', verbose) path_tmp = sct.slash_at_the_end('tmp.' + time.strftime("%y%m%d%H%M%S"), 1) sct.create_folder(path_tmp) # change orientation and load data sct.printv('\nChange image orientation and load it...', verbose) data = nib.load( set_orientation(fname_data, 'RPI', path_tmp + 'orient_data.nii')).get_data() # Do the same for labels sct.printv('\nChange labels orientation and load them...', verbose) labels = np.empty([nb_labels_total], dtype=object) # labels(nb_labels_total, x, y, z) for i_label in range(0, nb_labels_total): labels[i_label] = nib.load( set_orientation(path_label + label_file[i_label], 'RPI', path_tmp + 'orient_' + label_file[i_label])).get_data() if fname_normalizing_label: # if the "normalization" option is wanted, normalizing_label = np.empty( [1], dtype=object ) # choose this kind of structure so as to keep easily the # compatibility with the rest of the code (dimensions: (1, x, y, z)) normalizing_label[0] = nib.load( set_orientation(fname_normalizing_label, 'RPI', path_tmp + 'orient_normalizing_volume.nii')).get_data() if vertebral_levels: # if vertebral levels were selected, data_vertebral_labeling = nib.load( set_orientation( fname_vertebral_labeling, 'RPI', path_tmp + 'orient_vertebral_labeling.nii.gz')).get_data() # Remove the temporary folder used to change the NIFTI files orientation into RPI sct.printv('\nRemove the temporary folder...', verbose) status, output = commands.getstatusoutput('rm -rf ' + path_tmp) else: # Load image sct.printv('\nLoad image...', verbose) data = nib.load(fname_data).get_data() # Load labels sct.printv('\nLoad labels...', verbose) labels = np.empty([nb_labels_total], dtype=object) # labels(nb_labels_total, x, y, z) for i_label in range(0, nb_labels_total): labels[i_label] = nib.load(path_label + label_file[i_label]).get_data() if fname_normalizing_label: # if the "normalization" option is wanted, normalizing_label = np.empty( [1], dtype=object ) # choose this kind of structure so as to keep easily the # compatibility with the rest of the code (dimensions: (1, x, y, z)) normalizing_label[0] = nib.load(fname_normalizing_label).get_data( ) # load the data of the normalizing label if vertebral_levels: # if vertebral levels were selected, data_vertebral_labeling = nib.load( fname_vertebral_labeling).get_data() # Change metric data type into floats for future manipulations (normalization) data = np.float64(data) # Get dimensions of data sct.printv('\nGet dimensions of data...', verbose) nx, ny, nz = data.shape sct.printv(' ' + str(nx) + ' x ' + str(ny) + ' x ' + str(nz), verbose) # Get dimensions of labels sct.printv('\nGet dimensions of label...', verbose) nx_atlas, ny_atlas, nz_atlas = labels[0].shape sct.printv( '.. ' + str(nx_atlas) + ' x ' + str(ny_atlas) + ' x ' + str(nz_atlas) + ' x ' + str(nb_labels_total), verbose) # Check dimensions consistency between atlas and data if (nx, ny, nz) != (nx_atlas, ny_atlas, nz_atlas): print '\nERROR: Metric data and labels DO NOT HAVE SAME DIMENSIONS.' sys.exit(2) # Update the flag "slices_of_interest" according to the vertebral levels selected by user (if it's the case) if vertebral_levels: slices_of_interest, actual_vert_levels, warning_vert_levels = \ get_slices_matching_with_vertebral_levels(data, vertebral_levels, data_vertebral_labeling) # select slice of interest by cropping data and labels if slices_of_interest: data = remove_slices(data, slices_of_interest) for i_label in range(0, nb_labels_total): labels[i_label] = remove_slices(labels[i_label], slices_of_interest) if fname_normalizing_label: # if the "normalization" option was selected, normalizing_label[0] = remove_slices(normalizing_label[0], slices_of_interest) # if user wants to get unique value across labels, then combine all labels together if average_all_labels == 1: sum_labels_user = np.sum( labels[label_id_user]) # sum the labels selected by user if method == 'ml' or method == 'map': # in case the maximum likelihood and the average across different labels are wanted labels_tmp = np.empty([nb_labels_total - len(label_id_user) + 1], dtype=object) labels = np.delete( labels, label_id_user) # remove the labels selected by user labels_tmp[ 0] = sum_labels_user # put the sum of the labels selected by user in first position of the tmp # variable for i_label in range(1, len(labels_tmp)): labels_tmp[i_label] = labels[ i_label - 1] # fill the temporary array with the values of the non-selected labels labels = labels_tmp # replace the initial labels value by the updated ones (with the summed labels) del labels_tmp # delete the temporary labels else: # in other cases than the maximum likelihood, we can remove other labels (not needed for estimation) labels = np.empty(1, dtype=object) labels[ 0] = sum_labels_user # we create a new label array that includes only the summed labels if fname_normalizing_label: # if the "normalization" option is wanted sct.printv('\nExtract normalization values...', verbose) if normalization_method == 'sbs': # case: the user wants to normalize slice-by-slice for z in range(0, data.shape[-1]): normalizing_label_slice = np.empty( [1], dtype=object ) # in order to keep compatibility with the function # 'extract_metric_within_tract', define a new array for the slice z of the normalizing labels normalizing_label_slice[0] = normalizing_label[0][..., z] metric_normalizing_label = extract_metric_within_tract( data[..., z], normalizing_label_slice, method, 0) # estimate the metric mean in the normalizing label for the slice z if metric_normalizing_label[0][0] != 0: data[..., z] = data[..., z] / metric_normalizing_label[0][ 0] # divide all the slice z by this value elif normalization_method == 'whole': # case: the user wants to normalize after estimations in the whole labels metric_mean_norm_label, metric_std_norm_label = extract_metric_within_tract( data, normalizing_label, method, param.verbose) # mean and std are lists # identify cluster for each tract (for use with robust ML) ml_clusters_array = get_clusters(ml_clusters, labels) # extract metrics within labels sct.printv('\nExtract metric within labels...', verbose) metric_mean, metric_std = extract_metric_within_tract( data, labels, method, verbose, ml_clusters_array, adv_param) # mean and std are lists if fname_normalizing_label and normalization_method == 'whole': # case: user wants to normalize after estimations in the whole labels metric_mean, metric_std = np.divide(metric_mean, metric_mean_norm_label), np.divide( metric_std, metric_std_norm_label) # update label name if average if average_all_labels == 1: label_name[0] = 'AVERAGED' + ' -'.join( label_name[i] for i in label_id_user) # concatenate the names of the # labels selected by the user if the average tag was asked label_id_user = [ 0 ] # update "label_id_user" to select the "averaged" label (which is in first position) metric_mean = metric_mean[label_id_user] metric_std = metric_std[label_id_user] # display metrics sct.printv('\nEstimation results:', 1) for i in range(0, metric_mean.size): sct.printv( str(label_id_user[i]) + ', ' + str(label_name[label_id_user[i]]) + ': ' + str(metric_mean[i]) + ' +/- ' + str(metric_std[i]), 1, 'info') # save and display metrics save_metrics(label_id_user, label_name, slices_of_interest, metric_mean, metric_std, fname_output, fname_data, method, fname_normalizing_label, actual_vert_levels, warning_vert_levels)
def main(): """Main function.""" parser = get_parser() args = parser.parse_args(args=None if sys.argv[1:] else ['--help']) fname_image = os.path.abspath(args.i) contrast_type = args.c ctr_algo = args.centerline if args.brain is None: if contrast_type in ['t2s', 'dwi']: brain_bool = False if contrast_type in ['t1', 't2']: brain_bool = True else: brain_bool = bool(args.brain) if bool(args.brain) and ctr_algo == 'svm': sct.printv('Please only use the flag "-brain 1" with "-centerline cnn".', 1, 'warning') sys.exit(1) kernel_size = args.kernel if kernel_size == '3d' and contrast_type == 'dwi': kernel_size = '2d' sct.printv('3D kernel model for dwi contrast is not available. 2D kernel model is used instead.', type="warning") if ctr_algo == 'file' and args.file_centerline is None: sct.printv('Please use the flag -file_centerline to indicate the centerline filename.', 1, 'warning') sys.exit(1) if args.file_centerline is not None: manual_centerline_fname = args.file_centerline ctr_algo = 'file' else: manual_centerline_fname = None threshold = args.thr if threshold is not None: if threshold > 1.0 or (threshold < 0.0 and threshold != -1.0): raise SyntaxError("Threshold should be between 0 and 1, or equal to -1 (no threshold)") remove_temp_files = args.r verbose = args.v sct.init_sct(log_level=verbose, update=True) # Update log level path_qc = args.qc qc_dataset = args.qc_dataset qc_subject = args.qc_subject output_folder = args.ofolder # check if input image is 2D or 3D sct.check_dim(fname_image, dim_lst=[2, 3]) # Segment image from spinalcordtoolbox.image import Image from spinalcordtoolbox.deepseg_sc.core import deep_segmentation_spinalcord from spinalcordtoolbox.reports.qc import generate_qc im_image = Image(fname_image) # note: below we pass im_image.copy() otherwise the field absolutepath becomes None after execution of this function im_seg, im_image_RPI_upsamp, im_seg_RPI_upsamp = \ deep_segmentation_spinalcord(im_image.copy(), contrast_type, ctr_algo=ctr_algo, ctr_file=manual_centerline_fname, brain_bool=brain_bool, kernel_size=kernel_size, threshold_seg=threshold, remove_temp_files=remove_temp_files, verbose=verbose) # Save segmentation fname_seg = os.path.abspath(os.path.join(output_folder, sct.extract_fname(fname_image)[1] + '_seg' + sct.extract_fname(fname_image)[2])) im_seg.save(fname_seg) # Generate QC report if path_qc is not None: generate_qc(fname_image, fname_seg=fname_seg, args=sys.argv[1:], path_qc=os.path.abspath(path_qc), dataset=qc_dataset, subject=qc_subject, process='sct_deepseg_sc') sct.display_viewer_syntax([fname_image, fname_seg], colormaps=['gray', 'red'], opacities=['', '0.7'])
def get_slices_matching_with_vertebral_levels(metric_data, vertebral_levels, data_vertebral_labeling): sct.printv('\nFind slices corresponding to vertebral levels...', param.verbose) # Convert the selected vertebral levels chosen into a 2-element list [start_level end_level] vert_levels_list = [int(x) for x in vertebral_levels.split(':')] # If only one vertebral level was selected (n), consider as n:n if len(vert_levels_list) == 1: vert_levels_list = [vert_levels_list[0], vert_levels_list[0]] # Check if there are only two values [start_level, end_level] and if the end level is higher than the start level if (len(vert_levels_list) > 2) or (vert_levels_list[0] > vert_levels_list[1]): print '\nERROR: "' + vertebral_levels + '" is not correct. Enter format "1:4". Exit program.\n' sys.exit(2) # Extract the vertebral levels available in the metric image vertebral_levels_available = np.array( list(set(data_vertebral_labeling[data_vertebral_labeling > 0]))) # Check if the vertebral levels selected are available warning = [ ] # list of strings gathering the potential following warning(s) to be written in the output .txt file min_vert_level_available = min( vertebral_levels_available) # lowest vertebral level available max_vert_level_available = max( vertebral_levels_available) # highest vertebral level available if vert_levels_list[0] < min_vert_level_available: vert_levels_list[0] = min_vert_level_available warning.append( 'WARNING: the bottom vertebral level you selected is lower to the lowest level available --> ' 'Selected the lowest vertebral level available: ' + str(int(vert_levels_list[0]))) # record the # warning to write it later in the .txt output file print color.yellow + 'WARNING: the bottom vertebral level you selected is lower to the lowest ' \ 'level available \n--> Selected the lowest vertebral level available: '+\ str(int(vert_levels_list[0])) + color.end if vert_levels_list[0] > max_vert_level_available: vert_levels_list[1] = max_vert_level_available warning.append( 'WARNING: the top vertebral level you selected is higher to the highest level available --> ' 'Selected the highest vertebral level available: ' + str(int(vert_levels_list[1]))) # record the # warning to write it later in the .txt output file print color.yellow + 'WARNING: the top vertebral level you selected is higher to the highest ' \ 'level available --> Selected the highest vertebral level available: ' + \ str(int(vert_levels_list[1])) + color.end if vert_levels_list[0] not in vertebral_levels_available: distance = vertebral_levels_available - vert_levels_list[ 0] # relative distance distance_min_among_negative_value = min(abs( distance[distance < 0])) # minimal distance among the negative # relative distances vert_levels_list[0] = vertebral_levels_available[ distance == distance_min_among_negative_value] # element # of the initial list corresponding to this minimal distance warning.append( 'WARNING: the bottom vertebral level you selected is not available --> Selected the nearest ' 'inferior level available: ' + str(int(vert_levels_list[0]))) print color.yellow + 'WARNING: the bottom vertebral level you selected is not available \n--> Selected the ' \ 'nearest inferior level available: '+str(int(vert_levels_list[0])) # record the # warning to write it later in the .txt output file if vert_levels_list[1] not in vertebral_levels_available: distance = vertebral_levels_available - vert_levels_list[ 1] # relative distance distance_min_among_positive_value = min(abs( distance[distance > 0])) # minimal distance among the negative # relative distances vert_levels_list[1] = vertebral_levels_available[ distance == distance_min_among_positive_value] # element # of the initial list corresponding to this minimal distance warning.append( 'WARNING: the top vertebral level you selected is not available --> Selected the nearest superior' ' level available: ' + str(int(vert_levels_list[1])) ) # record the warning to write it later in the .txt output file print color.yellow + 'WARNING: the top vertebral level you selected is not available \n--> Selected the ' \ 'nearest superior level available: ' + str(int(vert_levels_list[1])) # Extract metric data size X, Y, Z [mx, my, mz] = metric_data.shape # Extract vertebral labeling data size X, Y, Z [vx, vy, vz] = data_vertebral_labeling.shape sct.printv(' Check consistency of data size...', param.verbose) # Initialisation of check error flag exit_program = 0 # Check if sizes along X are the same if mx != vx: print '\tERROR: Size of vertebral_labeling.nii.gz along X is not the same as the metric data.' exit_program = 1 # Check if sizes along Y are the same if my != vy: print '\tERROR: Size of vertebral_labeling.nii.gz along Y is not the same as the metric data.' exit_program = 1 # Check if sizes along Z are the same if mz != vz: print '\tERROR: Size of vertebral_labeling.nii.gz along Z is not the same as the metric data.' exit_program = 1 # Exit program if an error was detected if exit_program == 1: print '\nExit program.\n' sys.exit(2) else: print ' OK!' sct.printv(' Find slices corresponding to vertebral levels...', param.verbose) # Extract the X, Y, Z positions of voxels belonging to the first vertebral level X_bottom_level, Y_bottom_level, Z_bottom_level = ( data_vertebral_labeling == vert_levels_list[0]).nonzero() # Record the bottom and top slices of this level slice_min_bottom = min(Z_bottom_level) slice_max_bottom = max(Z_bottom_level) # Extract the X, Y, Z positions of voxels belonging to the last vertebral level X_top_level, Y_top_level, Z_top_level = ( data_vertebral_labeling == vert_levels_list[1]).nonzero() # Record the bottom and top slices of this level slice_min_top = min(Z_top_level) slice_max_top = max(Z_top_level) # Take into account the case where the ordering of the slice is reversed compared to the ordering of the vertebral # levels (usually the case) and if several slices include two different vertebral levels if slice_min_bottom >= slice_min_top or slice_max_bottom >= slice_max_top: slice_min = slice_min_top slice_max = slice_max_bottom else: slice_min = slice_min_bottom slice_max = slice_max_top # display info sct.printv(' ' + str(slice_min) + ':' + str(slice_max), param.verbose) # Return the slice numbers in the right format ("-1" because the function "remove_slices", which runs next, add 1 # to the top slice return str(slice_min) + ':' + str(slice_max), vert_levels_list, warning
def extract_metric_within_tract(data, labels, method, verbose, ml_clusters='', adv_param=[]): """ :data: (nx,ny,nz) numpy array :labels: nlabel tuple of (nx,ny,nz) array """ nb_labels = len(labels) # number of labels # if user asks for binary regions, binarize atlas if method == 'bin': for i in range(0, nb_labels): labels[i][labels[i] < 0.5] = 0 labels[i][labels[i] >= 0.5] = 1 # if user asks for thresholded weighted-average, threshold atlas if method == 'wath': for i in range(0, nb_labels): labels[i][labels[i] < 0.5] = 0 # Select non-zero values in the union of all labels labels_sum = np.sum(labels) ind_positive_labels = labels_sum > ALMOST_ZERO # labels_sum > ALMOST_ZERO # ind_positive_data = data > -9999999999 # data > 0 ind_positive = ind_positive_labels # & ind_positive_data data1d = data[ind_positive] labels2d = np.empty([nb_labels, len(data1d)], dtype=float) for i in range(0, nb_labels): labels2d[i] = labels[i][ind_positive] # # display labels # import matplotlib.pyplot as plt # plt.imshow(labels_sum[:,:,3]) # plt.show() # plt.imshow(data[:,:,3]) # plt.show() # clear memory del data, labels # Display number of non-zero values sct.printv(' Number of non-null voxels: ' + str(len(data1d)), verbose=verbose) # initialization metric_mean = np.empty([nb_labels], dtype=object) metric_std = np.empty([nb_labels], dtype=object) nb_vox = len(data1d) # Estimation with 3-class maximum likelihood if method == 'map': sct.printv('Estimation maximum likelihood within clustered labels...', verbose=verbose) y = data1d # [nb_vox x 1] x = labels2d.T # [nb_vox x nb_labels] # construct matrix with clusters of tracts ml_clusters_unique = np.unique(np.sort(ml_clusters)) nb_clusters = len(ml_clusters_unique) sct.printv(' Number of clusters: ' + str(nb_clusters), verbose=verbose) # initialize cluster matrix x_cluster = np.zeros([nb_vox, nb_clusters]) # loop across clusters for i_cluster in ml_clusters_unique: # find tracts belonging to cluster index_tracts_in_cluster = np.where(ml_clusters == i_cluster)[0] # sum tracts and append to matrix x_cluster[:, i_cluster] = x[:, index_tracts_in_cluster].sum(axis=1) x = x_cluster # estimate values using ML beta = np.dot(np.linalg.pinv(np.dot(x.T, x)), np.dot(x.T, y)) # beta = (Xt . X)-1 . Xt . y # display results sct.printv(' Estimated beta per cluster: ' + str(beta), verbose=verbose) # Estimation with weighted average (also works for binary) if method == 'wa' or method == 'bin' or method == 'wath': for i_label in range(0, nb_labels): # check if all labels are equal to zero if sum(labels2d[i_label, :]) == 0: print 'WARNING: labels #' + str( i_label ) + ' contains only null voxels. Mean and std are set to 0.' metric_mean[i_label] = 0 metric_std[i_label] = 0 else: # estimate the weighted average metric_mean[i_label] = sum( data1d * labels2d[i_label, :]) / sum(labels2d[i_label, :]) # estimate the biased weighted standard deviation metric_std[i_label] = np.sqrt( sum(labels2d[i_label, :] * (data1d - metric_mean[i_label])**2) / sum(labels2d[i_label, :])) # Estimation with maximum likelihood if method == 'ml': y = data1d # [nb_vox x 1] x = labels2d.T # [nb_vox x nb_labels] beta = np.dot(np.linalg.pinv(np.dot(x.T, x)), np.dot(x.T, y)) # beta = (Xt . X)-1 . Xt . y #beta, residuals, rank, singular_value = np.linalg.lstsq(np.dot(x.T, x), np.dot(x.T, y), rcond=-1) #beta, residuals, rank, singular_value = np.linalg.lstsq(x, y) #print beta, residuals, rank, singular_value for i_label in range(0, nb_labels): metric_mean[i_label] = beta[i_label] metric_std[ i_label] = 0 # need to assign a value for writing output file # Estimation with maximum a posteriori (map) if method == 'map': # perc_var_label = int(adv_param[0])^2 # variance within label, in percentage of the mean (mean is estimated using cluster-based ML) var_label = int(adv_param[0]) ^ 2 # variance within label var_noise = int( adv_param[1]) ^ 2 # variance of the noise (assumed Gaussian) y = data1d # [nb_vox x 1] x = labels2d.T # [nb_vox x nb_labels] # construct beta0 beta0 = np.zeros(nb_labels) for i_cluster in range(nb_clusters): beta0[np.where(ml_clusters == i_cluster)[0]] = beta[i_cluster] # construct covariance matrix (variance between tracts). For simplicity, we set it to be the identity. Rlabel = np.diag(np.ones(nb_labels)) # Vlabel = np.diag(np.ones(nb_labels) * var_label) # Vlabel = np.diag(beta0 * perc_var_label * 0.01) # [nb_labels x nb_labels] # construct noise matrix # Vnoise = np.diag(np.ones(nb_labels) * var_noise) # beta = beta0 + (Xt . X + var_noise/Var_label * Rlabel^-1)^-1 . Xt . ( y - X . beta0 ) # beta = beta0 + A . B . C # A = np.linalg.pinv(np.dot(x.T, x) + np.dot(Vnoise, np.linalg.pinv(Vlabel))) A = np.linalg.pinv( np.dot(x.T, x) + np.linalg.pinv(Rlabel) * var_noise / var_label) B = x.T C = y - np.dot(x, beta0) beta = beta0 + np.dot(A, np.dot(B, C)) for i_label in range(0, nb_labels): metric_mean[i_label] = beta[i_label] metric_std[ i_label] = 0 # need to assign a value for writing output file return metric_mean, metric_std
def main(): # Default params param = Param() # Get parser info parser = get_parser() arguments = parser.parse_args(args=None if sys.argv[1:] else ['--help']) fname_data = arguments.i if arguments.m is not None: fname_mask = arguments.m else: fname_mask = '' method = arguments.method if arguments.vol is not None: index_vol_user = arguments.vol else: index_vol_user = '' verbose = arguments.v sct.init_sct(log_level=verbose, update=True) # Update log level # Check parameters if method == 'diff': if not fname_mask: sct.printv('You need to provide a mask with -method diff. Exit.', 1, type='error') # Load data and orient to RPI im_data = Image(fname_data).change_orientation('RPI') data = im_data.data if fname_mask: mask = Image(fname_mask).change_orientation('RPI').data # Retrieve selected volumes if index_vol_user: index_vol = parse_num_list(index_vol_user) else: index_vol = range(data.shape[3]) # Make sure user selected 2 volumes with diff method if method == 'diff': if not len(index_vol) == 2: sct.printv( 'Method "diff" should be used with exactly two volumes (specify with flag "-vol").', 1, 'error') # Compute SNR # NB: "time" is assumed to be the 4th dimension of the variable "data" if method == 'mult': # Compute mean and STD across time data_mean = np.mean(data[:, :, :, index_vol], axis=3) data_std = np.std(data[:, :, :, index_vol], axis=3, ddof=1) # Generate mask where std is different from 0 mask_std_nonzero = np.where(data_std > param.almost_zero) snr_map = np.zeros_like(data_mean) snr_map[mask_std_nonzero] = data_mean[mask_std_nonzero] / data_std[ mask_std_nonzero] # Output SNR map fname_snr = sct.add_suffix(fname_data, '_SNR-' + method) im_snr = empty_like(im_data) im_snr.data = snr_map im_snr.save(fname_snr, dtype=np.float32) # Output non-zero mask fname_stdnonzero = sct.add_suffix(fname_data, '_mask-STD-nonzero' + method) im_stdnonzero = empty_like(im_data) data_stdnonzero = np.zeros_like(data_mean) data_stdnonzero[mask_std_nonzero] = 1 im_stdnonzero.data = data_stdnonzero im_stdnonzero.save(fname_stdnonzero, dtype=np.float32) # Compute SNR in ROI if fname_mask: mean_in_roi = np.average(data_mean[mask_std_nonzero], weights=mask[mask_std_nonzero]) std_in_roi = np.average(data_std[mask_std_nonzero], weights=mask[mask_std_nonzero]) snr_roi = mean_in_roi / std_in_roi # snr_roi = np.average(snr_map[mask_std_nonzero], weights=mask[mask_std_nonzero]) elif method == 'diff': data_2vol = np.take(data, index_vol, axis=3) # Compute mean in ROI data_mean = np.mean(data_2vol, axis=3) mean_in_roi = np.average(data_mean, weights=mask) data_sub = np.subtract(data_2vol[:, :, :, 1], data_2vol[:, :, :, 0]) _, std_in_roi = weighted_avg_and_std(data_sub, mask) # Compute SNR, correcting for Rayleigh noise (see eq. 7 in Dietrich et al.) snr_roi = (2 / np.sqrt(2)) * mean_in_roi / std_in_roi # Display result if fname_mask: sct.printv('\nSNR_' + method + ' = ' + str(snr_roi) + '\n', type='info')
def check_labels(labels_of_interest, nb_labels, average_labels, method): # by default, all labels are selected list_label_id = range(0, nb_labels) if labels_of_interest: # Check if label chosen is in the right format for char in labels_of_interest: if not char in '0123456789,:scwmg': sct.printv( '\nERROR: ' + labels_of_interest + ' is not the correct format to select labels.\n Exit program.\n', type='error') usage() # if spinal cord was selected, need all 32 labels from folder atlas if labels_of_interest == 'sc': if nb_labels < 32 and (method == 'ml' or method == 'map'): sct.printv( '\nERROR: You\'ve asked to extract metric in the all spinal cord using the method ' + method + ' but your atlas folder containing' ' the labels only contains ' + nb_labels + ' labels. You need all 32 labels from the folder /atlas of' ' the SpinalCordToolbox (files WMtract_XX, with XX from 00 to 31).\nExit program.\n\n', type='error') usage() if nb_labels < 31 and (method != 'ml' and method != 'map'): sct.printv( '\nERROR: You\'ve asked to extract metric in the all spinal cord using the method ' + method + ' but your atlas folder containing' ' the labels only contains ' + nb_labels + ' labels. You need all 30 white matter tracts and the gray matter from the folder /atlas of' ' the SpinalCordToolbox (files WMtract_XX, with XX from 00 to 30).\nExit program.\n\n', type='error') usage() else: list_label_id = range(0, 31) average_labels = 1 elif labels_of_interest == 'gm': if nb_labels < 32 and (method == 'ml' or method == 'map'): sct.printv( '\nERROR: You\'ve asked to extract metric in the gray matter using the method ' + method + ' but your atlas folder containing' ' the labels only contains ' + nb_labels + ' labels. You need all 32 labels from the folder /atlas of' ' the SpinalCordToolbox (files WMtract_XX, with XX from 00 to 31).\nExit program.\n\n', type='error') usage() else: list_label_id = [30] elif labels_of_interest == 'wm': if nb_labels < 32 and (method == 'ml' or method == 'map'): sct.printv( '\nERROR: You\'ve asked to extract metric in the white matter using the method ' + method + ' but your atlas folder containing' ' the labels only contains ' + nb_labels + ' labels. You need all 32 labels from the folder /atlas of' ' the SpinalCordToolbox (files WMtract_XX, with XX from 00 to 31).\nExit program.\n\n', type='error') usage() else: list_label_id = range(0, 30) average_labels = 1 elif ':' in labels_of_interest: label_ids_range = [int(x) for x in labels_of_interest.split(':')] if len(label_ids_range) > 2: sct.printv( '\nERROR: label IDs selection must be in format X:Y, with X and Y between 0 and 31.\nExit program.\n\n', type='error') usage() else: label_ids_range.sort() list_label_id = [ int(x) for x in range(label_ids_range[0], label_ids_range[1] + 1) ] else: list_label_id = list( set([int(x) for x in labels_of_interest.split(",")])) # Sort labels ID and remove redundant values list_label_id.sort() list_label_id = list(set(list_label_id)) return list_label_id, average_labels
def main(args=None): # check user arguments if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) input_filename = arguments['-i'] input_fname_output = None input_fname_ref = None input_cross_radius = 5 input_dilate = False input_coordinates = None vertebral_levels = None value = None if '-add' in arguments: process_type = 'add' value = arguments['-add'] elif '-create' in arguments: process_type = 'create' input_coordinates = arguments['-create'] elif '-create-add' in arguments: process_type = 'create-add' input_coordinates = arguments['-create-add'] elif '-create-seg' in arguments: process_type = 'create-seg' input_coordinates = arguments['-create-seg'] elif '-cross' in arguments: process_type = 'cross' input_cross_radius = arguments['-cross'] elif '-cubic-to-point' in arguments: process_type = 'cubic-to-point' elif '-display' in arguments: process_type = 'display-voxel' elif '-increment' in arguments: process_type = 'increment' elif '-vert-body' in arguments: process_type = 'vert-body' vertebral_levels = arguments['-vert-body'] # elif '-vert-disc' in arguments: # process_type = 'vert-disc' # vertebral_levels = arguments['-vert-disc'] elif '-vert-continuous' in arguments: process_type = 'vert-continuous' elif '-MSE' in arguments: process_type = 'MSE' input_fname_ref = arguments['-r'] elif '-remove' in arguments: process_type = 'remove' input_fname_ref = arguments['-remove'] elif '-remove-symm' in arguments: process_type = 'remove-symm' input_fname_ref = arguments['-r'] elif '-create-viewer' in arguments: process_type = 'create-viewer' value = arguments['-create-viewer'] else: # no process chosen sct.printv('ERROR: No process was chosen.', 1, 'error') if '-o' in arguments: input_fname_output = arguments['-o'] input_verbose = int(arguments['-v']) processor = ProcessLabels(input_filename, fname_output=input_fname_output, fname_ref=input_fname_ref, cross_radius=input_cross_radius, dilate=input_dilate, coordinates=input_coordinates, verbose=input_verbose, vertebral_levels=vertebral_levels, value=value) processor.process(process_type)
def main(args=None): if args is None: args = sys.argv[1:] parser = get_parser() arguments = parser.parse(args) # assigning variables to arguments input_filename = arguments["-i"] centerline_file = arguments["-s"] sc_straight = SpinalCordStraightener(input_filename, centerline_file) if "-dest" in arguments: sc_straight.use_straight_reference = True sc_straight.centerline_reference_filename = str(arguments["-dest"]) if "-ldisc_input" in arguments: if not sc_straight.use_straight_reference: sct.printv('Warning: discs position are not taken into account if reference is not provided.') else: sc_straight.discs_input_filename = str(arguments["-ldisc_input"]) sc_straight.precision = 4.0 if "-ldisc_dest" in arguments: if not sc_straight.use_straight_reference: sct.printv('Warning: discs position are not taken into account if reference is not provided.') else: sc_straight.discs_ref_filename = str(arguments["-ldisc_dest"]) sc_straight.precision = 4.0 # Handling optional arguments if "-r" in arguments: sc_straight.remove_temp_files = int(arguments["-r"]) if "-x" in arguments: sc_straight.interpolation_warp = str(arguments["-x"]) if "-o" in arguments: sc_straight.output_filename = str(arguments["-o"]) if '-ofolder' in arguments: sc_straight.path_output = arguments['-ofolder'] else: sc_straight.path_output = './' verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level sc_straight.verbose = verbose # if "-cpu-nb" in arguments: # sc_straight.cpu_number = int(arguments["-cpu-nb"]) if '-disable-straight2curved' in arguments: sc_straight.straight2curved = False if '-disable-curved2straight' in arguments: sc_straight.curved2straight = False if '-speed_factor' in arguments: sc_straight.speed_factor = arguments['-speed_factor'] if '-xy_size' in arguments: sc_straight.xy_size = arguments['-xy_size'] if "-param" in arguments: params_user = arguments['-param'] # update registration parameters for param in params_user: param_split = param.split('=') if param_split[0] == 'algo_fitting': sc_straight.algo_fitting = param_split[1] if param_split[0] == 'degree': sc_straight.degree = int(param_split[1]) if param_split[0] == 'precision': sc_straight.precision = float(param_split[1]) if param_split[0] == 'threshold_distance': sc_straight.threshold_distance = float(param_split[1]) if param_split[0] == 'accuracy_results': sc_straight.accuracy_results = int(param_split[1]) if param_split[0] == 'template_orientation': sc_straight.template_orientation = int(param_split[1]) fname_straight = sc_straight.straighten() sct.printv("\nFinished! Elapsed time: {} s".format(sc_straight.elapsed_time), verbose) sct.display_viewer_syntax([fname_straight], verbose=verbose)
def __init__(self, fname_src, fname_transfo, warp_atlas, warp_spinal_levels, folder_out, path_template, verbose): # Initialization self.fname_src = fname_src self.fname_transfo = fname_transfo self.warp_atlas = warp_atlas self.warp_spinal_levels = warp_spinal_levels self.folder_out = folder_out self.path_template = path_template self.folder_template = param.folder_template self.folder_atlas = param.folder_atlas self.folder_spinal_levels = param.folder_spinal_levels self.verbose = verbose # sct.printv(arguments) sct.printv('\nCheck parameters:') sct.printv(' Working directory ........ ' + os.getcwd()) sct.printv(' Destination image ........ ' + self.fname_src) sct.printv(' Warping field ............ ' + self.fname_transfo) sct.printv(' Path template ............ ' + self.path_template) sct.printv(' Output folder ............ ' + self.folder_out + "\n") # create output folder if not os.path.exists(self.folder_out): os.makedirs(self.folder_out) # Warp template objects sct.printv('\nWARP TEMPLATE:', self.verbose) warp_label(self.path_template, self.folder_template, param.file_info_label, self.fname_src, self.fname_transfo, self.folder_out) # Warp atlas if self.warp_atlas == 1: sct.printv('\nWARP ATLAS OF WHITE MATTER TRACTS:', self.verbose) warp_label(self.path_template, self.folder_atlas, param.file_info_label, self.fname_src, self.fname_transfo, self.folder_out) # Warp spinal levels if self.warp_spinal_levels == 1: sct.printv('\nWARP SPINAL LEVELS:', self.verbose) warp_label(self.path_template, self.folder_spinal_levels, param.file_info_label, self.fname_src, self.fname_transfo, self.folder_out)
def crop_image_around_segmentation(fname_in, fname_seg, path_output_im, path_output_seg, size_crop, offset, remove_tmp_files, verbose): # 1. Resample to 1mm^3 isotropic fname_in_resampled = sct.add_suffix(fname_in, 'r') sct.run('sct_resample -i ' + fname_in + ' -mm 1x1x1 -o ' + fname_in_resampled, verbose=verbose) fname_in = fname_in_resampled fname_seg_resample = sct.add_suffix(fname_seg, 'r') sct.run('sct_resample -i ' + fname_seg + ' -mm 1x1x1 -o ' + fname_seg_resample, verbose=verbose) fname_seg = fname_seg_resample # 2. Orient both input images to RPI for the sake of simplicity sct.run('sct_image -i ' + fname_in + ' -setorient RPI', verbose=verbose) fname_in = sct.add_suffix(fname_in, '_RPI') sct.run('sct_image -i ' + fname_seg + ' -setorient RPI', verbose=verbose) fname_seg = sct.add_suffix(fname_seg, '_RPI') # 3. Pad both images to avoid edge issues when cropping fname_in_pad = sct.add_suffix(fname_in, 'p') pad_image = str(int(int(size_crop) / 2)) sct.run('sct_image -i ' + fname_in + ' -pad ' + pad_image + ',' + pad_image + ',0 -o ' + fname_in_pad, verbose=verbose) fname_in = fname_in_pad fname_seg_pad = sct.add_suffix(fname_seg, 'p') sct.run('sct_image -i ' + fname_seg + ' -pad ' + pad_image + ',' + pad_image + ',0 -o ' + fname_seg_pad, verbose=verbose) fname_seg = fname_seg_pad # 4. Extract centerline from segmentation fname_centerline = sct.add_suffix(fname_seg, '_centerline') sct.run('sct_process_segmentation -i ' + fname_seg + ' -p centerline', verbose=verbose) # -o ' + fname_centerline) # 5. Create a square mask around the spinal cord centerline fname_mask_box = 'mask_box.nii.gz' sct.run('sct_create_mask -i ' + fname_in + ' -m centerline,' + fname_centerline + ' -s ' + str(size_crop) + ' -o ' + fname_mask_box + ' -f box -e 1 -k ' + offset, verbose=verbose) # 6. Crop image around the spinal cord and create a stack of square images sct.printv('Cropping around mask and stacking slices...', verbose=verbose) im_mask_box = Image(fname_mask_box) im_input = Image(fname_in) im_input.crop_and_stack(im_mask_box, suffix='_stack', save=True) im_seg = Image(fname_seg) im_seg.crop_and_stack(im_mask_box, suffix='_stack', save=True) # 6.5 Change name of images fname_stack_image = sct.add_suffix(fname_in, '_stack') fname_stack_seg = sct.add_suffix(fname_seg, '_stack') import random output_im_filename = str(random.randint(1, 1000000000000)) output_im_fname = output_im_filename + '_im.nii.gz' output_seg_fname = output_im_filename + '_seg.nii.gz' sct.run('mv ' + fname_stack_image + ' ' + output_im_fname, verbose=verbose) sct.run('mv ' + fname_stack_seg + ' ' + output_seg_fname, verbose=verbose) # 7. Split the two stack images and save each slice sct.run('sct_image -i ' + output_im_fname + ' -split z', verbose=verbose) sct.run('sct_image -i ' + output_seg_fname + ' -split z', verbose=verbose) # 8. Move all images to output folders path_fname, file_fname, ext_fname = sct.extract_fname(output_im_fname) sct.run('mv ' + file_fname + '_* ' + path_output_im, verbose=verbose) path_fname, file_fname, ext_fname = sct.extract_fname(output_seg_fname) sct.run('mv ' + file_fname + '_* ' + path_output_seg, verbose=verbose)
def validate_atlas(folder_cropped_atlas, nb_bootstraps, std_noise, range_tract, val_csf, results_folder, results_file, mask_folder, list_methods, test_map=0, param_map='20,20', list_tracts=[]): # Parameters file_phantom = "WM_phantom.nii.gz" file_phantom_noise = "WM_phantom_noise.nii.gz" file_tract_sum = "tracts_sum.nii.gz" true_value = 40 file_extract_metrics = "metric_label.txt" # list_tracts = ['2', '17', '0,1,15,16'] list_tracts_txt = ['csl', 'csr', 'dc'] index_dorsalcolumn = 2 # index of dorsal column in list_tracts nb_tracts_all = 32 # total number of tracts in atlas (do not include CSF tracts) # dorsal_column_labels = '0,1,15,16' # nb_tracts_dorsalcolumn = 4 value_gm = 35 # value in gray matter #value_csf = 5 # value in csf nb_digits_results = 2 # number of digits to display for result file mask_prefix = 'manual_' mask_ext = '.nii.gz' # initialization start_time = time.time() # save start time for duration folder_tmp = 'tmp.' + datetime.datetime.now().strftime("%y%m%d%H%M%S%f/") nb_methods = len(list_methods) nb_tracts = len(list_tracts) perc_error = np.zeros( shape=(nb_tracts, nb_methods, nb_bootstraps) ) # percent error within single tract (for comparison with manual labeling) perc_error_all = np.zeros(shape=( nb_tracts_all, nb_methods, nb_bootstraps )) # percent error for all tracts (for comparing automatic methods) stat_perc_error_all = np.zeros(shape=(nb_methods, nb_bootstraps, 4)) # statistics list_stat = ['MSE', 'median', 'min', 'max'] x_true_i = np.zeros(shape=(nb_tracts)) fname_phantom = folder_tmp + file_phantom fname_phantom_noise = folder_tmp + file_phantom_noise fname_tract_sum = folder_tmp + file_tract_sum # create output folder create_folder(results_folder, 0) # Extract the tracts from the atlas' folder tracts = get_tracts(folder_cropped_atlas) # get file name of the first atlas file fname_atlas = folder_cropped_atlas + 'WMtract__00.nii.gz' # Get ponderation of each tract for dorsal column average ponderation of each tract of the dorsal column if nb_tracts: list_tract_dorsalcolumn = list_tracts[index_dorsalcolumn].split(',') nb_tracts_dorsalcolumn = len(list_tract_dorsalcolumn) pond_dc = np.zeros(nb_tracts_dorsalcolumn) # sum of each pond_sum = 0 for i in range(nb_tracts_dorsalcolumn): # i = int(i) # Sum tracts values which are higher than 0 in the tracts pond_dc[i] = sum( tracts[int(list_tract_dorsalcolumn[i]), 0][tracts[int(list_tract_dorsalcolumn[i]), 0] > 0]) pond_sum = pond_sum + pond_dc[i] # Normalize the sum of ponderations to 1 pond_dc = pond_dc / pond_sum # create temporary folder sct.run('mkdir ' + folder_tmp) # loop across bootstrap for i_bootstrap in range(0, nb_bootstraps): sct.printv( 'Iteration: ' + str(i_bootstrap + 1) + '/' + str(nb_bootstraps), 1, 'warning') # Generate phantom [WM_phantom, WM_phantom_noise, values_synthetic_data, tracts_sum] = phantom_generation(tracts, std_noise, range_tract, true_value, folder_tmp, value_gm, true_value * val_csf / 100) # Save generated phantoms as nifti image (.nii.gz) save_3D_nparray_nifti(WM_phantom, fname_phantom, fname_atlas) save_3D_nparray_nifti(WM_phantom_noise, fname_phantom_noise, fname_atlas) save_3D_nparray_nifti(tracts_sum, fname_tract_sum, fname_atlas) # Get the np.mean of all values in dorsal column in the generated phantom if nb_tracts: dc_val_avg = 0 for j in range(nb_tracts_dorsalcolumn): dc_val_avg = dc_val_avg + values_synthetic_data[int( list_tract_dorsalcolumn[j])] * pond_dc[j] dc_val_avg = float(dc_val_avg) # build variable with true values (WARNING: HARD-CODED INDICES) x_true_i[0] = values_synthetic_data[int(list_tracts[0])] x_true_i[1] = values_synthetic_data[int(list_tracts[1])] x_true_i[2] = dc_val_avg fname_extract_metrics = folder_tmp + file_extract_metrics if nb_tracts: if not test_map: # loop across tracts for i_tract in range(len(list_tracts)): # loop across methods for i_method in range(len(list_methods)): # display stuff print 'Tract: ' + list_tracts[ i_tract] + ', Method: ' + list_methods[i_method] # check if method is manual if not list_methods[i_method].find('man') == -1: # find index of manual mask index_manual = int(list_methods[i_method][ list_methods[i_method].find('man') + 3]) fname_mask = mask_folder[ index_manual] + mask_prefix + list_tracts_txt[ i_tract] + mask_ext # manual extraction status, output = sct.run( 'sct_average_data_within_mask -i ' + fname_phantom_noise + ' -m ' + fname_mask + ' -v 0') x_estim_i = float(output) else: # automatic extraction sct.run('sct_extract_metric -i ' + fname_phantom_noise + ' -f ' + folder_cropped_atlas + ' -m ' + list_methods[i_method] + ' -l ' + list_tracts[i_tract] + ' -a -o ' + fname_extract_metrics) # read in txt file x_estim_i = read_results(fname_extract_metrics) # Get the percent absolute deviation with the true value #perc_error[i_tract, i_method, i_bootstrap] = 100 * (x_true_i[i_tract] - x_estim_i) / float(x_true_i[i_tract]) perc_error[i_tract, i_method, i_bootstrap] = 100 * abs( x_estim_i - x_true_i[i_tract]) / float( x_true_i[i_tract]) # calculate percentage error for all tracts (only for automatic methods) # loop across methods for i_method in range(len(list_methods)): # check if method is automatic if list_methods[i_method].find('man') == -1: # display stuff print 'Tract: ALL, Method: ' + list_methods[i_method] # automatic extraction in all tracts sct.run('sct_extract_metric -i ' + fname_phantom_noise + ' -f ' + folder_cropped_atlas + ' -m ' + list_methods[i_method] + ' -o ' + fname_extract_metrics + ' -p ' + param_map) # read results in txt file x_estim_i_all = read_results(fname_extract_metrics) # get nonzero values index_nonzero = np.nonzero(values_synthetic_data) perc_error_all[ 0:nb_tracts_all, i_method, i_bootstrap] = 100 * abs( x_estim_i_all[index_nonzero] - values_synthetic_data[index_nonzero] ) / values_synthetic_data[ index_nonzero] # will be used to display boxcar # perc_error_all[0:nb_tracts_all, i_method, i_bootstrap] = 100 * (x_estim_i_all[index_nonzero] - values_synthetic_data[index_nonzero]) / values_synthetic_data[index_nonzero] # will be used to display boxcar # compute mean squared error stat_perc_error_all[i_method, i_bootstrap, 0] = (perc_error_all[:, i_method, i_bootstrap]**2 ).mean() # mean squared error stat_perc_error_all[i_method, i_bootstrap, 1] = np.median( perc_error_all[:, i_method, i_bootstrap]) # median stat_perc_error_all[i_method, i_bootstrap, 2] = min(perc_error_all[:, i_method, i_bootstrap]) stat_perc_error_all[i_method, i_bootstrap, 3] = max(perc_error_all[:, i_method, i_bootstrap]) # Calculate elapsed time elapsed_time = int(round(time.time() - start_time)) # Extract time in minutes and seconds sec = elapsed_time % 60 mte = (elapsed_time - sec) / 60 # PRINT RESULTS FOR SINGLE TRACTS # =============================== if nb_tracts: # create output folder create_folder(results_folder + 'sub/', 0) # Open text file where results are printed fname_results = results_folder + 'sub/' + results_file + '.txt' results_text = open(fname_results, 'w+') # print header print >> results_text, '# Mean(std) percentage of absolute error within single tracts.' print >> results_text, '# Generated on: ' + time.strftime( '%Y-%m-%d %H:%M:%S') print >> results_text, '# true_value: ' + str(true_value) print >> results_text, '# sigma noise (in percentage of true value): ' + str( std_noise) + '%' print >> results_text, '# range tracts (in percentage of true value): (-' + str( range_tract) + '%:+' + str(range_tract) + '%)' print >> results_text, '# value CSF (in percentage of true value): ' + str( val_csf) + '%' print >> results_text, '# number of iterations: ' + str(nb_bootstraps) print >> results_text, '# elapsed time: ' + str(mte) + 'min' + str( sec) + 's' text_methods = 'Label' # loop across methods for i_method in range(len(list_methods)): text_methods = text_methods + ', ' + list_methods[i_method] print >> results_text, text_methods # print results # loop across tracts for i_tract in range(len(list_tracts)): text_results = list_tracts_txt[i_tract] # loop across methods for i_method in range(len(list_methods)): text_results = text_results + ', ' + str( round(np.mean(perc_error[i_tract, i_method, :]), ndigits=nb_digits_results)) + '(' + str( round(np.std(perc_error[i_tract, i_method, :]), ndigits=nb_digits_results)) + ')' print >> results_text, text_results # close file results_text.close() # display results status, output = sct.run('cat ' + fname_results) print output # PRINT RESULTS FOR ALL TRACTS # ============================ # Open text file where results are printed fname_results = results_folder + results_file + '_all.txt' results_text = open(fname_results, 'w+') # print header print >> results_text, '# Mean(std) percentage of absolute error within all tracts (only for automatic methods).' print >> results_text, '# Generated on: ' + time.strftime( '%Y-%m-%d %H:%M:%S') print >> results_text, '# true_value: ' + str(true_value) print >> results_text, '# sigma noise (in percentage of true value): ' + str( std_noise) + '%' print >> results_text, '# range tracts (in percentage of true value): (-' + str( range_tract) + '%:+' + str(range_tract) + '%)' print >> results_text, '# value CSF (in percentage of true value): ' + str( val_csf) + '%' print >> results_text, '# number of iterations: ' + str(nb_bootstraps) print >> results_text, '# elapsed time: ' + str(mte) + 'min' + str( sec) + 's' text_methods = 'Label' # loop across methods for i_method in range(len(list_methods)): # check if method is automatic if list_methods[i_method].find('man') == -1: text_methods = text_methods + ', ' + list_methods[i_method] print >> results_text, text_methods # print results # loop across tracts for i_tract in range(nb_tracts_all): text_results = str(i_tract) # loop across methods for i_method in range(len(list_methods)): # check if method is automatic if list_methods[i_method].find('man') == -1: text_results = text_results + ', ' + str( round(np.mean(perc_error_all[i_tract, i_method, :]), ndigits=nb_digits_results)) + '(' + str( round(np.std(perc_error_all[i_tract, i_method, :]), ndigits=nb_digits_results)) + ')' print >> results_text, text_results # loop across statistics nb_stats = len(list_stat) for i_stat in range(nb_stats): text_results = list_stat[i_stat] # loop across methods for i_method in range(len(list_methods)): # check if method is automatic if list_methods[i_method].find('man') == -1: text_results = text_results + ', ' + str( round(np.mean(stat_perc_error_all[i_method, :, i_stat]), ndigits=nb_digits_results)) + '(' + str( round(np.std(stat_perc_error_all[i_method, :, i_stat]), ndigits=nb_digits_results)) + ')' print >> results_text, text_results # close file results_text.close() # display results status, output = sct.run('cat ' + fname_results) print output
def main(file_to_denoise, param, output_file_name): path, file, ext = sct.extract_fname(file_to_denoise) img = nib.load(file_to_denoise) hdr_0 = img.get_header() data = img.get_data() # aff = img.get_affine() if min(data.shape) <= 5: sct.printv( 'One of the image dimensions is <= 5 : reducing the size of the block radius.' ) block_radius = min(data.shape) - 1 else: block_radius = 5 # default value # Process for manual detecting of background # mask = data[:, :, :] > noise_threshold # data = data[:, :, :] from dipy.denoise.nlmeans import nlmeans if '-std' in arguments: sigma = std_noise # Application of NLM filter to the image print 'Applying Non-local mean filter...' if param.parameter == 'Rician': den = nlmeans(data, sigma=sigma, mask=None, rician=True, block_radius=block_radius) else: den = nlmeans(data, sigma=sigma, mask=None, rician=False, block_radius=block_radius) else: # # Process for manual detecting of background mask = data > noise_threshold sigma = np.std(data[~mask]) # Application of NLM filter to the image print 'Applying Non-local mean filter...' if param.parameter == 'Rician': den = nlmeans(data, sigma=sigma, mask=mask, rician=True, block_radius=block_radius) else: den = nlmeans(data, sigma=sigma, mask=mask, rician=False, block_radius=block_radius) t = time() print("total time", time() - t) print("vol size", den.shape) axial_middle = data.shape[2] / 2 before = data[:, :, axial_middle].T after = den[:, :, axial_middle].T diff_3d = np.absolute(den.astype('f8') - data.astype('f8')) difference = np.absolute(after.astype('f8') - before.astype('f8')) if '-std' not in arguments: difference[~mask[:, :, axial_middle].T] = 0 if param.verbose == 2: import matplotlib.pyplot as plt fig, ax = plt.subplots(1, 3) ax[0].imshow(before, cmap='gray', origin='lower') ax[0].set_title('before') ax[1].imshow(after, cmap='gray', origin='lower') ax[1].set_title('after') ax[2].imshow(difference, cmap='gray', origin='lower') ax[2].set_title('difference') for i in range(3): ax[i].set_axis_off() plt.show() #Save files img_denoise = nib.Nifti1Image(den, None, hdr_0) img_diff = nib.Nifti1Image(diff_3d, None, hdr_0) if output_file_name != None: output_file_name = output_file_name else: output_file_name = file + '_denoised' + ext nib.save(img_denoise, output_file_name) nib.save(img_diff, file + '_difference' + ext)
def main(args=None): # initializations param = Param() # check user arguments if not args: args = sys.argv[1:] # Get parser info parser = get_parser() arguments = parser.parse(args) fname_data = arguments['-i'] fname_seg = arguments['-s'] if '-l' in arguments: fname_landmarks = arguments['-l'] label_type = 'body' elif '-ldisc' in arguments: fname_landmarks = arguments['-ldisc'] label_type = 'disc' else: sct.printv('ERROR: Labels should be provided.', 1, 'error') if '-ofolder' in arguments: path_output = arguments['-ofolder'] else: path_output = '' param.path_qc = arguments.get("-qc", None) path_template = arguments['-t'] contrast_template = arguments['-c'] ref = arguments['-ref'] remove_temp_files = int(arguments['-r']) verbose = int(arguments['-v']) param.verbose = verbose # TODO: not clean, unify verbose or param.verbose in code, but not both if '-param-straighten' in arguments: param.param_straighten = arguments['-param-straighten'] # if '-cpu-nb' in arguments: # arg_cpu = ' -cpu-nb '+str(arguments['-cpu-nb']) # else: # arg_cpu = '' # registration parameters if '-param' in arguments: # reset parameters but keep step=0 (might be overwritten if user specified step=0) paramreg = ParamregMultiStep([step0]) if ref == 'subject': paramreg.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz' # add user parameters for paramStep in arguments['-param']: paramreg.addStep(paramStep) else: paramreg = ParamregMultiStep([step0, step1, step2]) # if ref=subject, initialize registration using different affine parameters if ref == 'subject': paramreg.steps['0'].dof = 'Tx_Ty_Tz_Rx_Ry_Rz_Sz' # initialize other parameters zsubsample = param.zsubsample # retrieve template file names file_template_vertebral_labeling = get_file_label( os.path.join(path_template, 'template'), 'vertebral labeling') file_template = get_file_label( os.path.join(path_template, 'template'), contrast_template.upper() + '-weighted template') file_template_seg = get_file_label(os.path.join(path_template, 'template'), 'spinal cord') # start timer start_time = time.time() # get fname of the template + template objects fname_template = os.path.join(path_template, 'template', file_template) fname_template_vertebral_labeling = os.path.join( path_template, 'template', file_template_vertebral_labeling) fname_template_seg = os.path.join(path_template, 'template', file_template_seg) fname_template_disc_labeling = os.path.join(path_template, 'template', 'PAM50_label_disc.nii.gz') # check file existence # TODO: no need to do that! sct.printv('\nCheck template files...') sct.check_file_exist(fname_template, verbose) sct.check_file_exist(fname_template_vertebral_labeling, verbose) sct.check_file_exist(fname_template_seg, verbose) path_data, file_data, ext_data = sct.extract_fname(fname_data) # sct.printv(arguments) sct.printv('\nCheck parameters:', verbose) sct.printv(' Data: ' + fname_data, verbose) sct.printv(' Landmarks: ' + fname_landmarks, verbose) sct.printv(' Segmentation: ' + fname_seg, verbose) sct.printv(' Path template: ' + path_template, verbose) sct.printv(' Remove temp files: ' + str(remove_temp_files), verbose) # check input labels labels = check_labels(fname_landmarks, label_type=label_type) vertebral_alignment = False if len(labels) > 2 and label_type == 'disc': vertebral_alignment = True path_tmp = sct.tmp_create(basename="register_to_template", verbose=verbose) # set temporary file names ftmp_data = 'data.nii' ftmp_seg = 'seg.nii.gz' ftmp_label = 'label.nii.gz' ftmp_template = 'template.nii' ftmp_template_seg = 'template_seg.nii.gz' ftmp_template_label = 'template_label.nii.gz' # copy files to temporary folder sct.printv('\nCopying input data to tmp folder and convert to nii...', verbose) Image(fname_data).save(os.path.join(path_tmp, ftmp_data)) Image(fname_seg).save(os.path.join(path_tmp, ftmp_seg)) Image(fname_landmarks).save(os.path.join(path_tmp, ftmp_label)) Image(fname_template).save(os.path.join(path_tmp, ftmp_template)) Image(fname_template_seg).save(os.path.join(path_tmp, ftmp_template_seg)) Image(fname_template_vertebral_labeling).save( os.path.join(path_tmp, ftmp_template_label)) if label_type == 'disc': Image(fname_template_disc_labeling).save( os.path.join(path_tmp, ftmp_template_label)) # go to tmp folder curdir = os.getcwd() os.chdir(path_tmp) # Generate labels from template vertebral labeling if label_type == 'body': sct.printv('\nGenerate labels from template vertebral labeling', verbose) ftmp_template_label_, ftmp_template_label = ftmp_template_label, sct.add_suffix( ftmp_template_label, "_body") sct_label_utils.main(args=[ '-i', ftmp_template_label_, '-vert-body', '0', '-o', ftmp_template_label ]) # check if provided labels are available in the template sct.printv('\nCheck if provided labels are available in the template', verbose) image_label_template = Image(ftmp_template_label) labels_template = image_label_template.getNonZeroCoordinates( sorting='value') if labels[-1].value > labels_template[-1].value: sct.printv( 'ERROR: Wrong landmarks input. Labels must have correspondence in template space. \nLabel max ' 'provided: ' + str(labels[-1].value) + '\nLabel max from template: ' + str(labels_template[-1].value), verbose, 'error') # if only one label is present, force affine transformation to be Tx,Ty,Tz only (no scaling) if len(labels) == 1: paramreg.steps['0'].dof = 'Tx_Ty_Tz' sct.printv( 'WARNING: Only one label is present. Forcing initial transformation to: ' + paramreg.steps['0'].dof, 1, 'warning') # Project labels onto the spinal cord centerline because later, an affine transformation is estimated between the # template's labels (centered in the cord) and the subject's labels (assumed to be centered in the cord). # If labels are not centered, mis-registration errors are observed (see issue #1826) ftmp_label = project_labels_on_spinalcord(ftmp_label, ftmp_seg) # binarize segmentation (in case it has values below 0 caused by manual editing) sct.printv('\nBinarize segmentation', verbose) ftmp_seg_, ftmp_seg = ftmp_seg, sct.add_suffix(ftmp_seg, "_bin") sct.run(['sct_maths', '-i', ftmp_seg_, '-bin', '0.5', '-o', ftmp_seg]) # Switch between modes: subject->template or template->subject if ref == 'template': # resample data to 1mm isotropic sct.printv('\nResample data to 1mm isotropic...', verbose) sct.run([ 'sct_resample', '-i', ftmp_data, '-mm', '1.0x1.0x1.0', '-x', 'linear', '-o', add_suffix(ftmp_data, '_1mm') ]) ftmp_data = add_suffix(ftmp_data, '_1mm') sct.run([ 'sct_resample', '-i', ftmp_seg, '-mm', '1.0x1.0x1.0', '-x', 'linear', '-o', add_suffix(ftmp_seg, '_1mm') ]) ftmp_seg = add_suffix(ftmp_seg, '_1mm') # N.B. resampling of labels is more complicated, because they are single-point labels, therefore resampling # with nearest neighbour can make them disappear. resample_labels(ftmp_label, ftmp_data, add_suffix(ftmp_label, '_1mm')) ftmp_label = add_suffix(ftmp_label, '_1mm') # Change orientation of input images to RPI sct.printv('\nChange orientation of input images to RPI...', verbose) ftmp_data = Image(ftmp_data).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_seg = Image(ftmp_seg).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_label = Image(ftmp_label).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_crop') if vertebral_alignment: # cropping the segmentation based on the label coverage to ensure good registration with vertebral alignment # See https://github.com/neuropoly/spinalcordtoolbox/pull/1669 for details image_labels = Image(ftmp_label) coordinates_labels = image_labels.getNonZeroCoordinates( sorting='z') nx, ny, nz, nt, px, py, pz, pt = image_labels.dim offset_crop = 10.0 * pz # cropping the image 10 mm above and below the highest and lowest label cropping_slices = [ coordinates_labels[0].z - offset_crop, coordinates_labels[-1].z + offset_crop ] # make sure that the cropping slices do not extend outside of the slice range (issue #1811) if cropping_slices[0] < 0: cropping_slices[0] = 0 if cropping_slices[1] > nz: cropping_slices[1] = nz msct_image.spatial_crop( Image(ftmp_seg_), dict(((2, np.int32(np.round(cropping_slices))), ))).save(ftmp_seg) else: # if we do not align the vertebral levels, we crop the segmentation from top to bottom im_seg_rpi = Image(ftmp_seg_) bottom = 0 for data in msct_image.SlicerOneAxis(im_seg_rpi, "IS"): if (data != 0).any(): break bottom += 1 top = im_seg_rpi.data.shape[2] for data in msct_image.SlicerOneAxis(im_seg_rpi, "SI"): if (data != 0).any(): break top -= 1 msct_image.spatial_crop(im_seg_rpi, dict( ((2, (bottom, top)), ))).save(ftmp_seg) # straighten segmentation sct.printv( '\nStraighten the spinal cord using centerline/segmentation...', verbose) # check if warp_curve2straight and warp_straight2curve already exist (i.e. no need to do it another time) fn_warp_curve2straight = os.path.join(curdir, "warp_curve2straight.nii.gz") fn_warp_straight2curve = os.path.join(curdir, "warp_straight2curve.nii.gz") fn_straight_ref = os.path.join(curdir, "straight_ref.nii.gz") cache_input_files = [ftmp_seg] if vertebral_alignment: cache_input_files += [ ftmp_template_seg, ftmp_label, ftmp_template_label, ] cache_sig = sct.cache_signature(input_files=cache_input_files, ) cachefile = os.path.join(curdir, "straightening.cache") if sct.cache_valid( cachefile, cache_sig ) and os.path.isfile(fn_warp_curve2straight) and os.path.isfile( fn_warp_straight2curve) and os.path.isfile(fn_straight_ref): sct.printv( 'Reusing existing warping field which seems to be valid', verbose, 'warning') sct.copy(fn_warp_curve2straight, 'warp_curve2straight.nii.gz') sct.copy(fn_warp_straight2curve, 'warp_straight2curve.nii.gz') sct.copy(fn_straight_ref, 'straight_ref.nii.gz') # apply straightening sct.run([ 'sct_apply_transfo', '-i', ftmp_seg, '-w', 'warp_curve2straight.nii.gz', '-d', 'straight_ref.nii.gz', '-o', add_suffix(ftmp_seg, '_straight') ]) else: from sct_straighten_spinalcord import SpinalCordStraightener sc_straight = SpinalCordStraightener(ftmp_seg, ftmp_seg) sc_straight.output_filename = add_suffix(ftmp_seg, '_straight') sc_straight.path_output = './' sc_straight.qc = '0' sc_straight.remove_temp_files = remove_temp_files sc_straight.verbose = verbose if vertebral_alignment: sc_straight.centerline_reference_filename = ftmp_template_seg sc_straight.use_straight_reference = True sc_straight.discs_input_filename = ftmp_label sc_straight.discs_ref_filename = ftmp_template_label sc_straight.straighten() sct.cache_save(cachefile, cache_sig) # N.B. DO NOT UPDATE VARIABLE ftmp_seg BECAUSE TEMPORARY USED LATER # re-define warping field using non-cropped space (to avoid issue #367) s, o = sct.run([ 'sct_concat_transfo', '-w', 'warp_straight2curve.nii.gz', '-d', ftmp_data, '-o', 'warp_straight2curve.nii.gz' ]) if vertebral_alignment: sct.copy('warp_curve2straight.nii.gz', 'warp_curve2straightAffine.nii.gz') else: # Label preparation: # -------------------------------------------------------------------------------- # Remove unused label on template. Keep only label present in the input label image sct.printv( '\nRemove unused label on template. Keep only label present in the input label image...', verbose) sct.run([ 'sct_label_utils', '-i', ftmp_template_label, '-o', ftmp_template_label, '-remove', ftmp_label ]) # Dilating the input label so they can be straighten without losing them sct.printv('\nDilating input labels using 3vox ball radius') sct.run([ 'sct_maths', '-i', ftmp_label, '-o', add_suffix(ftmp_label, '_dilate'), '-dilate', '3' ]) ftmp_label = add_suffix(ftmp_label, '_dilate') # Apply straightening to labels sct.printv('\nApply straightening to labels...', verbose) sct.run([ 'sct_apply_transfo', '-i', ftmp_label, '-o', add_suffix(ftmp_label, '_straight'), '-d', add_suffix(ftmp_seg, '_straight'), '-w', 'warp_curve2straight.nii.gz', '-x', 'nn' ]) ftmp_label = add_suffix(ftmp_label, '_straight') # Compute rigid transformation straight landmarks --> template landmarks sct.printv('\nEstimate transformation for step #0...', verbose) from msct_register_landmarks import register_landmarks try: register_landmarks(ftmp_label, ftmp_template_label, paramreg.steps['0'].dof, fname_affine='straight2templateAffine.txt', verbose=verbose) except Exception: sct.printv( 'ERROR: input labels do not seem to be at the right place. Please check the position of the labels. See documentation for more details: https://sourceforge.net/p/spinalcordtoolbox/wiki/create_labels/', verbose=verbose, type='error') # Concatenate transformations: curve --> straight --> affine sct.printv( '\nConcatenate transformations: curve --> straight --> affine...', verbose) sct.run([ 'sct_concat_transfo', '-w', 'warp_curve2straight.nii.gz,straight2templateAffine.txt', '-d', 'template.nii', '-o', 'warp_curve2straightAffine.nii.gz' ]) # Apply transformation sct.printv('\nApply transformation...', verbose) sct.run([ 'sct_apply_transfo', '-i', ftmp_data, '-o', add_suffix(ftmp_data, '_straightAffine'), '-d', ftmp_template, '-w', 'warp_curve2straightAffine.nii.gz' ]) ftmp_data = add_suffix(ftmp_data, '_straightAffine') sct.run([ 'sct_apply_transfo', '-i', ftmp_seg, '-o', add_suffix(ftmp_seg, '_straightAffine'), '-d', ftmp_template, '-w', 'warp_curve2straightAffine.nii.gz', '-x', 'linear' ]) ftmp_seg = add_suffix(ftmp_seg, '_straightAffine') """ # Benjamin: Issue from Allan Martin, about the z=0 slice that is screwed up, caused by the affine transform. # Solution found: remove slices below and above landmarks to avoid rotation effects points_straight = [] for coord in landmark_template: points_straight.append(coord.z) min_point, max_point = int(np.round(np.min(points_straight))), int(np.round(np.max(points_straight))) ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_black') msct_image.spatial_crop(Image(ftmp_seg_), dict(((2, (min_point,max_point)),))).save(ftmp_seg) """ # open segmentation im = Image(ftmp_seg) im_new = msct_image.empty_like(im) # binarize im_new.data = im.data > 0.5 # find min-max of anat2template (for subsequent cropping) zmin_template, zmax_template = msct_image.find_zmin_zmax(im_new, threshold=0.5) # save binarized segmentation im_new.save(add_suffix(ftmp_seg, '_bin')) # unused? # crop template in z-direction (for faster processing) # TODO: refactor to use python module instead of doing i/o sct.printv('\nCrop data in template space (for faster processing)...', verbose) ftmp_template_, ftmp_template = ftmp_template, add_suffix( ftmp_template, '_crop') msct_image.spatial_crop(Image(ftmp_template_), dict( ((2, (zmin_template, zmax_template)), ))).save(ftmp_template) ftmp_template_seg_, ftmp_template_seg = ftmp_template_seg, add_suffix( ftmp_template_seg, '_crop') msct_image.spatial_crop( Image(ftmp_template_seg_), dict(((2, (zmin_template, zmax_template)), ))).save(ftmp_template_seg) ftmp_data_, ftmp_data = ftmp_data, add_suffix(ftmp_data, '_crop') msct_image.spatial_crop(Image(ftmp_data_), dict(((2, (zmin_template, zmax_template)), ))).save(ftmp_data) ftmp_seg_, ftmp_seg = ftmp_seg, add_suffix(ftmp_seg, '_crop') msct_image.spatial_crop(Image(ftmp_seg_), dict(((2, (zmin_template, zmax_template)), ))).save(ftmp_seg) # sub-sample in z-direction # TODO: refactor to use python module instead of doing i/o sct.printv('\nSub-sample in z-direction (for faster processing)...', verbose) sct.run([ 'sct_resample', '-i', ftmp_template, '-o', add_suffix(ftmp_template, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_template = add_suffix(ftmp_template, '_sub') sct.run([ 'sct_resample', '-i', ftmp_template_seg, '-o', add_suffix(ftmp_template_seg, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_template_seg = add_suffix(ftmp_template_seg, '_sub') sct.run([ 'sct_resample', '-i', ftmp_data, '-o', add_suffix(ftmp_data, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_data = add_suffix(ftmp_data, '_sub') sct.run([ 'sct_resample', '-i', ftmp_seg, '-o', add_suffix(ftmp_seg, '_sub'), '-f', '1x1x' + zsubsample ], verbose) ftmp_seg = add_suffix(ftmp_seg, '_sub') # Registration straight spinal cord to template sct.printv('\nRegister straight spinal cord to template...', verbose) # loop across registration steps warp_forward = [] warp_inverse = [] for i_step in range(1, len(paramreg.steps)): sct.printv( '\nEstimate transformation for step #' + str(i_step) + '...', verbose) # identify which is the src and dest if paramreg.steps[str(i_step)].type == 'im': src = ftmp_data dest = ftmp_template interp_step = 'linear' elif paramreg.steps[str(i_step)].type == 'seg': src = ftmp_seg dest = ftmp_template_seg interp_step = 'nn' else: sct.printv('ERROR: Wrong image type.', 1, 'error') # if step>1, apply warp_forward_concat to the src image to be used if i_step > 1: # sct.run('sct_apply_transfo -i '+src+' -d '+dest+' -w '+','.join(warp_forward)+' -o '+sct.add_suffix(src, '_reg')+' -x '+interp_step, verbose) # apply transformation from previous step, to use as new src for registration sct.run([ 'sct_apply_transfo', '-i', src, '-d', dest, '-w', ','.join(warp_forward), '-o', add_suffix(src, '_regStep' + str(i_step - 1)), '-x', interp_step ], verbose) src = add_suffix(src, '_regStep' + str(i_step - 1)) # register src --> dest # TODO: display param for debugging warp_forward_out, warp_inverse_out = register( src, dest, paramreg, param, str(i_step)) warp_forward.append(warp_forward_out) warp_inverse.append(warp_inverse_out) # Concatenate transformations: sct.printv('\nConcatenate transformations: anat --> template...', verbose) sct.run([ 'sct_concat_transfo', '-w', 'warp_curve2straightAffine.nii.gz,' + ','.join(warp_forward), '-d', 'template.nii', '-o', 'warp_anat2template.nii.gz' ], verbose) # sct.run('sct_concat_transfo -w warp_curve2straight.nii.gz,straight2templateAffine.txt,'+','.join(warp_forward)+' -d template.nii -o warp_anat2template.nii.gz', verbose) sct.printv('\nConcatenate transformations: template --> anat...', verbose) warp_inverse.reverse() if vertebral_alignment: sct.run([ 'sct_concat_transfo', '-w', ','.join(warp_inverse) + ',warp_straight2curve.nii.gz', '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz' ], verbose) else: sct.run([ 'sct_concat_transfo', '-w', ','.join(warp_inverse) + ',-straight2templateAffine.txt,warp_straight2curve.nii.gz', '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz' ], verbose) # register template->subject elif ref == 'subject': # Change orientation of input images to RPI sct.printv('\nChange orientation of input images to RPI...', verbose) ftmp_data = Image(ftmp_data).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_seg = Image(ftmp_seg).change_orientation( "RPI", generate_path=True).save().absolutepath ftmp_label = Image(ftmp_label).change_orientation( "RPI", generate_path=True).save().absolutepath # Remove unused label on template. Keep only label present in the input label image sct.printv( '\nRemove unused label on template. Keep only label present in the input label image...', verbose) sct.run([ 'sct_label_utils', '-i', ftmp_template_label, '-o', ftmp_template_label, '-remove', ftmp_label ]) # Add one label because at least 3 orthogonal labels are required to estimate an affine transformation. This # new label is added at the level of the upper most label (lowest value), at 1cm to the right. for i_file in [ftmp_label, ftmp_template_label]: im_label = Image(i_file) coord_label = im_label.getCoordinatesAveragedByValue( ) # N.B. landmarks are sorted by value # Create new label from copy import deepcopy new_label = deepcopy(coord_label[0]) # move it 5mm to the left (orientation is RAS) nx, ny, nz, nt, px, py, pz, pt = im_label.dim new_label.x = np.round(coord_label[0].x + 5.0 / px) # assign value 99 new_label.value = 99 # Add to existing image im_label.data[int(new_label.x), int(new_label.y), int(new_label.z)] = new_label.value # Overwrite label file # im_label.absolutepath = 'label_rpi_modif.nii.gz' im_label.save() # Bring template to subject space using landmark-based transformation sct.printv('\nEstimate transformation for step #0...', verbose) from msct_register_landmarks import register_landmarks warp_forward = ['template2subjectAffine.txt'] warp_inverse = ['-template2subjectAffine.txt'] try: register_landmarks(ftmp_template_label, ftmp_label, paramreg.steps['0'].dof, fname_affine=warp_forward[0], verbose=verbose, path_qc="./") except Exception: sct.printv( 'ERROR: input labels do not seem to be at the right place. Please check the position of the labels. See documentation for more details: https://sourceforge.net/p/spinalcordtoolbox/wiki/create_labels/', verbose=verbose, type='error') # loop across registration steps for i_step in range(1, len(paramreg.steps)): sct.printv( '\nEstimate transformation for step #' + str(i_step) + '...', verbose) # identify which is the src and dest if paramreg.steps[str(i_step)].type == 'im': src = ftmp_template dest = ftmp_data interp_step = 'linear' elif paramreg.steps[str(i_step)].type == 'seg': src = ftmp_template_seg dest = ftmp_seg interp_step = 'nn' else: sct.printv('ERROR: Wrong image type.', 1, 'error') # apply transformation from previous step, to use as new src for registration sct.run([ 'sct_apply_transfo', '-i', src, '-d', dest, '-w', ','.join(warp_forward), '-o', add_suffix(src, '_regStep' + str(i_step - 1)), '-x', interp_step ], verbose) src = add_suffix(src, '_regStep' + str(i_step - 1)) # register src --> dest # TODO: display param for debugging warp_forward_out, warp_inverse_out = register( src, dest, paramreg, param, str(i_step)) warp_forward.append(warp_forward_out) warp_inverse.insert(0, warp_inverse_out) # Concatenate transformations: sct.printv('\nConcatenate transformations: template --> subject...', verbose) sct.run([ 'sct_concat_transfo', '-w', ','.join(warp_forward), '-d', 'data.nii', '-o', 'warp_template2anat.nii.gz' ], verbose) sct.printv('\nConcatenate transformations: subject --> template...', verbose) sct.run([ 'sct_concat_transfo', '-w', ','.join(warp_inverse), '-d', 'template.nii', '-o', 'warp_anat2template.nii.gz' ], verbose) # Apply warping fields to anat and template sct.run([ 'sct_apply_transfo', '-i', 'template.nii', '-o', 'template2anat.nii.gz', '-d', 'data.nii', '-w', 'warp_template2anat.nii.gz', '-crop', '1' ], verbose) sct.run([ 'sct_apply_transfo', '-i', 'data.nii', '-o', 'anat2template.nii.gz', '-d', 'template.nii', '-w', 'warp_anat2template.nii.gz', '-crop', '1' ], verbose) # come back os.chdir(curdir) # Generate output files sct.printv('\nGenerate output files...', verbose) fname_template2anat = os.path.join(path_output, 'template2anat' + ext_data) fname_anat2template = os.path.join(path_output, 'anat2template' + ext_data) sct.generate_output_file( os.path.join(path_tmp, "warp_template2anat.nii.gz"), os.path.join(path_output, "warp_template2anat.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "warp_anat2template.nii.gz"), os.path.join(path_output, "warp_anat2template.nii.gz"), verbose) sct.generate_output_file(os.path.join(path_tmp, "template2anat.nii.gz"), fname_template2anat, verbose) sct.generate_output_file(os.path.join(path_tmp, "anat2template.nii.gz"), fname_anat2template, verbose) if ref == 'template': # copy straightening files in case subsequent SCT functions need them sct.generate_output_file( os.path.join(path_tmp, "warp_curve2straight.nii.gz"), os.path.join(path_output, "warp_curve2straight.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "warp_straight2curve.nii.gz"), os.path.join(path_output, "warp_straight2curve.nii.gz"), verbose) sct.generate_output_file( os.path.join(path_tmp, "straight_ref.nii.gz"), os.path.join(path_output, "straight_ref.nii.gz"), verbose) # Delete temporary files if remove_temp_files: sct.printv('\nDelete temporary files...', verbose) sct.rmtree(path_tmp, verbose=verbose) # display elapsed time elapsed_time = time.time() - start_time sct.printv( '\nFinished! Elapsed time: ' + str(int(np.round(elapsed_time))) + 's', verbose) if param.path_qc is not None: generate_qc(fname_data, fname_template2anat, fname_seg, args, os.path.abspath(param.path_qc)) sct.display_viewer_syntax([fname_data, fname_template2anat], verbose=verbose) sct.display_viewer_syntax([fname_template, fname_anat2template], verbose=verbose)
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 = image_input.orientation 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.astype('float') * (max_out - min_out) / (max_in - min_in) img_int16.data = data_rescaled - (data_rescaled.min() - min_out) # change data type img_int16.save(image_int_filename, dtype=np.uint16) 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)
def main(): # Initialization fname_mt0 = '' fname_mt1 = '' file_out = param.file_out # register = param.register # remove_tmp_files = param.remove_tmp_files # verbose = param.verbose # get path of the toolbox # status, path_sct = commands.getstatusoutput('echo $SCT_DIR') # Check input parameters parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_mt0 = arguments['-mt0'] fname_mt1 = arguments['-mt1'] remove_tmp_files = int(arguments['-r']) verbose = int(arguments['-v']) # Extract path/file/extension path_mt0, file_mt0, ext_mt0 = sct.extract_fname(fname_mt0) path_out, file_out, ext_out = '', file_out, ext_mt0 # create temporary folder path_tmp = sct.tmp_create() # Copying input data to tmp folder and convert to nii sct.printv('\nCopying input data to tmp folder and convert to nii...', verbose) from sct_convert import convert convert(fname_mt0, path_tmp + 'mt0.nii', type='float32') convert(fname_mt1, path_tmp + 'mt1.nii', type='float32') # go to tmp folder os.chdir(path_tmp) # compute MTR sct.printv('\nCompute MTR...', verbose) from msct_image import Image nii_mt1 = Image('mt1.nii') data_mt1 = nii_mt1.data data_mt0 = Image('mt0.nii').data data_mtr = 100 * (data_mt0 - data_mt1) / data_mt0 # save MTR file nii_mtr = nii_mt1 nii_mtr.data = data_mtr nii_mtr.setFileName('mtr.nii') nii_mtr.save() # sct.run(fsloutput+'fslmaths -dt double mt0.nii -sub mt1.nii -mul 100 -div mt0.nii -thr 0 -uthr 100 mtr.nii', verbose) # come back to parent folder os.chdir('..') # Generate output files sct.printv('\nGenerate output files...', verbose) sct.generate_output_file(path_tmp + 'mtr.nii', path_out + file_out + ext_out) # Remove temporary files if remove_tmp_files == 1: print('\nRemove temporary files...') sct.run('rm -rf ' + path_tmp) # to view results sct.printv('\nDone! To view results, type:', verbose) sct.printv( 'fslview ' + fname_mt0 + ' ' + fname_mt1 + ' ' + file_out + ' &\n', verbose, 'info')
def main(): # get default parameters step1 = Paramreg(step='1', type='seg', algo='slicereg', metric='MeanSquares', iter='10') step2 = Paramreg(step='2', type='im', algo='syn', metric='MI', iter='3') # step1 = Paramreg() paramreg = ParamregMultiStep([step1, step2]) # step1 = Paramreg_step(step='1', type='seg', algo='bsplinesyn', metric='MeanSquares', iter='10', shrink='1', smooth='0', gradStep='0.5') # step2 = Paramreg_step(step='2', type='im', algo='syn', metric='MI', iter='10', shrink='1', smooth='0', gradStep='0.5') # paramreg = ParamregMultiStep([step1, step2]) # Initialize the parser parser = Parser(__file__) parser.usage.set_description('Register anatomical image to the template.') parser.add_option(name="-i", type_value="file", description="Anatomical image.", mandatory=True, example="anat.nii.gz") parser.add_option(name="-s", type_value="file", description="Spinal cord segmentation.", mandatory=True, example="anat_seg.nii.gz") parser.add_option( name="-l", type_value="file", description= "Labels. See: http://sourceforge.net/p/spinalcordtoolbox/wiki/create_labels/", mandatory=True, default_value='', example="anat_labels.nii.gz") parser.add_option(name="-t", type_value="folder", description="Path to MNI-Poly-AMU template.", mandatory=False, default_value=param.path_template) parser.add_option( name="-p", type_value=[[':'], 'str'], description= """Parameters for registration (see sct_register_multimodal). Default:\n--\nstep=1\ntype=""" + paramreg.steps['1'].type + """\nalgo=""" + paramreg.steps['1'].algo + """\nmetric=""" + paramreg.steps['1'].metric + """\npoly=""" + paramreg.steps['1'].poly + """\n--\nstep=2\ntype=""" + paramreg.steps['2'].type + """\nalgo=""" + paramreg.steps['2'].algo + """\nmetric=""" + paramreg.steps['2'].metric + """\niter=""" + paramreg.steps['2'].iter + """\nshrink=""" + paramreg.steps['2'].shrink + """\nsmooth=""" + paramreg.steps['2'].smooth + """\ngradStep=""" + paramreg.steps['2'].gradStep + """\n--""", mandatory=False, example= "step=2,type=seg,algo=bsplinesyn,metric=MeanSquares,iter=5,shrink=2:step=3,type=im,algo=syn,metric=MI,iter=5,shrink=1,gradStep=0.3" ) parser.add_option(name="-r", type_value="multiple_choice", description="""Remove temporary files.""", mandatory=False, default_value='1', example=['0', '1']) parser.add_option( name="-v", type_value="multiple_choice", description="""Verbose. 0: nothing. 1: basic. 2: extended.""", mandatory=False, default_value=param.verbose, example=['0', '1', '2']) if param.debug: print '\n*** WARNING: DEBUG MODE ON ***\n' fname_data = '/Users/julien/data/temp/sct_example_data/t2/t2.nii.gz' fname_landmarks = '/Users/julien/data/temp/sct_example_data/t2/labels.nii.gz' fname_seg = '/Users/julien/data/temp/sct_example_data/t2/t2_seg.nii.gz' path_template = param.path_template remove_temp_files = 0 verbose = 2 # speed = 'superfast' #param_reg = '2,BSplineSyN,0.6,MeanSquares' else: arguments = parser.parse(sys.argv[1:]) # get arguments fname_data = arguments['-i'] fname_seg = arguments['-s'] fname_landmarks = arguments['-l'] path_template = arguments['-t'] remove_temp_files = int(arguments['-r']) verbose = int(arguments['-v']) if '-p' in arguments: paramreg_user = arguments['-p'] # update registration parameters for paramStep in paramreg_user: paramreg.addStep(paramStep) # initialize other parameters file_template = param.file_template file_template_label = param.file_template_label file_template_seg = param.file_template_seg output_type = param.output_type zsubsample = param.zsubsample # smoothing_sigma = param.smoothing_sigma # start timer start_time = time.time() # get absolute path - TO DO: remove! NEVER USE ABSOLUTE PATH... path_template = os.path.abspath(path_template) # get fname of the template + template objects fname_template = sct.slash_at_the_end(path_template, 1) + file_template fname_template_label = sct.slash_at_the_end(path_template, 1) + file_template_label fname_template_seg = sct.slash_at_the_end(path_template, 1) + file_template_seg # check file existence sct.printv('\nCheck template files...') sct.check_file_exist(fname_template, verbose) sct.check_file_exist(fname_template_label, verbose) sct.check_file_exist(fname_template_seg, verbose) # print arguments sct.printv('\nCheck parameters:', verbose) sct.printv('.. Data: ' + fname_data, verbose) sct.printv('.. Landmarks: ' + fname_landmarks, verbose) sct.printv('.. Segmentation: ' + fname_seg, verbose) sct.printv('.. Path template: ' + path_template, verbose) sct.printv('.. Output type: ' + str(output_type), verbose) sct.printv('.. Remove temp files: ' + str(remove_temp_files), verbose) sct.printv('\nParameters for registration:') for pStep in range(1, len(paramreg.steps) + 1): sct.printv('Step #' + paramreg.steps[str(pStep)].step, verbose) sct.printv('.. Type #' + paramreg.steps[str(pStep)].type, verbose) sct.printv( '.. Algorithm................ ' + paramreg.steps[str(pStep)].algo, verbose) sct.printv( '.. Metric................... ' + paramreg.steps[str(pStep)].metric, verbose) sct.printv( '.. Number of iterations..... ' + paramreg.steps[str(pStep)].iter, verbose) sct.printv( '.. Shrink factor............ ' + paramreg.steps[str(pStep)].shrink, verbose) sct.printv( '.. Smoothing factor......... ' + paramreg.steps[str(pStep)].smooth, verbose) sct.printv( '.. Gradient step............ ' + paramreg.steps[str(pStep)].gradStep, verbose) sct.printv( '.. Degree of polynomial..... ' + paramreg.steps[str(pStep)].poly, verbose) path_data, file_data, ext_data = sct.extract_fname(fname_data) sct.printv('\nCheck input labels...') # check if label image contains coherent labels image_label = Image(fname_landmarks) # -> all labels must be different labels = image_label.getNonZeroCoordinates(sorting='value') hasDifferentLabels = True for lab in labels: for otherlabel in labels: if lab != otherlabel and lab.hasEqualValue(otherlabel): hasDifferentLabels = False break if not hasDifferentLabels: sct.printv( 'ERROR: Wrong landmarks input. All labels must be different.', verbose, 'error') # all labels must be available in tempalte image_label_template = Image(fname_template_label) labels_template = image_label_template.getNonZeroCoordinates( sorting='value') if labels[-1].value > labels_template[-1].value: sct.printv( 'ERROR: Wrong landmarks input. Labels must have correspondance in tempalte space. \nLabel max ' 'provided: ' + str(labels[-1].value) + '\nLabel max from template: ' + str(labels_template[-1].value), verbose, 'error') # create temporary folder sct.printv('\nCreate temporary folder...', verbose) path_tmp = 'tmp.' + time.strftime("%y%m%d%H%M%S") status, output = sct.run('mkdir ' + path_tmp) # copy files to temporary folder sct.printv('\nCopy files...', verbose) sct.run('isct_c3d ' + fname_data + ' -o ' + path_tmp + '/data.nii') sct.run('isct_c3d ' + fname_landmarks + ' -o ' + path_tmp + '/landmarks.nii.gz') sct.run('isct_c3d ' + fname_seg + ' -o ' + path_tmp + '/segmentation.nii.gz') sct.run('isct_c3d ' + fname_template + ' -o ' + path_tmp + '/template.nii') sct.run('isct_c3d ' + fname_template_label + ' -o ' + path_tmp + '/template_labels.nii.gz') sct.run('isct_c3d ' + fname_template_seg + ' -o ' + path_tmp + '/template_seg.nii.gz') # go to tmp folder os.chdir(path_tmp) # resample data to 1mm isotropic sct.printv('\nResample data to 1mm isotropic...', verbose) sct.run( 'isct_c3d data.nii -resample-mm 1.0x1.0x1.0mm -interpolation Linear -o datar.nii' ) sct.run( 'isct_c3d segmentation.nii.gz -resample-mm 1.0x1.0x1.0mm -interpolation NearestNeighbor -o segmentationr.nii.gz' ) # N.B. resampling of labels is more complicated, because they are single-point labels, therefore resampling with neighrest neighbour can make them disappear. Therefore a more clever approach is required. resample_labels('landmarks.nii.gz', 'datar.nii', 'landmarksr.nii.gz') # # TODO # sct.run('sct_label_utils -i datar.nii -t create -x 124,186,19,2:129,98,23,8 -o landmarksr.nii.gz') # Change orientation of input images to RPI sct.printv('\nChange orientation of input images to RPI...', verbose) set_orientation('datar.nii', 'RPI', 'data_rpi.nii') set_orientation('landmarksr.nii.gz', 'RPI', 'landmarks_rpi.nii.gz') set_orientation('segmentationr.nii.gz', 'RPI', 'segmentation_rpi.nii.gz') # # Change orientation of input images to RPI # sct.printv('\nChange orientation of input images to RPI...', verbose) # set_orientation('data.nii', 'RPI', 'data_rpi.nii') # set_orientation('landmarks.nii.gz', 'RPI', 'landmarks_rpi.nii.gz') # set_orientation('segmentation.nii.gz', 'RPI', 'segmentation_rpi.nii.gz') # get landmarks in native space # crop segmentation # output: segmentation_rpi_crop.nii.gz sct.run( 'sct_crop_image -i segmentation_rpi.nii.gz -o segmentation_rpi_crop.nii.gz -dim 2 -bzmax' ) # straighten segmentation sct.printv('\nStraighten the spinal cord using centerline/segmentation...', verbose) sct.run( 'sct_straighten_spinalcord -i segmentation_rpi_crop.nii.gz -c segmentation_rpi_crop.nii.gz -r 0 -v ' + str(verbose), verbose) # re-define warping field using non-cropped space (to avoid issue #367) sct.run( 'sct_concat_transfo -w warp_straight2curve.nii.gz -d data_rpi.nii -o warp_straight2curve.nii.gz' ) # Label preparation: # -------------------------------------------------------------------------------- # Remove unused label on template. Keep only label present in the input label image sct.printv( '\nRemove unused label on template. Keep only label present in the input label image...', verbose) sct.run( 'sct_label_utils -t remove -i template_labels.nii.gz -o template_label.nii.gz -r landmarks_rpi.nii.gz' ) # Make sure landmarks are INT sct.printv('\nConvert landmarks to INT...', verbose) sct.run( 'isct_c3d template_label.nii.gz -type int -o template_label.nii.gz', verbose) # Create a cross for the template labels - 5 mm sct.printv('\nCreate a 5 mm cross for the template labels...', verbose) sct.run( 'sct_label_utils -t cross -i template_label.nii.gz -o template_label_cross.nii.gz -c 5' ) # Create a cross for the input labels and dilate for straightening preparation - 5 mm sct.printv( '\nCreate a 5mm cross for the input labels and dilate for straightening preparation...', verbose) sct.run( 'sct_label_utils -t cross -i landmarks_rpi.nii.gz -o landmarks_rpi_cross3x3.nii.gz -c 5 -d' ) # Apply straightening to labels sct.printv('\nApply straightening to labels...', verbose) sct.run( 'sct_apply_transfo -i landmarks_rpi_cross3x3.nii.gz -o landmarks_rpi_cross3x3_straight.nii.gz -d segmentation_rpi_crop_straight.nii.gz -w warp_curve2straight.nii.gz -x nn' ) # Convert landmarks from FLOAT32 to INT sct.printv('\nConvert landmarks from FLOAT32 to INT...', verbose) sct.run( 'isct_c3d landmarks_rpi_cross3x3_straight.nii.gz -type int -o landmarks_rpi_cross3x3_straight.nii.gz' ) # Remove labels that do not correspond with each others. sct.printv('\nRemove labels that do not correspond with each others.', verbose) sct.run( 'sct_label_utils -t remove-symm -i landmarks_rpi_cross3x3_straight.nii.gz -o landmarks_rpi_cross3x3_straight.nii.gz,template_label_cross.nii.gz -r template_label_cross.nii.gz' ) # Estimate affine transfo: straight --> template (landmark-based)' sct.printv( '\nEstimate affine transfo: straight anat --> template (landmark-based)...', verbose) # converting landmarks straight and curved to physical coordinates image_straight = Image('landmarks_rpi_cross3x3_straight.nii.gz') landmark_straight = image_straight.getNonZeroCoordinates(sorting='value') image_template = Image('template_label_cross.nii.gz') landmark_template = image_template.getNonZeroCoordinates(sorting='value') # Reorganize landmarks points_fixed, points_moving = [], [] landmark_straight_mean = [] for coord in landmark_straight: if coord.value not in [c.value for c in landmark_straight_mean]: temp_landmark = coord temp_number = 1 for other_coord in landmark_straight: if coord.hasEqualValue(other_coord) and coord != other_coord: temp_landmark += other_coord temp_number += 1 landmark_straight_mean.append(temp_landmark / temp_number) for coord in landmark_straight_mean: point_straight = image_straight.transfo_pix2phys( [[coord.x, coord.y, coord.z]]) points_moving.append( [point_straight[0][0], point_straight[0][1], point_straight[0][2]]) for coord in landmark_template: point_template = image_template.transfo_pix2phys( [[coord.x, coord.y, coord.z]]) points_fixed.append( [point_template[0][0], point_template[0][1], point_template[0][2]]) # Register curved landmarks on straight landmarks based on python implementation sct.printv( '\nComputing rigid transformation (algo=translation-scaling-z) ...', verbose) import msct_register_landmarks (rotation_matrix, translation_array, points_moving_reg, points_moving_barycenter) = \ msct_register_landmarks.getRigidTransformFromLandmarks( points_fixed, points_moving, constraints='translation-scaling-z', show=False) # writing rigid transformation file text_file = open("straight2templateAffine.txt", "w") text_file.write("#Insight Transform File V1.0\n") text_file.write("#Transform 0\n") text_file.write( "Transform: FixedCenterOfRotationAffineTransform_double_3_3\n") text_file.write( "Parameters: %.9f %.9f %.9f %.9f %.9f %.9f %.9f %.9f %.9f %.9f %.9f %.9f\n" % (1.0 / rotation_matrix[0, 0], rotation_matrix[0, 1], rotation_matrix[0, 2], rotation_matrix[1, 0], 1.0 / rotation_matrix[1, 1], rotation_matrix[1, 2], rotation_matrix[2, 0], rotation_matrix[2, 1], 1.0 / rotation_matrix[2, 2], translation_array[0, 0], translation_array[0, 1], -translation_array[0, 2])) text_file.write("FixedParameters: %.9f %.9f %.9f\n" % (points_moving_barycenter[0], points_moving_barycenter[1], points_moving_barycenter[2])) text_file.close() # Apply affine transformation: straight --> template sct.printv('\nApply affine transformation: straight --> template...', verbose) sct.run( 'sct_concat_transfo -w warp_curve2straight.nii.gz,straight2templateAffine.txt -d template.nii -o warp_curve2straightAffine.nii.gz' ) sct.run( 'sct_apply_transfo -i data_rpi.nii -o data_rpi_straight2templateAffine.nii -d template.nii -w warp_curve2straightAffine.nii.gz' ) sct.run( 'sct_apply_transfo -i segmentation_rpi.nii.gz -o segmentation_rpi_straight2templateAffine.nii.gz -d template.nii -w warp_curve2straightAffine.nii.gz -x linear' ) # threshold to 0.5 nii = Image('segmentation_rpi_straight2templateAffine.nii.gz') data = nii.data data[data < 0.5] = 0 nii.data = data nii.setFileName('segmentation_rpi_straight2templateAffine_th.nii.gz') nii.save() # find min-max of anat2template (for subsequent cropping) zmin_template, zmax_template = find_zmin_zmax( 'segmentation_rpi_straight2templateAffine_th.nii.gz') # crop template in z-direction (for faster processing) sct.printv('\nCrop data in template space (for faster processing)...', verbose) sct.run( 'sct_crop_image -i template.nii -o template_crop.nii -dim 2 -start ' + str(zmin_template) + ' -end ' + str(zmax_template)) sct.run( 'sct_crop_image -i template_seg.nii.gz -o template_seg_crop.nii.gz -dim 2 -start ' + str(zmin_template) + ' -end ' + str(zmax_template)) sct.run( 'sct_crop_image -i data_rpi_straight2templateAffine.nii -o data_rpi_straight2templateAffine_crop.nii -dim 2 -start ' + str(zmin_template) + ' -end ' + str(zmax_template)) sct.run( 'sct_crop_image -i segmentation_rpi_straight2templateAffine.nii.gz -o segmentation_rpi_straight2templateAffine_crop.nii.gz -dim 2 -start ' + str(zmin_template) + ' -end ' + str(zmax_template)) # sub-sample in z-direction sct.printv('\nSub-sample in z-direction (for faster processing)...', verbose) sct.run( 'sct_resample -i template_crop.nii -o template_crop_r.nii -f 1x1x' + zsubsample, verbose) sct.run( 'sct_resample -i template_seg_crop.nii.gz -o template_seg_crop_r.nii.gz -f 1x1x' + zsubsample, verbose) sct.run( 'sct_resample -i data_rpi_straight2templateAffine_crop.nii -o data_rpi_straight2templateAffine_crop_r.nii -f 1x1x' + zsubsample, verbose) sct.run( 'sct_resample -i segmentation_rpi_straight2templateAffine_crop.nii.gz -o segmentation_rpi_straight2templateAffine_crop_r.nii.gz -f 1x1x' + zsubsample, verbose) # Registration straight spinal cord to template sct.printv('\nRegister straight spinal cord to template...', verbose) # loop across registration steps warp_forward = [] warp_inverse = [] for i_step in range(1, len(paramreg.steps) + 1): sct.printv( '\nEstimate transformation for step #' + str(i_step) + '...', verbose) # identify which is the src and dest if paramreg.steps[str(i_step)].type == 'im': src = 'data_rpi_straight2templateAffine_crop_r.nii' dest = 'template_crop_r.nii' interp_step = 'linear' elif paramreg.steps[str(i_step)].type == 'seg': src = 'segmentation_rpi_straight2templateAffine_crop_r.nii.gz' dest = 'template_seg_crop_r.nii.gz' interp_step = 'nn' else: sct.printv('ERROR: Wrong image type.', 1, 'error') # if step>1, apply warp_forward_concat to the src image to be used if i_step > 1: # sct.run('sct_apply_transfo -i '+src+' -d '+dest+' -w '+','.join(warp_forward)+' -o '+sct.add_suffix(src, '_reg')+' -x '+interp_step, verbose) sct.run( 'sct_apply_transfo -i ' + src + ' -d ' + dest + ' -w ' + ','.join(warp_forward) + ' -o ' + sct.add_suffix(src, '_reg') + ' -x ' + interp_step, verbose) src = sct.add_suffix(src, '_reg') # register src --> dest warp_forward_out, warp_inverse_out = register(src, dest, paramreg, param, str(i_step)) warp_forward.append(warp_forward_out) warp_inverse.append(warp_inverse_out) # Concatenate transformations: sct.printv('\nConcatenate transformations: anat --> template...', verbose) sct.run( 'sct_concat_transfo -w warp_curve2straightAffine.nii.gz,' + ','.join(warp_forward) + ' -d template.nii -o warp_anat2template.nii.gz', verbose) # sct.run('sct_concat_transfo -w warp_curve2straight.nii.gz,straight2templateAffine.txt,'+','.join(warp_forward)+' -d template.nii -o warp_anat2template.nii.gz', verbose) warp_inverse.reverse() sct.run( 'sct_concat_transfo -w ' + ','.join(warp_inverse) + ',-straight2templateAffine.txt,warp_straight2curve.nii.gz -d data.nii -o warp_template2anat.nii.gz', verbose) # Apply warping fields to anat and template if output_type == 1: sct.run( 'sct_apply_transfo -i template.nii -o template2anat.nii.gz -d data.nii -w warp_template2anat.nii.gz -c 1', verbose) sct.run( 'sct_apply_transfo -i data.nii -o anat2template.nii.gz -d template.nii -w warp_anat2template.nii.gz -c 1', verbose) # come back to parent folder os.chdir('..') # Generate output files sct.printv('\nGenerate output files...', verbose) sct.generate_output_file(path_tmp + '/warp_template2anat.nii.gz', 'warp_template2anat.nii.gz', verbose) sct.generate_output_file(path_tmp + '/warp_anat2template.nii.gz', 'warp_anat2template.nii.gz', verbose) if output_type == 1: sct.generate_output_file(path_tmp + '/template2anat.nii.gz', 'template2anat' + ext_data, verbose) sct.generate_output_file(path_tmp + '/anat2template.nii.gz', 'anat2template' + ext_data, verbose) # Delete temporary files if remove_temp_files: sct.printv('\nDelete temporary files...', verbose) sct.run('rm -rf ' + path_tmp) # display elapsed time elapsed_time = time.time() - start_time sct.printv( '\nFinished! Elapsed time: ' + str(int(round(elapsed_time))) + 's', verbose) # to view results sct.printv('\nTo view results, type:', verbose) sct.printv('fslview ' + fname_data + ' template2anat -b 0,4000 &', verbose, 'info') sct.printv('fslview ' + fname_template + ' -b 0,5000 anat2template &\n', verbose, 'info')