def main(): # Get parser info parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_data = arguments['-i'] fname_mask = arguments['-m'] vert_label_fname = arguments["-vertfile"] vert_levels = arguments["-vert"] slices_of_interest = arguments["-z"] index_vol = arguments['-vol'] method = arguments["-method"] # Load data and orient to RPI data = Image(fname_data).change_orientation('RPI').data mask = Image(fname_mask).change_orientation('RPI').data # Fetch slices to compute SNR on slices_list = [] if not vert_levels == 'None': list_levels = parse_num_list(vert_levels) im_vertlevel = Image(vert_label_fname).change_orientation('RPI') for level in list_levels: slices_list.append(get_slices_from_vertebral_levels(im_vertlevel, level)) if slices_list == []: sct.log.error('The specified vertebral levels are not in the vertebral labeling file.') else: slices_list = reduce(operator.add, slices_list) # flatten and sort slices_list.sort() elif not slices_of_interest == 'None': slices_list = parse_num_list(slices_of_interest) else: slices_list = np.arange(data.shape[2]).tolist() # Set to 0 all slices in the mask that are not includes in the slices_list nz_to_exclude = [i for i in range(mask.shape[2]) if not i in slices_list] mask[:, :, nz_to_exclude] = 0 # if user selected all 3d volumes from the input 4d volume ("-vol -1"), then assign index_vol if index_vol[0] == -1: index_vol = range(data.shape[3]) # Get signal and noise indexes_roi = np.where(mask == 1) if method == 'mult': # get voxels in ROI to obtain a (x*y*z)*t 2D matrix data_in_roi = data[indexes_roi] # compute signal and STD across by averaging across time signal = np.mean(data_in_roi[:, index_vol]) std_input_temporal = np.std(data_in_roi[:, index_vol], 1) noise = np.mean(std_input_temporal) elif method == 'diff': # if user did not select two volumes, then exit with error if not len(index_vol) == 2: sct.printv('ERROR: ' + str(len(index_vol)) + ' volumes were specified. Method "diff" should be used with ' 'exactly two volumes (check flag "vol").', 1, 'error') data_1 = data[:, :, :, index_vol[0]] data_2 = data[:, :, :, index_vol[1]] # compute voxel-average of voxelwise sum signal = np.mean(np.add(data_1[indexes_roi], data_2[indexes_roi])) # compute voxel-STD of voxelwise substraction, multiplied by sqrt(2) as described in equation 7 of Dietrich et al. noise = np.std(np.subtract(data_1[indexes_roi], data_2[indexes_roi])) * np.sqrt(2) # compute SNR SNR = signal / noise # Display result sct.printv('\nSNR_' + method + ' = ' + str(SNR) + '\n', type='info')
def main(args=None): parser = get_parser() if args: arguments = parser.parse_args(args) else: arguments = parser.parse_args(args=None if sys.argv[1:] else ['--help']) verbosity = arguments.v init_sct(log_level=verbosity, update=True) # Update log level input_filename = arguments.i output_fname = arguments.o img = Image(input_filename) dtype = None if arguments.add is not None: value = arguments.add out = sct_labels.add(img, value) elif arguments.create is not None: labels = arguments.create out = sct_labels.create_labels_empty(img, labels) elif arguments.create_add is not None: labels = arguments.create_add out = sct_labels.create_labels(img, labels) elif arguments.create_seg is not None: labels = arguments.create_seg out = sct_labels.create_labels_along_segmentation(img, labels) elif arguments.cubic_to_point: out = sct_labels.cubic_to_point(img) elif arguments.display: display_voxel(img, verbosity) return elif arguments.increment: out = sct_labels.increment_z_inverse(img) elif arguments.disc is not None: ref = Image(arguments.disc) out = sct_labels.labelize_from_discs(img, ref) elif arguments.vert_body is not None: levels = arguments.vert_body if len(levels) == 1 and levels[0] == 0: levels = None # all levels out = sct_labels.label_vertebrae(img, levels) elif arguments.vert_continuous: out = sct_labels.continuous_vertebral_levels(img) dtype = 'float32' elif arguments.MSE is not None: ref = Image(arguments.MSE) mse = sct_labels.compute_mean_squared_error(img, ref) printv(f"Computed MSE: {mse}") return elif arguments.remove_reference is not None: ref = Image(arguments.remove_reference) out = sct_labels.remove_missing_labels(img, ref) elif arguments.remove_sym is not None: # first pass use img as source ref = Image(arguments.remove_reference) out = sct_labels.remove_missing_labels(img, ref) # second pass use previous pass result as reference ref_out = sct_labels.remove_missing_labels(ref, out) ref_out.save(path=ref.absolutepath) elif arguments.remove is not None: labels = arguments.remove out = sct_labels.remove_labels_from_image(img, labels) elif arguments.keep is not None: labels = arguments.keep out = sct_labels.remove_other_labels_from_image(img, labels) elif arguments.create_viewer is not None: msg = "" if arguments.msg is None else f"{arguments.msg}\n" if arguments.ilabel is not None: input_labels_img = Image(arguments.ilabel) out = launch_manual_label_gui(img, input_labels_img, parse_num_list(arguments.create_viewer), msg) else: out = launch_sagittal_viewer(img, parse_num_list(arguments.create_viewer), msg) printv("Generating output files...") out.save(path=output_fname, dtype=dtype) display_viewer_syntax([input_filename, output_fname]) if arguments.qc is not None: generate_qc(fname_in1=input_filename, fname_seg=output_fname, args=args, path_qc=os.path.abspath(arguments.qc), dataset=arguments.qc_dataset, subject=arguments.qc_subject, process='sct_label_utils')
def main(): # Default params param = Param() # Get parser info parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_data = arguments['-i'] if '-m' in arguments: fname_mask = arguments['-m'] else: fname_mask = '' method = arguments["-method"] if '-vol' in arguments: index_vol_user = arguments['-vol'] else: index_vol_user = '' # 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) # 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 main(fname_data, path_label, method, slices, levels, fname_output, labels_user, append_csv, fname_vertebral_labeling="", perslice=1, perlevel=1, verbose=1, combine_labels=True): """ Extract metrics from MRI data based on mask (could be single file of folder to atlas) :param fname_data: data to extract metric from :param path_label: mask: could be single file or folder to atlas (which contains info_label.txt) :param method {'wa', 'bin', 'ml', 'map'} :param slices. Slices of interest. Accepted format: "0,1,2,3": slices 0,1,2,3 "0:3": slices 0,1,2,3 :param levels: Vertebral levels to extract metrics from. Should be associated with a template (e.g. PAM50/template/) or a specified file: fname_vertebral_labeling. Same format as slices_of_interest. :param fname_output: :param labels_user: :param append_csv: Append to csv file :param fname_normalizing_label: :param fname_vertebral_labeling: vertebral labeling to be used with vertebral_levels :param perslice: if user selected several slices, then the function outputs a metric within each slice instead of a single average output. :param perlevel: if user selected several levels, then the function outputs a metric within each vertebral level instead of a single average output. :param verbose :param combine_labels: bool: Combine labels into a single value :return: """ # check if path_label is a file (e.g., single binary mask) instead of a folder (e.g., SCT atlas structure which # contains info_label.txt file) if os.path.isfile(path_label): # Label is a single file indiv_labels_ids = [0] indiv_labels_files = [path_label] combined_labels_ids = [] label_struc = {0: LabelStruc(id=0, name=sct.extract_fname(path_label)[1], filename=path_label)} # set path_label to empty string, because indiv_labels_files will replace it from now on path_label = '' elif os.path.isdir(path_label): # Labels is an SCT atlas folder structure # Parse labels according to the file info_label.txt # Note: the "combined_labels_*" is a list of single labels that are defined in the section defined by the keyword # "# Keyword=CombinedLabels" in info_label.txt. # TODO: redirect to appropriate Sphinx documentation # TODO: output Class instead of multiple variables. # Example 1: # label_struc[2].id = (2) # label_struc[2].name = "left fasciculus cuneatus" # label_struc[2].filename = "PAM50_atlas_02.nii.gz" # Example 2: # label_struc[51].id = (1, 2, 3, ..., 29) # label_struc[51].name = "White Matter" # label_struc[51].filename = "" # no name because it is combined indiv_labels_ids, indiv_labels_names, indiv_labels_files, \ combined_labels_ids, combined_labels_names, combined_labels_id_groups, map_clusters \ = read_label_file(path_label, param_default.file_info_label) label_struc = {} # fill IDs for indiv labels for i_label in range(len(indiv_labels_ids)): label_struc[indiv_labels_ids[i_label]] = LabelStruc(id=indiv_labels_ids[i_label], name=indiv_labels_names[i_label], filename=indiv_labels_files[i_label], map_cluster=[indiv_labels_ids[i_label] in map_cluster for map_cluster in map_clusters].index(True)) # fill IDs for combined labels # TODO: problem for defining map_cluster: if labels overlap two regions, e.g. WM and GM (e.g. id=50), # map_cluster will take value 0, which is wrong. for i_label in range(len(combined_labels_ids)): label_struc[combined_labels_ids[i_label]] = LabelStruc(id=combined_labels_id_groups[i_label], name=combined_labels_names[i_label], map_cluster=[indiv_labels_ids[i_label] in map_cluster for map_cluster in map_clusters].index(True)) else: raise RuntimeError(path_label + ' does not exist') # check syntax of labels asked by user labels_id_user = check_labels(indiv_labels_ids + combined_labels_ids, parse_num_list(labels_user)) nb_labels = len(indiv_labels_files) # Load data and systematically reorient to RPI because we need the 3rd dimension to be z sct.printv('\nLoad metric image...', verbose) input_im = Image(fname_data).change_orientation("RPI") data = Metric(data=input_im.data, label='') # Load labels labels_tmp = np.empty([nb_labels], dtype=object) for i_label in range(nb_labels): im_label = Image(os.path.join(path_label, indiv_labels_files[i_label])).change_orientation("RPI") labels_tmp[i_label] = np.expand_dims(im_label.data, 3) # TODO: generalize to 2D input label labels = np.concatenate(labels_tmp[:], 3) # labels: (x,y,z,label) # Load vertebral levels if vertebral_levels: im_vertebral_labeling = Image(fname_vertebral_labeling).change_orientation("RPI") else: im_vertebral_labeling = None # Get dimensions of data and labels nx, ny, nz = data.data.shape nx_atlas, ny_atlas, nz_atlas, nt_atlas = labels.shape # Check dimensions consistency between atlas and data if (nx, ny, nz) != (nx_atlas, ny_atlas, nz_atlas): sct.printv('\nERROR: Metric data and labels DO NOT HAVE SAME DIMENSIONS.', 1, type='error') # Combine individual labels for estimation if combine_labels: # Add entry with internal ID value (99) which corresponds to combined labels label_struc[99] = LabelStruc(id=labels_id_user, name=','.join([str(i) for i in labels_id_user]), map_cluster=None) labels_id_user = [99] for id_label in labels_id_user: sct.printv('Estimation for label: '+label_struc[id_label].name, verbose) agg_metric = extract_metric(data, labels=labels, slices=slices, levels=levels, perslice=perslice, perlevel=perlevel, vert_level=im_vertebral_labeling, method=method, label_struc=label_struc, id_label=id_label, indiv_labels_ids=indiv_labels_ids) save_as_csv(agg_metric, fname_output, fname_in=fname_data, append=append_csv) append_csv = True # when looping across labels, need to append results in the same file sct.display_open(fname_output)
def main(args): parser = get_parser() arguments = parser.parse(args) # Initialization slices = '' group_funcs = (('MEAN', func_wa), ('STD', func_std)) # functions to perform when aggregating metrics along S-I fname_segmentation = sct.get_absolute_path(arguments['-i']) fname_vert_levels = '' if '-o' in arguments: file_out = os.path.abspath(arguments['-o']) else: file_out = '' if '-append' in arguments: append = int(arguments['-append']) else: append = 0 if '-vert' in arguments: vert_levels = arguments['-vert'] else: vert_levels = '' if '-r' in arguments: remove_temp_files = arguments['-r'] if '-vertfile' in arguments: fname_vert_levels = arguments['-vertfile'] if '-perlevel' in arguments: perlevel = arguments['-perlevel'] else: perlevel = None if '-z' in arguments: slices = arguments['-z'] if '-perslice' in arguments: perslice = arguments['-perslice'] else: perslice = None if '-angle-corr' in arguments: if arguments['-angle-corr'] == '1': angle_correction = True elif arguments['-angle-corr'] == '0': angle_correction = False param_centerline = ParamCenterline( algo_fitting=arguments['-centerline-algo'], smooth=arguments['-centerline-smooth'], minmax=True) path_qc = arguments.get("-qc", None) qc_dataset = arguments.get("-qc-dataset", None) qc_subject = arguments.get("-qc-subject", None) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level # update fields metrics_agg = {} if not file_out: file_out = 'csa.csv' metrics, fit_results = process_seg.compute_shape(fname_segmentation, angle_correction=angle_correction, param_centerline=param_centerline, verbose=verbose) for key in metrics: metrics_agg[key] = aggregate_per_slice_or_level(metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=group_funcs) metrics_agg_merged = _merge_dict(metrics_agg) save_as_csv(metrics_agg_merged, file_out, fname_in=fname_segmentation, append=append) # QC report (only show CSA for clarity) if path_qc is not None: generate_qc(fname_segmentation, args=args, path_qc=os.path.abspath(path_qc), dataset=qc_dataset, subject=qc_subject, path_img=_make_figure(metrics_agg_merged, fit_results), process='sct_process_segmentation') sct.display_open(file_out)
def main(args): parser = get_parser() arguments = parser.parse(args) param = Param() # Initialization slices = param.slices group_funcs = (('MEAN', func_wa), ('STD', func_std)) # functions to perform when aggregating metrics along S-I fname_segmentation = sct.get_absolute_path(arguments['-i']) fname_vert_levels = '' if '-o' in arguments: file_out = os.path.abspath(arguments['-o']) else: file_out = '' if '-append' in arguments: append = int(arguments['-append']) else: append = 0 if '-vert' in arguments: vert_levels = arguments['-vert'] else: vert_levels = '' if '-r' in arguments: remove_temp_files = arguments['-r'] if '-vertfile' in arguments: fname_vert_levels = arguments['-vertfile'] if '-perlevel' in arguments: perlevel = arguments['-perlevel'] else: perlevel = Param().perlevel if '-z' in arguments: slices = arguments['-z'] if '-perslice' in arguments: perslice = arguments['-perslice'] else: perslice = Param().perslice if '-angle-corr' in arguments: if arguments['-angle-corr'] == '1': angle_correction = True elif arguments['-angle-corr'] == '0': angle_correction = False verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level # update fields metrics_agg = {} if not file_out: file_out = 'csa.csv' metrics = process_seg.compute_shape(fname_segmentation, algo_fitting='bspline', angle_correction=angle_correction, verbose=verbose) for key in metrics: metrics_agg[key] = aggregate_per_slice_or_level(metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=group_funcs) metrics_agg_merged = _merge_dict(metrics_agg) save_as_csv(metrics_agg_merged, file_out, fname_in=fname_segmentation, append=append) sct.display_open(file_out)
arguments = parser.parse_args(args=None if sys.argv[1:] else ['--help']) overwrite = 0 # TODO: Not used. Why? fname_data = sct.get_absolute_path(arguments.i) path_label = arguments.f method = arguments.method fname_output = arguments.o append_csv = arguments.append combine_labels = arguments.combine labels_user = arguments.l adv_param_user = arguments.param # TODO: Not used. Why? slices_of_interest = arguments.z vertebral_levels = arguments.vert fname_vertebral_labeling = arguments.vertfile perslice = arguments.perslice perlevel = arguments.perlevel fname_normalizing_label = arguments.norm_file # TODO: Not used. Why? normalization_method = arguments.norm_method # TODO: Not used. Why? label_to_fix = arguments.fix_label # TODO: Not used. Why? fname_output_metric_map = arguments.output_map # TODO: Not used. Why? fname_mask_weight = arguments.mask_weighted # TODO: Not used. Why? discard_negative_values = int(arguments.discard_neg_val) # TODO: Not used. Why? verbose = int(arguments.v) init_sct(log_level=verbose, update=True) # Update log level # call main function main(fname_data=fname_data, path_label=path_label, method=method, slices=parse_num_list(slices_of_interest), levels=parse_num_list(vertebral_levels), fname_output=fname_output, labels_user=labels_user, append_csv=append_csv, fname_vertebral_labeling=fname_vertebral_labeling, perslice=perslice, perlevel=perlevel, verbose=verbose, combine_labels=combine_labels)
def main(args): parser = get_parser() arguments = parser.parse(args) param = Param() # Initialization slices = param.slices angle_correction = True use_phys_coord = True group_funcs = (('MEAN', func_wa), ('STD', func_std)) # functions to perform when aggregating metrics along S-I fname_segmentation = sct.get_absolute_path(arguments['-i']) name_process = arguments['-p'] fname_vert_levels = '' if '-o' in arguments: file_out = os.path.abspath(arguments['-o']) else: file_out = '' if '-append' in arguments: append = int(arguments['-append']) else: append = 0 if '-vert' in arguments: vert_levels = arguments['-vert'] else: vert_levels = '' if '-r' in arguments: remove_temp_files = arguments['-r'] if '-vertfile' in arguments: fname_vert_levels = arguments['-vertfile'] if '-perlevel' in arguments: perlevel = arguments['-perlevel'] else: perlevel = Param().perlevel if '-v' in arguments: verbose = int(arguments['-v']) if '-z' in arguments: slices = arguments['-z'] if '-perslice' in arguments: perslice = arguments['-perslice'] else: perslice = Param().perslice if '-a' in arguments: param.algo_fitting = arguments['-a'] if '-no-angle' in arguments: if arguments['-no-angle'] == '1': angle_correction = False elif arguments['-no-angle'] == '0': angle_correction = True if '-use-image-coord' in arguments: if arguments['-use-image-coord'] == '1': use_phys_coord = False if arguments['-use-image-coord'] == '0': use_phys_coord = True # update fields param.verbose = verbose metrics_agg = {} if not file_out: file_out = name_process + '.csv' if name_process == 'centerline': process_seg.extract_centerline(fname_segmentation, verbose=param.verbose, algo_fitting=param.algo_fitting, use_phys_coord=use_phys_coord, file_out=file_out) if name_process == 'csa': metrics = process_seg.compute_csa(fname_segmentation, algo_fitting=param.algo_fitting, type_window=param.type_window, window_length=param.window_length, angle_correction=angle_correction, use_phys_coord=use_phys_coord, remove_temp_files=remove_temp_files, verbose=verbose) for key in metrics: metrics_agg[key] = aggregate_per_slice_or_level(metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=group_funcs) metrics_agg_merged = merge_dict(metrics_agg) save_as_csv(metrics_agg_merged, file_out, fname_in=fname_segmentation, append=append) sct.printv('\nFile created: '+file_out, verbose=1, type='info') if name_process == 'label-vert': if '-discfile' in arguments: fname_discs = arguments['-discfile'] else: sct.printv('\nERROR: Disc label file is mandatory (flag: -discfile).\n', 1, 'error') process_seg.label_vert(fname_segmentation, fname_discs, verbose=verbose) if name_process == 'shape': fname_discs = None if '-discfile' in arguments: fname_discs = arguments['-discfile'] metrics = process_seg.compute_shape(fname_segmentation, remove_temp_files=remove_temp_files, verbose=verbose) for key in metrics: metrics_agg[key] = aggregate_per_slice_or_level(metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=group_funcs) metrics_agg_merged = merge_dict(metrics_agg) save_as_csv(metrics_agg_merged, file_out, fname_in=fname_segmentation, append=append) sct.printv('\nFile created: ' + file_out, verbose=1, type='info')
if '-output-map' in arguments: fname_output_metric_map = arguments['-output-map'] else: fname_output_metric_map = '' if '-mask-weighted' in arguments: fname_mask_weight = arguments['-mask-weighted'] else: fname_mask_weight = '' # if 'discard_negative_values' in arguments: discard_negative_values = int(arguments['-discard-neg-val']) # call main function main(fname_data, path_label, method, parse_num_list(slices_of_interest), parse_num_list(vertebral_levels), fname_output, labels_user, append, fname_normalizing_label, normalization_method, label_to_fix, adv_param_user, fname_output_metric_map, fname_mask_weight, fname_vertebral_labeling=fname_vertebral_labeling, perslice=perslice, perlevel=perlevel, discard_negative_values=discard_negative_values)
def main(args=None): parser = get_parser() if args: arguments = parser.parse_args(args) else: arguments = parser.parse_args( args=None if sys.argv[1:] else ['--help']) # Initialization slices = '' group_funcs = (('MEAN', func_wa), ('STD', func_std) ) # functions to perform when aggregating metrics along S-I fname_segmentation = sct.get_absolute_path(arguments.i) fname_vert_levels = '' if arguments.o is not None: file_out = os.path.abspath(arguments.o) else: file_out = '' if arguments.append is not None: append = arguments.append else: append = 0 if arguments.vert is not None: vert_levels = arguments.vert else: vert_levels = '' remove_temp_files = arguments.r if arguments.vertfile is not None: fname_vert_levels = arguments.vertfile if arguments.perlevel is not None: perlevel = arguments.perlevel else: perlevel = None if arguments.z is not None: slices = arguments.z if arguments.perslice is not None: perslice = arguments.perslice else: perslice = None angle_correction = arguments.angle_corr param_centerline = ParamCenterline(algo_fitting=arguments.centerline_algo, smooth=arguments.centerline_smooth, minmax=True) path_qc = arguments.qc qc_dataset = arguments.qc_dataset qc_subject = arguments.qc_subject verbose = int(arguments.v) init_sct(log_level=verbose, update=True) # Update log level # update fields metrics_agg = {} if not file_out: file_out = 'csa.csv' metrics, fit_results = process_seg.compute_shape( fname_segmentation, angle_correction=angle_correction, param_centerline=param_centerline, verbose=verbose) for key in metrics: if key == 'length': # For computing cord length, slice-wise length needs to be summed across slices metrics_agg[key] = aggregate_per_slice_or_level( metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=(('SUM', func_sum), )) else: # For other metrics, we compute the average and standard deviation across slices metrics_agg[key] = aggregate_per_slice_or_level( metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=group_funcs) metrics_agg_merged = merge_dict(metrics_agg) save_as_csv(metrics_agg_merged, file_out, fname_in=fname_segmentation, append=append) # QC report (only show CSA for clarity) if path_qc is not None: generate_qc(fname_segmentation, args=args, path_qc=os.path.abspath(path_qc), dataset=qc_dataset, subject=qc_subject, path_img=_make_figure(metrics_agg_merged, fit_results), process='sct_process_segmentation') sct.display_open(file_out)
def main(argv=None): parser = get_parser() arguments = parser.parse_args(argv) verbose = arguments.v set_loglevel(verbose=verbose) # Default params param = Param() # Get parser info fname_data = arguments.i fname_mask = arguments.m fname_mask_noise = arguments.m_noise method = arguments.method file_name = arguments.o rayleigh_correction = arguments.rayleigh # Check parameters if method in ['diff', 'single']: if not fname_mask: raise parser.error( f"Argument '-m' must be specified when using '-method {method}'." ) # Load data im_data = Image(fname_data) data = im_data.data dim = len(data.shape) nz = data.shape[2] if fname_mask: mask = Image(fname_mask).data # Check dimensionality if method in ['diff', 'mult']: if dim != 4: raise ValueError( f"Input data dimension: {dim}. Input dimension for this method should be 4." ) if method in ['single']: if dim not in [3, 4]: raise ValueError( f"Input data dimension: {dim}. Input dimension for this method should be 3 or 4." ) # Check dimensionality of mask if fname_mask: if len(mask.shape) != 3: raise ValueError( f"Mask should be a 3D image, but the input mask has shape '{mask.shape}'." ) # Retrieve selected volumes index_vol = parse_num_list(arguments.vol) if not index_vol: if method == 'mult': index_vol = range(data.shape[3]) elif method == 'diff': index_vol = [0, 1] elif method == 'single': index_vol = [0] # 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 = 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 = 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: snr_roi = np.average(snr_map[mask_std_nonzero], weights=mask[mask_std_nonzero]) elif method == 'diff': # Check user selected exactly 2 volumes for this method. if not len(index_vol) == 2: raise ValueError( f"Number of selected volumes: {len(index_vol)}. The method 'diff' should be used with " f"exactly 2 volumes. You can specify the number of volumes with the flag '-vol'." ) data_2vol = np.take(data, index_vol, axis=3) # Compute mean across the two volumes data_mean = np.mean(data_2vol, axis=3) # Compute mean in ROI for each z-slice, if the slice in the mask is not null mean_in_roi = [ np.average(data_mean[..., iz], weights=mask[..., iz]) for iz in range(nz) if np.any(mask[..., iz]) ] data_sub = np.subtract(data_2vol[:, :, :, 1], data_2vol[:, :, :, 0]) # Compute STD in the ROI for each z-slice. The "np.sqrt(2)" results from the variance of the subtraction of two # distributions: var(A-B) = var(A) + var(B). # More context in: https://github.com/spinalcordtoolbox/spinalcordtoolbox/issues/3481 std_in_roi = [ weighted_std(data_sub[..., iz] / np.sqrt(2), weights=mask[..., iz]) for iz in range(nz) if np.any(mask[..., iz]) ] # Compute SNR snr_roi_slicewise = [m / s for m, s in zip(mean_in_roi, std_in_roi)] snr_roi = sum(snr_roi_slicewise) / len(snr_roi_slicewise) elif method == 'single': # Check that the input volume is 3D, or if it is 4D, that the user selected exactly 1 volume for this method. if dim == 3: data3d = data elif dim == 4: if not len(index_vol) == 1: raise ValueError( f"Selected volumes: {index_vol}. The method 'single' should be used with " f"exactly 1 volume. You can specify the index of the volume with the flag '-vol'." ) data3d = np.squeeze(data[..., index_vol]) # Check that input noise mask is provided if fname_mask_noise: mask_noise = Image(fname_mask_noise).data else: raise parser.error( "A noise mask is mandatory with '-method single'.") # Check dimensionality of the noise mask if len(mask_noise.shape) != 3: raise ValueError( f"Input noise mask dimension: {dim}. Input dimension for the noise mask should be 3." ) # Check that non-null slices are consistent between mask and mask_noise. for iz in range(nz): if not np.any(mask[..., iz]) == np.any(mask_noise[..., iz]): raise ValueError( f"Slice {iz} is empty in either mask or mask_noise. Non-null slices should be " f"consistent between mask and mask_noise.") # Compute mean in ROI for each z-slice, if the slice in the mask is not null mean_in_roi = [ np.average(data3d[..., iz], weights=mask[..., iz]) for iz in range(nz) if np.any(mask[..., iz]) ] std_in_roi = [ weighted_std(data3d[..., iz], weights=mask_noise[..., iz]) for iz in range(nz) if np.any(mask_noise[..., iz]) ] # Compute SNR snr_roi_slicewise = [m / s for m, s in zip(mean_in_roi, std_in_roi)] snr_roi = sum(snr_roi_slicewise) / len(snr_roi_slicewise) if rayleigh_correction: # Correcting for Rayleigh noise (see eq. A12 in Dietrich et al.) snr_roi *= np.sqrt((4 - np.pi) / 2) # Display result if fname_mask: printv('\nSNR_' + method + ' = ' + str(snr_roi) + '\n', type='info') # Added function for text file if file_name is not None: with open(file_name, "w") as f: f.write(str(snr_roi)) printv('\nFile saved to ' + file_name)
def load(self, file, parent=None, verify=True): """ Load contents from file :param file: input filename or file-like object to load from """ # reset contents self.__init__() if isinstance(file, str): parent = os.path.dirname(file) file = io.open(file, "rb") section = "" for idx_line, line in enumerate(file): line = line.rstrip().decode("utf-8") if line == "": continue # update section index m_sec = re.match(r"^# Keyword=(?P<kw>IndivLabels|CombinedLabels|MAPLabels)\s*(?P<comment>.*)$", line) if m_sec is not None: section = m_sec.group("kw") continue m_comment = re.match(r"#.*", line) if m_comment is not None: continue if section == 'IndivLabels': m = re.match(r"^(?P<id>\d+), (?P<name>.*), (?P<filename>.*)$", line) if m is None: raise ValueError("Unexpected at line {}, in IndivLabels section: {}".format(idx_line+1, line)) _id = int(m.group("id")) _name = m.group("name") _filename = m.group("filename") if verify and parent is not None: if not os.path.exists(os.path.join(parent, _filename)): raise ValueError("Unexpected at line {}, specifying file {} which doesn't exist: {}".format(idx_line+1, _filename, line)) self._indiv_labels.append((_id, _name, _filename)) elif section == 'CombinedLabels': m = re.match(r"^(?P<id>\d+), (?P<name>.*), (?P<group>.*)$", line) if m is None: raise ValueError("Unexpected at line {}, in CombinedLabels section: {}".format(idx_line+1, line)) _id = int(m.group("id")) _name = m.group("name") try: _group = parse_num_list(m.group("group")) except ValueError as e: raise ValueError("Unexpected at line {}: {} in line: {}".format(idx_line+1, e, line)) self._combined_labels.append((_id, _name, _group)) elif section == 'MAPLabels': m = re.match(r"^(?P<name>.*), (?P<group>.*)$", line) if m is None: raise ValueError("Unexpected at line {}, in MAPLabels section: {}".format(idx_line+1, line)) _name = m.group("name") try: _group = parse_num_list(m.group("group")) except ValueError as e: raise ValueError("Unexpected at line {}: {} in line: {}".format(idx_line+1, e, line)) self._clusters_apriori.append((_name, _group)) else: raise ValueError("Unexpected at line {}, unparsed data: {}".format(idx_line+1, line))
def main(fname_data, path_label, method, slices, levels, fname_output, labels_user, append, fname_normalizing_label, normalization_method, label_to_fix, adv_param_user, fname_output_metric_map, fname_mask_weight, fname_vertebral_labeling="", perslice=1, perlevel=1, discard_negative_values=False, verbose=1): """ Extract metrics from MRI data based on mask (could be single file of folder to atlas) :param fname_data: data to extract metric from :param path_label: mask: could be single file or folder to atlas (which contains info_label.txt) :param method: :param slices_of_interest. Accepted format: "0,1,2,3": slices 0,1,2,3 "0:3": slices 0,1,2,3 :param vertebral_levels: Vertebral levels to extract metrics from. Should be associated with a template (e.g. PAM50/template/) or a specified file: fname_vertebral_labeling. Same format as slices_of_interest. :param fname_output: :param labels_user: :param overwrite: :param fname_normalizing_label: :param normalization_method: :param label_to_fix: :param adv_param_user: :param fname_output_metric_map: :param fname_mask_weight: :param fname_vertebral_labeling: vertebral labeling to be used with vertebral_levels :param perslice: if user selected several slices, then the function outputs a metric within each slice instead of a single average output. :param perlevel: if user selected several levels, then the function outputs a metric within each vertebral level instead of a single average output. :param discard_negative_values: Bool: Discard negative voxels when computing metrics statistics :param verbose :return: """ # check if path_label is a file (e.g., single binary mask) instead of a folder (e.g., SCT atlas structure which # contains info_label.txt file) if os.path.isfile(path_label): # Label is a single file indiv_labels_ids = [0] indiv_labels_names = [path_label] indiv_labels_files = [path_label] combined_labels_ids = [] combined_labels_names = [] combined_labels_id_groups = [] map_clusters = [] label_struc = {0: LabelStruc(id=0, name=sct.extract_fname(path_label)[1], filename=path_label)} # set path_label to empty string, because indiv_labels_files will replace it from now on path_label = '' elif os.path.isdir(path_label): # Labels is an SCT atlas folder structure # Parse labels according to the file info_label.txt # Note: the "combined_labels_*" is a list of single labels that are defined in the section defined by the keyword # "# Keyword=CombinedLabels" in info_label.txt. # TODO: redirect to appropriate Sphinx documentation # TODO: output Class instead of multiple variables. # Example 1: # label_struc[2].id = (2) # label_struc[2].name = "left fasciculus cuneatus" # label_struc[2].filename = "PAM50_atlas_02.nii.gz" # Example 2: # label_struc[51].id = (1, 2, 3, ..., 29) # label_struc[51].name = "White Matter" # label_struc[51].filename = "" # no name because it is combined indiv_labels_ids, indiv_labels_names, indiv_labels_files, \ combined_labels_ids, combined_labels_names, combined_labels_id_groups, map_clusters \ = read_label_file(path_label, param_default.file_info_label) label_struc = {} # fill IDs for indiv labels for i_label in range(len(indiv_labels_ids)): label_struc[indiv_labels_ids[i_label]] = LabelStruc(id=indiv_labels_ids[i_label], name=indiv_labels_names[i_label], filename=indiv_labels_files[i_label], map_cluster=[indiv_labels_ids[i_label] in map_cluster for map_cluster in map_clusters].index(True)) # fill IDs for combined labels for i_label in range(len(combined_labels_ids)): label_struc[combined_labels_ids[i_label]] = LabelStruc(id=combined_labels_id_groups[i_label], name=combined_labels_names[i_label], map_cluster=[indiv_labels_ids[i_label] in map_cluster for map_cluster in map_clusters].index(True)) else: sct.printv('\nERROR: ' + path_label + ' does not exist.', 1, 'error') # check syntax of labels asked by user labels_id_user = check_labels(indiv_labels_ids + combined_labels_ids, parse_num_list(labels_user)) nb_labels = len(indiv_labels_files) # Load data and systematically reorient to RPI because we need the 3rd dimension to be z sct.printv('\nLoad metric image...', verbose) input_im = Image(fname_data).change_orientation("RPI") data = Metric(data=input_im.data, label='') # Load labels labels_tmp = np.empty([nb_labels], dtype=object) for i_label in range(nb_labels): im_label = Image(os.path.join(path_label, indiv_labels_files[i_label])).change_orientation("RPI") labels_tmp[i_label] = np.expand_dims(im_label.data, 3) # TODO: generalize to 2D input label labels = np.concatenate(labels_tmp[:], 3) # labels: (x,y,z,label) # Load vertebral levels if vertebral_levels: im_vertebral_labeling = Image(fname_vertebral_labeling).change_orientation("RPI") else: im_vertebral_labeling = None # Get dimensions of data and labels nx, ny, nz = data.data.shape nx_atlas, ny_atlas, nz_atlas, nt_atlas = labels.shape # Check dimensions consistency between atlas and data if (nx, ny, nz) != (nx_atlas, ny_atlas, nz_atlas): sct.printv('\nERROR: Metric data and labels DO NOT HAVE SAME DIMENSIONS.', 1, type='error') for id_label in labels_id_user: sct.printv('Estimation for label: '+label_struc[id_label].name, verbose) agg_metric = extract_metric(data, labels=labels, slices=slices, levels=levels, perslice=perslice, perlevel=perlevel, vert_level=im_vertebral_labeling, method=method, label_struc=label_struc, id_label=id_label, indiv_labels_ids=indiv_labels_ids) save_as_csv(agg_metric, fname_output, fname_in=fname_data, append=append) append = True # when looping across labels, need to append results in the same file sct.display_open(fname_output)
else: perlevel = 0 fname_normalizing_label = '' if '-norm-file' in arguments: fname_normalizing_label = arguments['-norm-file'] normalization_method = '' if '-norm-method' in arguments: normalization_method = arguments['-norm-method'] if '-fix-label' in arguments: label_to_fix = arguments['-fix-label'] else: label_to_fix = '' if '-output-map' in arguments: fname_output_metric_map = arguments['-output-map'] else: fname_output_metric_map = '' if '-mask-weighted' in arguments: fname_mask_weight = arguments['-mask-weighted'] else: fname_mask_weight = '' # if 'discard_negative_values' in arguments: discard_negative_values = int(arguments['-discard-neg-val']) verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level # call main function main(fname_data, path_label, method, parse_num_list(slices_of_interest), parse_num_list(vertebral_levels), fname_output, labels_user, append, fname_normalizing_label, normalization_method, label_to_fix, adv_param_user, fname_output_metric_map, fname_mask_weight, fname_vertebral_labeling=fname_vertebral_labeling, perslice=perslice, perlevel=perlevel, discard_negative_values=discard_negative_values, verbose=verbose)
def load(self, file, parent=None, verify=True): """ Load contents from file :param file: input filename or file-like object to load from """ # reset contents self.__init__() if isinstance(file, str): parent = os.path.dirname(file) file = io.open(file, "rb") section = "" for idx_line, line in enumerate(file): line = line.rstrip().decode("utf-8") if line == "": continue # update section index m_sec = re.match(r"^# Keyword=(?P<kw>IndivLabels|CombinedLabels|MAPLabels)\s*(?P<comment>.*)$", line) if m_sec is not None: section = m_sec.group("kw") continue m_comment = re.match(r"#.*", line) if m_comment is not None: continue if section == 'IndivLabels': m = re.match(r"^(?P<id>\d+), (?P<name>.*), (?P<filename>.*)$", line) if m is None: raise ValueError("Unexpected at line {}, in IndivLabels section: {}".format(idx_line+1, line)) _id = int(m.group("id")) _name = m.group("name") _filename = m.group("filename") if verify and parent is not None: if not os.path.exists(os.path.join(parent, _filename)): raise ValueError("Unexpected at line {}, specifying file {} which doesn't exist: {}".format(idx_line+1, _filename, line)) self._indiv_labels.append((_id, _name, _filename)) elif section == 'CombinedLabels': m = re.match(r"^(?P<id>\d+), (?P<name>.*), (?P<group>.*)$", line) if m is None: raise ValueError("Unexpected at line {}, in CombinedLabels section: {}".format(idx_line+1, line)) _id = int(m.group("id")) _name = m.group("name") try: _group = parse_num_list(m.group("group")) except ValueError as e: raise ValueError("Unexpected at line {}: {} in line: {}".format(idx_line+1, e, line)) self._combined_labels.append((_id, _name, _group)) elif section == 'MAPLabels': m = re.match(r"^(?P<name>.*), (?P<group>.*)$", line) if m is None: raise ValueError("Unexpected at line {}, in MAPLabels section: {}".format(idx_line+1, line)) _name = m.group("name") try: _group = parse_num_list(m.group("group")) except ValueError as e: raise ValueError("Unexpected at line {}: {} in line: {}".format(idx_line+1, e, line)) self._clusters_apriori.append((_name, _group)) else: raise ValueError("Unexpected at line {}, unparsed data: {}".format(idx_line+1, line))
def main(args): parser = get_parser() arguments = parser.parse(args) param = Param() # Initialization slices = param.slices group_funcs = (('MEAN', func_wa), ('STD', func_std) ) # functions to perform when aggregating metrics along S-I fname_segmentation = sct.get_absolute_path(arguments['-i']) fname_vert_levels = '' if '-o' in arguments: file_out = os.path.abspath(arguments['-o']) else: file_out = '' if '-append' in arguments: append = int(arguments['-append']) else: append = 0 if '-vert' in arguments: vert_levels = arguments['-vert'] else: vert_levels = '' if '-r' in arguments: remove_temp_files = arguments['-r'] if '-vertfile' in arguments: fname_vert_levels = arguments['-vertfile'] if '-perlevel' in arguments: perlevel = arguments['-perlevel'] else: perlevel = Param().perlevel if '-z' in arguments: slices = arguments['-z'] if '-perslice' in arguments: perslice = arguments['-perslice'] else: perslice = Param().perslice if '-angle-corr' in arguments: if arguments['-angle-corr'] == '1': angle_correction = True elif arguments['-angle-corr'] == '0': angle_correction = False verbose = int(arguments.get('-v')) sct.init_sct(log_level=verbose, update=True) # Update log level # update fields metrics_agg = {} if not file_out: file_out = 'csa.csv' metrics = process_seg.compute_shape(fname_segmentation, algo_fitting='bspline', angle_correction=angle_correction, verbose=verbose) for key in metrics: metrics_agg[key] = aggregate_per_slice_or_level( metrics[key], slices=parse_num_list(slices), levels=parse_num_list(vert_levels), perslice=perslice, perlevel=perlevel, vert_level=fname_vert_levels, group_funcs=group_funcs) metrics_agg_merged = _merge_dict(metrics_agg) save_as_csv(metrics_agg_merged, file_out, fname_in=fname_segmentation, append=append) sct.display_open(file_out)
def main(argv: Sequence[str]): """ Main function. When this script is run via CLI, the main function is called using main(sys.argv[1:]). :param argv: A list of unparsed arguments, which is passed to ArgumentParser.parse_args() """ for i, arg in enumerate(argv): if arg == '-create-seg' and len(argv) > i+1 and '-1,' in argv[i+1]: raise DeprecationWarning("The use of '-1' for '-create-seg' has been deprecated. Please use " "'-create-seg-mid' instead.") parser = get_parser() arguments = parser.parse_args(argv) verbose = arguments.v set_global_loglevel(verbose=verbose) input_filename = arguments.i output_fname = arguments.o img = Image(input_filename) dtype = None if arguments.add is not None: value = arguments.add out = sct_labels.add(img, value) elif arguments.create is not None: labels = arguments.create out = sct_labels.create_labels_empty(img, labels) elif arguments.create_add is not None: labels = arguments.create_add out = sct_labels.create_labels(img, labels) elif arguments.create_seg is not None: labels = arguments.create_seg out = sct_labels.create_labels_along_segmentation(img, labels) elif arguments.create_seg_mid is not None: labels = [(-1, arguments.create_seg_mid)] out = sct_labels.create_labels_along_segmentation(img, labels) elif arguments.cubic_to_point: out = sct_labels.cubic_to_point(img) elif arguments.display: display_voxel(img, verbose) return elif arguments.increment: out = sct_labels.increment_z_inverse(img) elif arguments.disc is not None: ref = Image(arguments.disc) out = sct_labels.labelize_from_discs(img, ref) elif arguments.vert_body is not None: levels = arguments.vert_body if len(levels) == 1 and levels[0] == 0: levels = None # all levels out = sct_labels.label_vertebrae(img, levels) elif arguments.vert_continuous: out = sct_labels.continuous_vertebral_levels(img) dtype = 'float32' elif arguments.MSE is not None: ref = Image(arguments.MSE) mse = sct_labels.compute_mean_squared_error(img, ref) printv(f"Computed MSE: {mse}") return elif arguments.remove_reference is not None: ref = Image(arguments.remove_reference) out = sct_labels.remove_missing_labels(img, ref) elif arguments.remove_sym is not None: # first pass use img as source ref = Image(arguments.remove_reference) out = sct_labels.remove_missing_labels(img, ref) # second pass use previous pass result as reference ref_out = sct_labels.remove_missing_labels(ref, out) ref_out.save(path=ref.absolutepath) elif arguments.remove is not None: labels = arguments.remove out = sct_labels.remove_labels_from_image(img, labels) elif arguments.keep is not None: labels = arguments.keep out = sct_labels.remove_other_labels_from_image(img, labels) elif arguments.create_viewer is not None: msg = "" if arguments.msg is None else f"{arguments.msg}\n" if arguments.ilabel is not None: input_labels_img = Image(arguments.ilabel) out = launch_manual_label_gui(img, input_labels_img, parse_num_list(arguments.create_viewer), msg) else: out = launch_sagittal_viewer(img, parse_num_list(arguments.create_viewer), msg) printv("Generating output files...") out.save(path=output_fname, dtype=dtype) display_viewer_syntax([input_filename, output_fname]) if arguments.qc is not None: generate_qc(fname_in1=input_filename, fname_seg=output_fname, args=argv, path_qc=os.path.abspath(arguments.qc), dataset=arguments.qc_dataset, subject=arguments.qc_subject, process='sct_label_utils')
def main(): # Default params param = Param() # Get parser info parser = get_parser() arguments = parser.parse(sys.argv[1:]) fname_data = arguments['-i'] if '-m' in arguments: fname_mask = arguments['-m'] else: fname_mask = '' method = arguments["-method"] if '-vol' in arguments: index_vol_user = arguments['-vol'] else: index_vol_user = '' verbose = int(arguments.get('-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')