Source code for CPAC.utils.create_fsl_flame_preset


# TODO: create a function that can help easily map raw pheno files that do not
# TODO: have the participant_session id that CPAC uses


[docs]def read_group_list_text_file(group_list_text_file): """Read in the group-level analysis participant-session list text file.""" with open(group_list_text_file, "r") as f: group_list = f.readlines() # each item here includes both participant and session, and this also will # become the main ID column in the written design matrix CSV group_list = [str(x).rstrip("\n") for x in group_list if x != ""] return group_list
[docs]def read_pheno_csv_into_df(pheno_csv, id_label=None): """Read the phenotypic file CSV or TSV into a Pandas DataFrame.""" import pandas as pd with open(pheno_csv, "r") as f: if id_label: if '.tsv' in pheno_csv or '.TSV' in pheno_csv: pheno_df = pd.read_table(f, dtype={id_label: object}) else: pheno_df = pd.read_csv(f, dtype={id_label: object}) else: if '.tsv' in pheno_csv or '.TSV' in pheno_csv: pheno_df = pd.read_table(f) else: pheno_df = pd.read_csv(f) return pheno_df
[docs]def write_group_list_text_file(group_list, out_file=None): """Write out the group-level analysis participant list as a text file.""" import os # prevent duplicates - depending on how the design matrix is set up, we # might have multiples of the sub_ses_ID's, like if we're doing repeated # measures with series/scans new_group_list = [] for sub_ses_id in group_list: if sub_ses_id not in new_group_list: new_group_list.append(sub_ses_id) if not out_file: out_file = os.path.join(os.getcwd(), "group_analysis_participant_" "list.txt") else: out_file = os.path.abspath(out_file) dir_path = out_file.split(os.path.basename(out_file))[0] if not os.path.exists(dir_path): os.makedirs(dir_path) with open(out_file, "wt") as f: for part_id in new_group_list: f.write("{0}\n".format(part_id)) if os.path.exists(out_file): print("Group-level analysis participant list written:" \ "\n{0}\n".format(out_file)) return out_file
[docs]def write_dataframe_to_csv(matrix_df, out_file=None): """Write out a matrix Pandas DataFrame into a CSV file.""" import os if not out_file: out_file = os.path.join(os.getcwd(), "matrix.csv") else: out_file = os.path.abspath(out_file) dir_path = out_file.split(os.path.basename(out_file))[0] if not os.path.exists(dir_path): os.makedirs(dir_path) try: matrix_df = matrix_df.drop(labels='participant_session_id', axis=1) except ValueError: pass except KeyError: pass matrix_df.to_csv(out_file, index=False) if os.path.exists(out_file): print("CSV file written:\n{0}\n".format(out_file))
[docs]def write_config_dct_to_yaml(config_dct, out_file=None): """Write out a configuration dictionary into a YAML file.""" import os import CPAC if not out_file: out_file = os.path.join(os.getcwd(), "group_config.yml") else: out_file = os.path.abspath(out_file) dir_path = out_file.split(os.path.basename(out_file))[0] if not os.path.exists(dir_path): os.makedirs(dir_path) if not out_file.endswith(".yml"): out_file = "{0}.yml".format(out_file) field_order = ['pipeline_dir', 'participant_list', 'output_dir', 'work_dir', 'log_dir', 'FSLDIR', 'run_fsl_feat', 'num_models_at_once', 'model_name', 'preset', 'pheno_file', 'ev_selections', 'participant_id_label', 'design_formula', 'mean_mask', 'custom_roi_mask', 'derivative_list', 'coding_scheme', 'group_sep', 'grouping_var', 'z_threshold', 'p_threshold', 'sessions_list', 'series_list', 'contrasts', 'f_tests', 'custom_contrasts', 'run_randomise', 'randomise_permutation', 'randomise_thresh', 'randomise_demean', 'randomise_tfce'] with open(out_file, "wt") as f: f.write("# CPAC Group-Level Analysis Configuration File\n" "# Version {0}\n".format(CPAC.__version__)) f.write("#\n# http://fcp-indi.github.io for more info.\n#\n" "# Tip: This file can be edited manually with " "a text editor for quick modifications.\n\n\n") f.write("# General Group-Level Analysis Settings\n" "##############################################################" "################\n\n") for key in field_order: val = config_dct[key] f.write("{0}: {1}\n\n".format(key, val)) if key == 'FSLDIR': f.write("\n# FSL-FEAT\n########################################" "######################################\n\n") if key == 'custom_contrasts': f.write("\n# FSL-Randomise\n###################################" "###########################################\n\n") if os.path.exists(out_file): print("Group-level analysis configuration YAML file written:\n" \ "{0}\n".format(out_file))
[docs]def create_design_matrix_df(group_list, pheno_df=None, ev_selections=None, pheno_sub_label=None, pheno_ses_label=None, pheno_site_label=None, ses_id=False): """Create the design matrix intended for group-level analysis via the FSL FLAME tool. This does NOT create the final .mat file that FSL FLAME takes in. This is an intermediary design matrix CSV meant for the user to review. If there is a phenotype CSV provided, this function will align the participant-session ID labels in the CPAC individual-level analysis output directory with the values listed in the phenotype file. """ import pandas as pd keep_cols = ['participant_id'] if ses_id: # if the group_list is participant_session_id instead of participant_id map_df = pd.DataFrame({'participant_session_id': group_list}) keep_cols += ['participant_session_id'] part_ids = [] sess_ids = [] for part_ses in group_list: part = part_ses.split('_')[0] sess = part_ses.split('_')[1] part_ids.append(part) sess_ids.append(sess) map_df['participant_id'] = part_ids map_df['session'] = sess_ids map_df = map_df.sort_values(by=['session', 'participant_id']) else: map_df = pd.DataFrame({'participant_id': group_list}) if pheno_df is None: # no phenotypic matrix provided; simpler design models design_df = map_df[keep_cols] else: # if a phenotype CSV file is provided with the data pheno_df = pheno_df.drop_duplicates() # replace spaces and dashes with underscores, to prevent confusion with # the Patsy design formula rename_pheno_cols = {} for col_name in pheno_df.columns: if ' ' in col_name or '-' in col_name: rename_pheno_cols.update({col_name: col_name.replace(' ', '_').replace('-', '_')}) pheno_df = pheno_df.rename(columns=rename_pheno_cols) # align the pheno's participant ID column with the group sublist text # file if not pheno_sub_label: # TODO: exception message raise Exception("there's a pheno file, but no pheno sub label") else: # rename the pheno sub label thingy pheno_df = pheno_df.rename( columns={pheno_sub_label: 'participant_id'}) if ev_selections: ev_selections.insert(0, 'participant_id') sort_by = ['participant_id'] if pheno_ses_label: # if sessions are important in the model, do this also pheno_df = pheno_df.rename( columns={pheno_ses_label: 'session_id'}) if ev_selections: ev_selections.append(1, 'session_id') # again, sort by session ID first in case of repeated # measures, where the sessions have to be all together first sort_by.insert(0, 'session_id') if pheno_site_label: # and if sites are important as well, same here pheno_df = pheno_df.rename( columns={pheno_site_label: 'site_id'}) if ev_selections: ev_selections.append(2, 'site_id') if ev_selections: # get specific covariates! pheno_df = pheno_df[ev_selections] # check for inconsistency with leading zeroes # (sometimes, the sub_ids from individual will be something like # '0002601' and the phenotype will have '2601') sublist_subs = map_df['participant_id'] pheno_subs = list(pheno_df['participant_id']) for index, row in pheno_df.iterrows(): pheno_sub_id = str(row['participant_id']) for sub_id in sublist_subs: if str(sub_id).lstrip('0') == pheno_sub_id: pheno_df.at[index, 'participant_id'] = sub_id for sub in sublist_subs: if sub in pheno_subs: # okay, there's at least one match break else: new_sublist_subs = [str(x).lstrip('0') for x in sublist_subs] for sub in new_sublist_subs: if sub in pheno_subs: # that's better map_df['participant_id'] = new_sublist_subs break else: raise Exception('the participant IDs in your group ' 'analysis participant list and the ' 'participant IDs in your phenotype file ' 'do not match') # merge if pheno_ses_label: design_df = pheno_df.merge(map_df, on=['participant_id']) else: design_df = pheno_df.merge(map_df[['participant_id']], on='participant_id') design_df = design_df.sort_values(sort_by) return design_df
[docs]def create_contrasts_template_df(design_df, contrasts_dct_list=None): """Create the template Pandas DataFrame for the contrasts matrix CSV. The headers in the contrasts matrix needs to match the headers of the design matrix.""" import pandas as pd contrast_cols = list(design_df.columns) contrast_cols.remove('participant_id') if contrasts_dct_list: # if we are initializing the contrasts matrix with pre-set contrast # vectors - just check for accuracy here for contrast_dct in contrasts_dct_list: # contrast_dct is a dictionary with each column name mapped to its # contrast vector value, like this: # {contrast: "Group Mean", "Group Mean": 1, "age": 0} if (len(contrast_dct) - 1) != len(contrast_cols): # it's -1 because of the "contrast" column in contrast_dct # TODO: message raise Exception("number of columns in the contrast vector " "does not match the number of covariate " "columns in the design matrix") else: # if default, start it up with a blank "template" contrast vector contrast_one = {"Contrasts": "contrast_1"} contrast_two = {"Contrasts": "contrast_2"} for col in contrast_cols: contrast_one.update({col: 0}) contrast_two.update({col: 0}) contrasts_dct_list = [contrast_one, contrast_two] contrast_cols.insert(0, "Contrasts") # now, make the actual dataframe contrasts_df = pd.DataFrame(contrasts_dct_list) # order the columns properly contrasts_df = contrasts_df[contrast_cols] return contrasts_df
[docs]def preset_single_group_avg(group_list, pheno_df=None, covariate=None, pheno_sub_label=None, output_dir=None, model_name="one_sample_T-test"): """Set up the design matrix CSV for running a single group average (one-sample T-test).""" import os if not output_dir: output_dir = os.getcwd() id_cols = ["participant_id", "session_id", "site_id"] # change spaces and dashes to underscores to prevent confusion with the # Patsy design formula if covariate: covariate = covariate.lstrip(' ').rstrip(' ') covariate = covariate.replace(' ', '_').replace('-', '_') ev_selections = None if pheno_df is not None: if covariate and pheno_sub_label: # if we're adding an additional covariate ev_selections = [covariate] design_df = create_design_matrix_df(group_list, pheno_df, ev_selections=ev_selections, pheno_sub_label=pheno_sub_label) design_df["Group_Mean"] = 1 group_mean_contrast = {"Contrasts": "Group Mean"} # make these loops in case we expand this to handle more than one # covariate past the Group Mean for col in design_df.columns: if col not in id_cols: if col == "Group_Mean": group_mean_contrast.update({col: 1}) else: group_mean_contrast.update({col: 0}) contrasts = [group_mean_contrast] if covariate: covariate_contrast = {"Contrasts": covariate} for col in design_df.columns: if col not in id_cols: if col == covariate: covariate_contrast.update({col: 1}) else: covariate_contrast.update({col: 0}) contrasts.append(covariate_contrast) contrasts_df = create_contrasts_template_df(design_df, contrasts) # create design and contrasts matrix file paths design_mat_path = os.path.join(output_dir, model_name, "design_matrix_{0}.csv".format(model_name)) contrasts_mat_path = os.path.join(output_dir, model_name, "contrasts_matrix_{0}.csv" "".format(model_name)) # start group config yaml dictionary design_formula = "Group_Mean" if covariate: design_formula = "{0} + {1}".format(design_formula, covariate) group_config = {"pheno_file": design_mat_path, "ev_selections": {"demean": [str(covariate)], "categorical": ["Group_Mean"]}, "design_formula": design_formula, "group_sep": "Off", "grouping_var": None, "sessions_list": [], "series_list": [], "custom_contrasts": contrasts_mat_path, "model_name": model_name, "output_dir": os.path.join(output_dir, model_name), "work_dir": os.path.join(output_dir, model_name), "log_dir": os.path.join(output_dir, model_name)} return design_df, contrasts_df, group_config
[docs]def preset_unpaired_two_group(group_list, pheno_df, groups, pheno_sub_label, output_dir=None, model_name="two_sample_unpaired_T-test"): """Set up the design matrix and contrasts matrix for running an unpaired two-group difference (two-sample unpaired T-test). group_list: a list of strings- sub_ses unique IDs pheno_df: a Pandas DataFrame object of the phenotypic file CSV/matrix groups: a list of either one or two strings- design matrix EV/covariate labels to take from the phenotype DF and include in the model pheno_sub_label: a string of the label name of the column in the phenotype file that holds the participant/session ID for each row output_dir: (optional) string of the output directory path model_name: (optional) name/label of the model to run Sets up the model described here: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FEAT/UserGuide #Unpaired_Two-Group_Difference_.28Two-Sample_Unpaired_T-Test.29 Only one "group" will be provided usually if the two groups in the phenotypic information you wish to compare are encoded in one covariate column, as categorical information. Thus, providing this one name will pull it from the phenotype file, and this function will break it out into two columns using dummy-coding. """ import os if not output_dir: output_dir = os.getcwd() id_cols = ["participant_id", "session_id", "site_id"] # change spaces and dashes to underscores to prevent confusion with the # Patsy design formula old_groups = groups groups = [] for group in old_groups: group = group.lstrip(' ').rstrip(' ') group = group.replace(' ', '_').replace('-', '_') groups.append(group) # if the two groups are encoded in one categorical EV/column, then we will # have to dummy code them out # if this is the case, then "groups" will be a list with only one # element in it- the one EV/column that is to be split up into two ev_selections = [] for group in groups: ev_selections.append(group) design_df = create_design_matrix_df(group_list, pheno_df, ev_selections=ev_selections, pheno_sub_label=pheno_sub_label) if len(groups) == 1: # we're going to split the one categorical EV into two new_groups = [] # get full range of values in one-column categorical EV group_set = list(set(design_df[groups[0]])) # run this again! # change spaces and dashes to underscores to prevent confusion with the # Patsy design formula new_group_set = [] for group in group_set: group = group.lstrip(' ').rstrip(' ') group = group.replace(' ', '_').replace('-', '_') new_group_set.append(group) # this preset is for an unpaired two-group difference- should only be # two groups encoded in this EV! if len(group_set) > 2: # TODO: message raise Exception("more than two groups provided, but this is a" "model for a two-group difference\n\ngroups " "found in column:\n{0}".format(str(group_set))) elif len(group_set) == 0: raise Exception("no groups were found - something went wrong " "with reading the phenotype information") elif len(group_set) == 1: raise Exception("only one group found in the column provided, " "but this is a model for a two-group difference" "\n\ngroups found in column:\n" "{0}".format(str(group_set))) # create the two new dummy-coded columns # column 1 # new column name new_name = "{0}_{1}".format(groups[0], new_group_set[0]) # create new column encoded in 0's design_df[new_name] = 0 # map the relevant values into 1's design_df[new_name] = design_df[groups[0]].map({group_set[0]: 1, group_set[1]: 0}) # update groups list new_groups.append(new_name) # column 2 # new column name new_name = "{0}_{1}".format(groups[0], new_group_set[1]) # create new column encoded in 0's design_df[new_name] = 0 # map the relevant values into 1's design_df[new_name] = design_df[groups[0]].map({group_set[1]: 1, group_set[0]: 0}) # update groups list new_groups.append(new_name) # drop original EV/column del design_df[groups[0]] # update groups list groups = new_groups # start the contrasts contrast_one = {"Contrasts": "{0} - {1}".format(groups[0], groups[1])} contrast_two = {"Contrasts": "{0} - {1}".format(groups[1], groups[0])} # make these loops in case we expand this to handle additional covariates # past the "prescribed" ones in the model/preset for col in design_df.columns: if col not in id_cols: if col == groups[0]: contrast_one.update({col: 1}) contrast_two.update({col: -1}) elif col == groups[1]: contrast_one.update({col: -1}) contrast_two.update({col: 1}) else: contrast_one.update({col: 0}) contrast_two.update({col: 0}) contrasts = [contrast_one, contrast_two] contrasts_df = create_contrasts_template_df(design_df, contrasts) # create design and contrasts matrix file paths design_mat_path = os.path.join(output_dir, model_name, "design_matrix_{0}.csv".format(model_name)) contrasts_mat_path = os.path.join(output_dir, model_name, "contrasts_matrix_{0}.csv" "".format(model_name)) # start group config yaml dictionary design_formula = "{0} + {1}".format(groups[0], groups[1]) group_config = {"pheno_file": design_mat_path, "ev_selections": {"demean": [], "categorical": str(groups)}, "design_formula": design_formula, "group_sep": "On", "grouping_var": str(groups), "sessions_list": [], "series_list": [], "custom_contrasts": contrasts_mat_path, "model_name": model_name, "output_dir": os.path.join(output_dir, model_name), "work_dir": os.path.join(output_dir, model_name), "log_dir": os.path.join(output_dir, model_name)} return design_df, contrasts_df, group_config
[docs]def preset_paired_two_group(group_list, conditions, condition_type="session", output_dir=None, model_name="two_sample_unpaired_T-test"): """Set up the design matrix and contrasts matrix for running an paired two-group difference (two-sample paired T-test). group_list: a list of strings- sub_ses unique IDs conditions: a two-item list of strings- session or series/scan names of the two sessions or two scans (per participant) you wish to compare condition_type: a string, either "session" or "scan", depending on what is in "conditions" output_dir: (optional) string of the output directory path model_name: (optional) name/label of the model to run Sets up the model described here: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FEAT/UserGuide #Paired_Two-Group_Difference_.28Two-Sample_Paired_T-Test.29 """ import os if not output_dir: output_dir = os.getcwd() if len(conditions) != 2: # TODO: msg raise Exception sess_conditions = ["session", "Session", "sessions", "Sessions"] scan_conditions = ["scan", "scans", "series", "Series/Scans", "Series"] sesflag = False if condition_type in sess_conditions: sesflag = True design_df = create_design_matrix_df(group_list, ses_id=sesflag) # make the "condition" EV (the 1's and -1's delineating the two # conditions, with the "conditions" being the two sessions or two scans) condition_ev = [] if condition_type in sess_conditions: # note: the participant_id column in design_df should be in order, so # the condition_ev should come out in order: # 1,1,1,1,2,2,2,2 (if the sessions are 1 and 2) # later on, this has to be converted to 1,1,1,1,-1,-1,-1,-1 ! for sub_ses_id in design_df["participant_session_id"]: if sub_ses_id.split("_")[-1] == conditions[0]: condition_ev.append(conditions[0]) elif sub_ses_id.split("_")[-1] == conditions[1]: condition_ev.append(conditions[1]) group_config = {"sessions_list": conditions, "series_list": []} elif condition_type in scan_conditions: # TODO: re-visit later, when session/scan difference in how to run # TODO: group-level analysis repeated measures is streamlined and # TODO: simplified # the information needed in this part is not encoded in the group # sublist! user inputs the two scan names, and we have a list of # sub_ses (which needs to be doubled), with each scan paired to each # half of this list (will need to ensure these scans exist for each # selected derivative in the output directory later on) # the condition_ev should come out in order: # scan-1,scan-1,scan-1,scan-2,scan-2,scan-2 # (if the scans are scan-1 and scan-2, etc.) # later on, this has to be converted to 1,1,1,-1,-1,-1 ! for sub_ses_id in design_df["participant_id"]: condition_ev.append(conditions[0]) for sub_ses_id in design_df["participant_id"]: condition_ev.append(conditions[1]) # NOTE: there is only one iteration of the sub_ses list in # design_df["participant_id"] at this point! so use append to # double that column: design_df = design_df.append(design_df) group_config = {"sessions_list": [], "series_list": conditions} else: # TODO: msg raise Exception # let's check to make sure it came out right # first half past_val = None for val in condition_ev[0:(len(condition_ev) / 2) - 1]: if past_val: if val != past_val: raise Exception('Non-equal amount of participants for each ' '{0}.\n'.format(condition_type)) past_val = val # second half past_val = None for val in condition_ev[(len(condition_ev) / 2):]: if past_val: if val != past_val: raise Exception('Non-equal amount of participants for each ' '{0}.\n'.format(condition_type)) past_val = val design_df[condition_type] = condition_ev # initalize the contrast dct's contrast_one = {} contrast_two = {} design_formula = "{0}".format(condition_type) # create the participant identity columns for sub_ses_id in design_df["participant_id"]: new_part_col = [] sub_id = sub_ses_id.split("_")[0] new_part_label = "participant_{0}".format(sub_id) for moving_sub_ses_id in design_df["participant_id"]: moving_sub_id = moving_sub_ses_id.split("_")[0] if moving_sub_id == sub_id: new_part_col.append(1) else: new_part_col.append(0) design_df[new_part_label] = new_part_col contrast_one.update({new_part_label: 0}) contrast_two.update({new_part_label: 0}) if new_part_label not in design_formula: design_formula = "{0} + {1}".format(design_formula, new_part_label) # finish the contrasts # should be something like # ses,sub,sub,sub, etc. # ses-1 - ses-2: 1, 0, 0, 0, 0... # ses-2 - ses-1: -1, 0, 0, 0, etc. contrast_one.update({ "Contrasts": "{0}_{1} - {2}_{3}".format(condition_type, conditions[0], condition_type, conditions[1])}) contrast_two.update({ "Contrasts": "{0}_{1} - {2}_{3}".format(condition_type, conditions[1], condition_type, conditions[0])}) contrast_one.update({condition_type: 1}) contrast_two.update({condition_type: -1}) try: design_df = design_df.drop(labels=['participant_session_id'], axis='columns') except KeyError: pass contrasts = [contrast_one, contrast_two] contrasts_df = create_contrasts_template_df(design_df, contrasts) # create design and contrasts matrix file paths design_mat_path = os.path.join(output_dir, model_name, "design_matrix_{0}.csv".format(model_name)) contrasts_mat_path = os.path.join(output_dir, model_name, "contrasts_matrix_{0}.csv" "".format(model_name)) # start group config yaml dictionary group_config.update({"pheno_file": design_mat_path, "ev_selections": {"demean": [], "categorical": []}, "design_formula": design_formula, "group_sep": "Off", "grouping_var": None, "custom_contrasts": contrasts_mat_path, "model_name": model_name, "output_dir": os.path.join(output_dir, model_name), "work_dir": os.path.join(output_dir, model_name), "log_dir": os.path.join(output_dir, model_name)}) return design_df, contrasts_df, group_config
[docs]def preset_tripled_two_group(group_list, conditions, condition_type="Sessions", output_dir=None, model_name="tripled_T-test"): """Set up the design matrix and contrasts matrix for running a tripled two-group difference ('tripled' T-test). group_list: a list of strings- sub_ses unique IDs conditions: a three-item list of strings- session or series/scan names of the three sessions or three scans (per participant) you wish to compare condition_type: a string, either "session" or "scan", depending on what is in "conditions" output_dir: (optional) string of the output directory path model_name: (optional) name/label of the model to run Sets up the model described here: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FEAT/UserGuide #Tripled_Two-Group_Difference_.28.22Tripled.22_T-Test.29 """ import os if not output_dir: output_dir = os.getcwd() if len(conditions) != 3: # TODO: msg raise Exception('Three conditions are required for the tripled ' 't-test.\n') sess_conditions = ["session", "Session", "sessions", "Sessions"] scan_conditions = ["scan", "scans", "series", "Series/Scans", "Series"] sesflag = False if condition_type in sess_conditions: sesflag = True design_df = create_design_matrix_df(group_list, ses_id=sesflag) # make the "condition" EVs (the 1's, -1's, and 0's delineating the three # conditions, with the "conditions" being the three sessions or three # scans) condition_ev_one = [] condition_ev_two = [] if condition_type in sess_conditions: # note: the participant_id column in design_df should be in order, so # the condition_ev's should come out in order: # 1,1,1,-1,-1,-1, 0, 0, 0 (this is checked further down) # 1,1,1, 0, 0, 0,-1,-1,-1 for sub_ses_id in design_df["participant_session_id"]: if sub_ses_id.split("_")[-1] == conditions[0]: condition_ev_one.append(1) condition_ev_two.append(1) elif sub_ses_id.split("_")[-1] == conditions[1]: condition_ev_one.append(-1) condition_ev_two.append(0) elif sub_ses_id.split("_")[-1] == conditions[2]: condition_ev_one.append(0) condition_ev_two.append(-1) group_config = {"sessions_list": conditions, "series_list": []} elif condition_type in scan_conditions: # TODO: re-visit later, when session/scan difference in how to run # TODO: group-level analysis repeated measures is streamlined and # TODO: simplified # the information needed in this part is not encoded in the group # sublist! user inputs the two scan names, and we have a list of # sub_ses (which needs to be doubled), with each scan paired to each # half of this list (will need to ensure these scans exist for each # selected derivative in the output directory later on) for sub_ses_id in design_df["participant_id"]: condition_ev_one.append(1) condition_ev_two.append(1) for sub_ses_id in design_df["participant_id"]: condition_ev_one.append(-1) condition_ev_two.append(0) for sub_ses_id in design_df["participant_id"]: condition_ev_one.append(0) condition_ev_two.append(-1) # NOTE: there is only one iteration of the sub_ses list in # design_df["participant_id"] at this point! so use append # (twice) triple that column: design_df_double = design_df.append(design_df) design_df = design_df_double.append(design_df) group_config = {"sessions_list": [], "series_list": conditions} else: # TODO: msg raise Exception # let's check to make sure it came out right # first third for val in condition_ev_one[0:(len(condition_ev_one) / 3) - 1]: if val != 1: # TODO: msg raise Exception # second third for val in condition_ev_one[(len(condition_ev_one) / 3):(len(condition_ev_one)/3)*2]: if val != -1: # TODO: msg raise Exception # third... third for val in condition_ev_one[((len(condition_ev_one)/3)*2 + 1):]: if val != 0: # TODO: msg raise Exception # first third for val in condition_ev_two[0:(len(condition_ev_two) / 3) - 1]: if val != 1: # TODO: msg raise Exception # second third for val in condition_ev_two[(len(condition_ev_two) / 3):(len(condition_ev_two)/3)*2]: if val != 0: # TODO: msg raise Exception # third... third for val in condition_ev_two[((len(condition_ev_two)/3)*2 + 1):]: if val != -1: # TODO: msg raise Exception # label the two covariate columns which encode the three conditions column_one = "{0}_column_one".format(condition_type) column_two = "{0}_column_two".format(condition_type) design_df[column_one] = condition_ev_one design_df[column_two] = condition_ev_two # initalize the contrast dct's contrast_one = {} contrast_two = {} contrast_three = {} design_formula = "{0} + {1}".format(column_one, column_two) # create the participant identity columns for sub_id in design_df["participant_id"]: new_part_col = [] new_part_label = "participant_{0}".format(sub_id) for moving_sub_ses_id in design_df["participant_id"]: moving_sub_id = moving_sub_ses_id.split("_")[0] if moving_sub_id == sub_id: new_part_col.append(1) else: new_part_col.append(0) design_df[new_part_label] = new_part_col contrast_one.update({new_part_label: 0}) contrast_two.update({new_part_label: 0}) contrast_three.update({new_part_label: 0}) if new_part_label not in design_formula: design_formula = "{0} + {1}".format(design_formula, new_part_label) # finish the contrasts # should be something like # ses,ses,sub,sub,sub, etc. # ses-1 - ses-2: 2, 1, 0, 0, 0... # ses-1 - ses-3: 1, 2, 0, 0, 0... # ses-2 - ses-3: -1, 1, 0, 0, 0, etc. contrast_one.update({ "Contrasts": "{0}_{1} - {2}_{3}".format(condition_type, conditions[0], condition_type, conditions[1])}) contrast_two.update({ "Contrasts": "{0}_{1} - {2}_{3}".format(condition_type, conditions[0], condition_type, conditions[2])}) contrast_three.update({ "Contrasts": "{0}_{1} - {2}_{3}".format(condition_type, conditions[1], condition_type, conditions[2])}) contrast_one.update({column_one: 2, column_two: 1}) contrast_two.update({column_one: 1, column_two: 2}) contrast_three.update({column_one: -1, column_two: 1}) try: design_df = design_df.drop(labels=['participant_session_id'], axis='columns') except KeyError: pass contrasts = [contrast_one, contrast_two, contrast_three] contrasts_df = create_contrasts_template_df(design_df, contrasts) # create design and contrasts matrix file paths design_mat_path = os.path.join(output_dir, model_name, "design_matrix_{0}.csv".format(model_name)) contrasts_mat_path = os.path.join(output_dir, model_name, "contrasts_matrix_{0}.csv" "".format(model_name)) # start group config yaml dictionary group_config.update({"pheno_file": design_mat_path, "ev_selections": {"demean": [], "categorical": []}, "design_formula": design_formula, "group_sep": "Off", "grouping_var": None, "custom_contrasts": contrasts_mat_path, "model_name": model_name, "output_dir": os.path.join(output_dir, model_name), "work_dir": os.path.join(output_dir, model_name), "log_dir": os.path.join(output_dir, model_name)}) return design_df, contrasts_df, group_config
[docs]def run(pipeline_dir, derivative_list, z_thresh, p_thresh, preset=None, group_list_text_file=None, pheno_file=None, pheno_sub_label=None, output_dir=None, model_name=None, covariate=None, condition_type=None, run=False): # FSL FEAT presets: run regular group analysis with no changes to its # original flow- use the generated pheno as the pheno, use the # contrasts DF as a custom contrasts matrix, and auto-generate the # group analysis config YAML as well # NOTE: the input parameters above may come in as a dictionary instead # or something import os import pandas as pd import pkg_resources as p # make life easy keys_csv = p.resource_filename('CPAC', 'resources/cpac_outputs.csv') try: keys = pd.read_csv(keys_csv) except Exception as e: err = "\n[!] Could not access or read the cpac_outputs.csv " \ "resource file:\n{0}\n\nError details {1}\n".format(keys_csv, e) raise Exception(err) if derivative_list == 'all': derivative_list = ['alff', 'falff', 'reho', 'sca_roi', 'sca_tempreg', 'vmhc', 'centrality', 'dr_tempreg'] if pheno_file and not pheno_sub_label: # TODO: message raise Exception("pheno file provided, but no pheno sub label") if pheno_sub_label and not pheno_file: # TODO: message raise Exception("pheno sub label provided, but no pheno file") try: if "None" in group_list_text_file or "none" in group_list_text_file: group_list_text_file = None except TypeError: pass if not group_list_text_file: from CPAC.pipeline.cpac_group_runner import grab_pipeline_dir_subs if (preset == "paired_two" or preset == "tripled_two") and "Sessions" in condition_type: group_list = grab_pipeline_dir_subs(pipeline_dir, True) else: group_list = grab_pipeline_dir_subs(pipeline_dir) group_list_text_file = os.path.join(output_dir, model_name, "group_participant_list_" "{0}.txt".format(model_name)) elif isinstance(group_list_text_file, list): group_list = group_list_text_file # write out a group analysis sublist text file so that it can be # linked in the group analysis config yaml group_list_text_file = os.path.join(output_dir, model_name, "group_participant_list_" "{0}.txt".format(model_name)) elif os.path.isfile(group_list_text_file): group_list = read_group_list_text_file(group_list_text_file) # write out a group analysis sublist text file so that it can be # linked in the group analysis config yaml group_list_text_file = os.path.join(output_dir, model_name, "group_participant_list_" "{0}.txt".format(model_name)) if len(group_list) == 0: msg = "\n\n[!] C-PAC says: No participants found in the pipeline " \ "directory you provided. Make sure the directory is the " \ "individual-level pipeline directory that contains the sub-" \ "directories labeled with the participant_session IDs.\n\n" \ "Pipeline directory provided: {0}\n\n".format(pipeline_dir) raise Exception(msg) if not preset: # TODO: this pass group_config = {"pipeline_dir": pipeline_dir, "FSLDIR": "FSLDIR", "run_fsl_feat": [1], "num_models_at_once": 1, "preset": preset, "participant_list": group_list_text_file, "participant_id_label": "participant_id", "mean_mask": ["Group Mask"], "custom_roi_mask": None, "derivative_list": derivative_list, "coding_scheme": ["Treatment"], "z_threshold": [float(z_thresh)], "p_threshold": [float(p_thresh)], "contrasts": [], "f_tests": [], "run_randomise": [0], "randomise_permutation": 500, "randomise_thresh": 5, "randomise_demean": True, "randomise_tfce": True} if preset == "single_grp": design_df, contrasts_df, group_config_update = \ preset_single_group_avg(group_list, pheno_df=None, covariate=None, pheno_sub_label=None, output_dir=output_dir, model_name=model_name) group_config.update(group_config_update) elif preset == "single_grp_cov": if not pheno_file: # TODO: message raise Exception("pheno file not provided") if not covariate: # TODO: message raise Exception("covariate not provided") pheno_df = read_pheno_csv_into_df(pheno_file, pheno_sub_label) design_df, contrasts_df, group_config_update = \ preset_single_group_avg(group_list, pheno_df, covariate=covariate, pheno_sub_label=pheno_sub_label, output_dir=output_dir, model_name=model_name) group_config.update(group_config_update) elif preset == "unpaired_two": if not pheno_file: # TODO: message raise Exception("pheno file not provided") if not covariate: # TODO: message raise Exception("the two groups were not provided") # we're assuming covariate will be coming in as a string of either one # covariate name, or a string with two covariates separated by a comma # either way, it needs to be in list form in this case, not string covariate = covariate.split(",") pheno_df = read_pheno_csv_into_df(pheno_file, pheno_sub_label) # in this case, "covariate" gets sent in as a list of two covariates design_df, contrasts_df, group_config_update = \ preset_unpaired_two_group(group_list, pheno_df, groups=covariate, pheno_sub_label=pheno_sub_label, output_dir=output_dir, model_name=model_name) group_config.update(group_config_update) elif preset == "paired_two": # run a two-sample paired T-test # we need it as repeated measures- either session or scan # and the list of subs # also: the two session or scan names (in a list together), and # whether they are sessions or scans if not covariate: # TODO: message raise Exception("the two conditions were not provided") if not condition_type: # TODO: message raise Exception("you didn't specify whether the two groups are " "sessions or series/scans") # we're assuming covariate (which in this case, is the two sessions, # or two scans) will be coming in as a string of either one covariate # name, or a string with two covariates separated by a comma # either way, it needs to be in list form in this case, not string try: covariate = covariate.split(",") except AttributeError: # it's already a list- keep it that way pass design_df, contrasts_df, group_config_update = \ preset_paired_two_group(group_list, conditions=covariate, condition_type=condition_type, output_dir=output_dir, model_name=model_name) group_config.update(group_config_update) elif preset == "tripled_two": # run a "tripled" T-test # we need it as repeated measures- either session or scan # and the list of subs # also: the two session or scan names (in a list together), and # whether they are sessions or scans if not covariate: # TODO: message raise Exception("the three conditions were not provided") if not condition_type: # TODO: message raise Exception("you didn't specify whether the three groups are " "sessions or series/scans") # we're assuming covariate (which in this case, is the three sessions, # or three scans) will be coming in as a string of either one # covariate name, or a string with three covariates separated by a # comma # either way, it needs to be in list form in this case, not string try: covariate = covariate.split(",") except AttributeError: # it's already a list- keep it that way pass design_df, contrasts_df, group_config_update = \ preset_tripled_two_group(group_list, conditions=covariate, condition_type=condition_type, output_dir=output_dir, model_name=model_name) group_config.update(group_config_update) else: # TODO: not a real preset! raise Exception("not one of the valid presets") # write participant list text file write_group_list_text_file(design_df["participant_id"], group_list_text_file) # write design matrix CSV write_dataframe_to_csv(design_df, group_config["pheno_file"]) # write custom contrasts matrix CSV write_dataframe_to_csv(contrasts_df, group_config["custom_contrasts"]) # write group-level analysis config YAML out_config = os.path.join(output_dir, model_name, "group_config_{0}.yml".format(model_name)) write_config_dct_to_yaml(group_config, out_config) if run: # TODO: we need to separate the individual-level pipeline config from # TODO: the group-level one, it's too restrictive pass