示例#1
0
plt.ylabel('Mean Intensitiy', fontsize=18)
plt.xlabel('Time (TRs)', fontsize=18)

Okay, now let's build our regression design matrix to perform the whole-brain functional connectivity analysis.

The goal is to find which regions in the brain have a similar time course to the vmPFC, controlling for all of our covariates (i.e., nuisance regressors).

Functional connectivity analyses are particularly sensitive to artifacts that might induce a temporal relationship, particularly head motion (See this [article](https://www.sciencedirect.com/science/article/pii/S1053811911011815) by Jonathan Power for more details). This means that we will need to use slightly different steps to preprocess data for this type of analyis then a typical event related mass univariate analysis. 

We are going to remove the mean from our vmPFC signal. We are also going to include the average activity in CSF as an additional nuisance regressor to remove physiological artifacts. Finally, we will be including our 24 motion covariates as well as linear and quadratic trends. We need to be a little careful about filtering as the normal high pass filter for an event related design might be too short and will remove potential signals of interest.

Resting state researchers also often remove the global signal, which can reduce physiological and motion related artifacts and also increase the likelihood of observing negative relationships with your seed regressor (i.e., anticorrelated). This procedure has remained quite controversial in practice (see [here](https://www.physiology.org/doi/full/10.1152/jn.90777.2008) [here](https://www.sciencedirect.com/science/article/pii/S1053811908010264), [here](https://www.pnas.org/content/107/22/10238.short), and [here](https://www.sciencedirect.com/science/article/pii/S1053811916306711) for a more in depth discussion). We think that in general including covariates like CSF should be sufficient. It is also common to additionally include covariates from white matter masks, and also multiple principal components of this signal rather than just the mean (see more details about [compcorr](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2214855/).

Overall, this code should seem very familiar as it is pretty much the same procedure we used in the single subject GLM tutorial. However, instead of modeling the task design, we are interested in calculating the functional connectivity with the vmPFC.

tr = layout.get_tr()
fwhm = 6
n_tr = len(data)

def make_motion_covariates(mc, tr):
    z_mc = zscore(mc)
    all_mc = pd.concat([z_mc, z_mc**2, z_mc.diff(), z_mc.diff()**2], axis=1)
    all_mc.fillna(value=0, inplace=True)
    return Design_Matrix(all_mc, sampling_freq=1/tr)


vmpfc = zscore(pd.DataFrame(vmpfc, columns=['vmpfc']))

csf_mask = Brain_Data(os.path.join(base_dir, 'masks', 'csf.nii.gz'))
csf = zscore(pd.DataFrame(smoothed.extract_roi(mask=csf_mask).T, columns=['csf']))
示例#2
0
def main(subject, sourcedata, derivatives, smoothed, n_jobs=5):

    os.environ['SUBJECTS_DIR'] = op.join(derivatives, 'freesurfer')

    source_layout = BIDSLayout(sourcedata, validate=False, derivatives=False)

    fmriprep_layout = BIDSLayout(op.join(derivatives, 'fmriprep'),
                                 validate=False)

    if smoothed:
        bold_layout = BIDSLayout(op.join(derivatives, 'smoothed'),
                                 validate=False)
        bold = bold_layout.get(subject=subject, extension='func.gii')
    else:
        bold = fmriprep_layout.get(subject=subject, extension='func.gii')

        bold = sorted([e for e in bold if 'fsaverage6' in e.filename],
                      key=lambda x: x.run)

    fmriprep_layout_df = fmriprep_layout.to_df()
    fmriprep_layout_df = fmriprep_layout_df[~fmriprep_layout_df.subject.isnull(
    )]
    fmriprep_layout_df['subject'] = fmriprep_layout_df.subject.astype(int)
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(fmriprep_layout_df.suffix,
                                                    ['regressors'])]
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.extension, ['tsv'])]
    fmriprep_layout_df = fmriprep_layout_df.set_index(['subject', 'run'])

    events_df = source_layout.to_df()
    events_df = events_df[events_df.suffix == 'events']
    events_df['subject'] = events_df['subject'].astype(int)
    events_df = events_df.set_index(['subject', 'run'])

    tr = source_layout.get_tr(bold[0].path)

    if smoothed:
        base_dir = op.join(derivatives, 'glm_stim1_surf_smoothed',
                           f'sub-{subject}', 'func')
    else:
        base_dir = op.join(derivatives, 'glm_stim1_surf', f'sub-{subject}',
                           'func')

    if not op.exists(base_dir):
        os.makedirs(base_dir)

    for b in bold:
        run = b.entities['run']
        hemi = b.entities['suffix']
        #     print(run)

        confounds_ = fmriprep_layout_df.loc[(subject, run), 'path'].iloc[0]
        confounds_ = pd.read_csv(confounds_, sep='\t')
        confounds_ = confounds_[to_include].fillna(method='bfill')

        pca = PCA(n_components=7)
        confounds_ -= confounds_.mean(0)
        confounds_ /= confounds_.std(0)
        confounds_pca = pca.fit_transform(confounds_[to_include])

        events_ = events_df.loc[(subject, run), 'path']
        events_ = pd.read_csv(events_, sep='\t')
        events_['trial_type'] = events_['trial_type'].apply(
            lambda x: 'stim2' if x.startswith('stim2') else x)

        frametimes = np.arange(0, tr * len(confounds_), tr)

        X = make_first_level_design_matrix(
            frametimes,
            events_,
            add_regs=confounds_pca,
            add_reg_names=[f'confound_pca.{i}' for i in range(1, 8)])

        Y = surface.load_surf_data(b.path).T
        Y = (Y / Y.mean(0) * 100)
        Y -= Y.mean(0)

        fit = run_glm(Y, X, noise_model='ols', n_jobs=n_jobs)
        r = fit[1][0.0]
        betas = pd.DataFrame(r.theta, index=X.columns)

        stim1 = []

        for stim in 5, 7, 10, 14, 20, 28:
            stim1.append(betas.loc[f'stim1-{stim}'])

        result = pd.concat(stim1, 1).T
        print(result.shape)

        pes = nb.gifti.GiftiImage(header=nb.load(b.path).header,
                                  darrays=[
                                      nb.gifti.GiftiDataArray(row)
                                      for ix, row in result.iterrows()
                                  ])

        fn_template = op.join(
            base_dir,
            'sub-{subject}_run-{run}_space-{space}_desc-stims1_hemi-{hemi}.pe.gii'
        )
        space = 'fsaverage6'

        pes.to_filename(fn_template.format(**locals()))

        transformer = SurfaceTransform(source_subject='fsaverage6',
                                       target_subject='fsaverage',
                                       hemi={
                                           'L': 'lh',
                                           'R': 'rh'
                                       }[hemi])

        transformer.inputs.source_file = pes.get_filename()
        space = 'fsaverage'
        transformer.inputs.out_file = fn_template.format(**locals())
        # Disable on MAC OS X (SIP problem)
        transformer.run()
示例#3
0
def main(subject, sourcedata, derivatives):
    source_layout = BIDSLayout(sourcedata, validate=False, derivatives=False)
    fmriprep_layout = BIDSLayout(op.join(derivatives, 'fmriprep'),
                                 validate=False)

    bold = fmriprep_layout.get(
        subject=subject,
        suffix='bold',
        description='preproc',
        extension='nii.gz',
    )
    bold = sorted([e for e in bold if 'MNI' in e.filename],
                  key=lambda x: x.run)

    reg = re.compile('.*_space-(?P<space>.+)_desc.*')

    fmriprep_layout_df = fmriprep_layout.to_df()
    fmriprep_layout_df = fmriprep_layout_df[~fmriprep_layout_df.subject.isnull(
    )]
    fmriprep_layout_df['subject'] = fmriprep_layout_df.subject.astype(int)
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.suffix, ['bold', 'regressors', 'mask'])]
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.extension, ['nii.gz', 'tsv'])]
    fmriprep_layout_df['space'] = fmriprep_layout_df.path.apply(
        lambda path: reg.match(path).group(1) if reg.match(path) else None)
    fmriprep_layout_df = fmriprep_layout_df.set_index(
        ['subject', 'run', 'suffix', 'space'])

    events_df = source_layout.to_df()
    events_df = events_df[events_df.suffix == 'events']
    events_df['subject'] = events_df['subject'].astype(int)
    events_df = events_df.set_index(['subject', 'run'])

    tr = source_layout.get_tr(bold[0].path)

    for b in bold:
        run = b.entities['run']
        print(run)

        confounds_ = fmriprep_layout_df.loc[(subject, run, 'regressors'),
                                            'path'].iloc[0]
        confounds_ = pd.read_csv(confounds_, sep='\t')
        confounds_ = confounds_[to_include].fillna(method='bfill')

        events_ = events_df.loc[(subject, run), 'path']
        events_ = pd.read_csv(events_, sep='\t')
        events_['trial_type'] = events_['trial_type'].apply(
            lambda x: 'stim2' if x.startswith('stim2') else x)

        model = FirstLevelModel(tr,
                                drift_model=None,
                                n_jobs=5,
                                smoothing_fwhm=4.0)
        pca = PCA(n_components=7)

        confounds_ -= confounds_.mean(0)
        confounds_ /= confounds_.std(0)
        confounds_pca = pca.fit_transform(confounds_[to_include])

        events_['onset'] += tr

        model.fit(b.path, events_, confounds_pca)

        base_dir = op.join(derivatives, 'glm_stim1', f'sub-{subject}', 'func')

        if not op.exists(base_dir):
            os.makedirs(base_dir)

        # PE
        ims = []
        for stim in 5, 7, 10, 14, 20, 28:
            im = model.compute_contrast(f'stim1-{stim}',
                                        output_type='effect_size')
            ims.append(im)
        ims = image.concat_imgs(ims)
        ims.to_filename(
            op.join(base_dir,
                    f'sub-{subject}_run-{run}_desc-stims1_pe.nii.gz'))

        # zmap
        ims = []
        for stim in 5, 7, 10, 14, 20, 28:
            im = model.compute_contrast(f'stim1-{stim}', output_type='z_score')
            ims.append(im)
        ims = image.concat_imgs(ims)
        ims.to_filename(
            op.join(base_dir,
                    f'sub-{subject}_run-{run}_desc-stims1_zmap.nii.gz'))
示例#4
0
def main(subject,
         sourcedata,
         derivatives):
    source_layout = BIDSLayout(sourcedata, validate=False, derivatives=False)
    fmriprep_layout = BIDSLayout(
        op.join(derivatives, 'fmriprep'), validate=False)

    bold = fmriprep_layout.get(subject=subject,
                               suffix='bold',
                               description='preproc',
                               extension='nii.gz', )
    bold = sorted([e for e in bold if 'MNI' in e.filename],
                  key=lambda x: x.run)

    reg = re.compile('.*_space-(?P<space>.+)_desc.*')

    fmriprep_layout_df = fmriprep_layout.to_df()
    fmriprep_layout_df = fmriprep_layout_df[~fmriprep_layout_df.subject.isnull(
    )]
    fmriprep_layout_df['subject'] = fmriprep_layout_df.subject.astype(int)
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.suffix, ['bold', 'regressors', 'mask'])]
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.extension, ['nii.gz', 'tsv'])]
    fmriprep_layout_df['space'] = fmriprep_layout_df.path.apply(
        lambda path: reg.match(path).group(1) if reg.match(path) else None)
    fmriprep_layout_df = fmriprep_layout_df.set_index(
        ['subject', 'run', 'suffix', 'space'])

    events_df = source_layout.to_df()
    events_df = events_df[events_df.suffix == 'events']
    events_df['subject'] = events_df['subject'].astype(int)
    events_df = events_df.set_index(['subject', 'run'])

    tr = source_layout.get_tr(bold[0].path)

    imgs = []
    confounds = []
    events = []

    for b in bold:
        run = b.entities['run']

        confounds_ = fmriprep_layout_df.loc[(
            subject, run, 'regressors'), 'path'].iloc[0]
        confounds_ = pd.read_csv(confounds_, sep='\t')

        events_ = events_df.loc[(subject, run), 'path']
        events_ = pd.read_csv(events_, sep='\t')
        events_['trial_type'] = events_['trial_type'].apply(
            lambda x: 'stim2' if x.startswith('stim2') else x)

        imgs.append(b.path)
        confounds.append(confounds_[to_include].fillna(method='bfill'))
        events.append(events_)

    mask = fmriprep_layout.get(subject=subject,
                               run=1,
                               suffix='mask',
                               extension='nii.gz',
                               description='brain')
    mask = [e for e in mask if 'MNI' in e.filename]
    assert(len(mask) == 1)
    mask = mask[0].path

    model = FirstLevelModel(tr, drift_model=None, n_jobs=5, smoothing_fwhm=6.0)
    model.fit(imgs, events, confounds, )

    base_dir = op.join(derivatives, 'glm', f'sub-{subject}', 'func')

    if not op.exists(base_dir):
        os.makedirs(base_dir)

    contrasts = [('left-right', 'leftright'), ('error', 'error'),
            ('stim1-10 + stim1-14 + stim1-20 + stim1-28 + stim1-5 + stim1-7', 'stim1'),
            ('stim2', 'stim2')]

    for contrast, label in contrasts:
        con = model.compute_contrast(contrast, output_type='z_score')
        pe = model.compute_contrast(contrast, output_type='effect_size')

        con.to_filename(op.join(base_dir, f'sub-{subject}_contrast-{label}_zmap.nii.gz'))
        pe.to_filename(op.join(base_dir, f'sub-{subject}_contrast-{label}_pe.nii.gz'))
示例#5
0
class Pipeline():

    def __init__(self, name, root):

        self.name = name
        self.root = root
        self.reload()

    @staticmethod
    def original_path(layout, sub):
        return layout._get_unique(subject=sub, scope="fMRIPrep", suffix='bold', extension='nii.gz', return_type='file')

    def reload(self):
        self.layout = BIDSLayout(self.root, derivatives=True)
        self.tr = self.layout.get_tr()
        with open(self.layout._get_unique(scope=self.name, suffix="pipeline").path) as file:
            pipeline = json.load(file)
        sys.path.append(os.path.dirname(self.layout._get_unique(
            scope=self.name, suffix="pipeline").path))
        self.masks = dict()
        for mask, mask_path in pipeline["Masks"].items():
            if not os.path.isabs(mask_path):
                mask_path = join(self.root, "derivatives",
                                 self.name, mask_path)
            self.masks[mask] = Brain_Data(mask_path)

        # Set up the process dictionary

        self.processes = dict()
        for process in pipeline["Processes"]:
            if not os.path.isabs(process["Source"]):
                process["Source"] = join(self.root, "derivatives",
                                         self.name, process["Source"])
            head, tail = os.path.split(os.path.abspath(process["Source"]))
            if tail.endswith(".py"):
                tail = tail[:-3]
            else:
                raise TypeError(f"{tail} is not a Python script.")
            sys.path.append(head)
            self.processes[process["Name"]] = Process(
                key=process["Readable"], process=getattr(__import__(tail), process["Name"]))
            sys.path.remove(head)

    @staticmethod
    def make_motion_covariates(mc, tr):
        z_mc = zscore(mc)
        all_mc = pd.concat(
            [z_mc, z_mc**2, z_mc.diff(), z_mc.diff()**2], axis=1)
        all_mc.fillna(value=0, inplace=True)
        return Design_Matrix(all_mc, sampling_freq=1/tr)

    def load_data(self, sub, return_type="Brain_Data", write="all", force="none", verbose=True, reload=True, **processes) -> Brain_Data:
        """Load data from pipeline.root/derivatives/pipeline.name and/or applies processes from
        pipeline.processes to it.
        By default, first checks wether the processes have been applied and saved before and 
        then loads them. By default, saves all the intermediate steps

        Parameters
        ----------
        sub : str
            Name of the subject to load the process from.
        return_type : str, optional
            Type the return value. Must be one of "path", "Brain_Data". If "path" and write="none" and file does not exist,
            throws an Error, as path does not exist. By default "Brain_Data"
        write : str, optional
            Wether to save the intermediate and the last step when applying processes. Must be one of "none" (no step is saved),
            "main" (only endresult is saved) or "all" (all intermediate steps are saved). By default "all"
        force : str, optional
            Wether to apply processes even though a file of this already exists. Must be one of "none", "main", "all" (see above).
            By default "none"
        verbose : bool, optional
            Wether to be verbose, by default True
        reload : bool, optional
            Wether to reload the pipeline.layout after writing a file. Only recommended if computing multiple independend processes.
            Then, afterwards, should be reloaded by hand (call `pipeline.layout = BIDSLayout(pipeline.root)`
            , by default True

        Returns
        -------
        Brain_Data, str
            (Un)Processed data or path to where the data is stored

        Raises
        ------
        TypeError
            If wrong return_type is supplied
        FileNotFoundError
            If subject is not found
        KeyError
            If an unknown process is supplied
        """
        #  We'll use this function to print only when being verbose
        def v_print(*args, **kwargs):
            if verbose:
                print(*args, **kwargs)

        return_types = ["Brain_Data", "path"]

        if return_type not in return_types:
            raise TypeError(
                f"Returntype {return_type} not recognised. Must be in {return_types}.")

        path = join(self.root, "derivatives", self.name, f"sub-{sub}")

        if not isdir(path):
            raise FileNotFoundError(
                f"Did not find subject {sub} in directory {dir}.")

        for key in processes.keys():
            if key not in self.processes.keys():
                raise KeyError(
                    f"{key} is not known process. Known processes are {self.processes.keys()}")

        if len(processes) == 0:
            v_print(f"...loading the unprocessed file of subject {sub}")
            path = self.original_path(layout=self.layout, sub=sub)
            if return_type == "path":
                return path
            if return_type == "Brain_Data":
                return Brain_Data(path)

        # This is the most important part:
        name = "_".join([f"sub-{sub}", "_".join(
            [f"{self.processes[key].readable(args)}" for key, args in processes.items()]), "bold.nii.gz"])

        if isfile(join(path, name)):
            v_print(f"...found {name}")
            if return_type == "path":
                return join(path, name)
            data = Brain_Data(join(path, name))
        else:
            last_process, last_key = processes.popitem()
            v_print(f"...{name} does not exist yet")
            yet_to_process = self.load_data(
                sub=sub,
                return_type="Brain_Data",
                write=("all" if write == "all" else "none"),
                **processes
            )
            v_print(f"Applying process {last_process}")
            data = self.processes[last_process].process(
                self,
                sub,
                yet_to_process,
                **last_key if isinstance(last_key, dict) else last_key
            )
            if write in ["all", "main"]:
                v_print(f"...writing {name}")
                data.write(join(path, name))
                if reload:
                    self.layout = BIDSLayout(self.root, derivatives=True)

        if return_type == "Brain_Data":
            return data
        if return_type == "path":
            return join(dir, f"sub-{sub}", name)

    def create_subject_folders(self, subs, format="sub-{sub}"):
        for sub in subs:
            os.mkdir(join(self.root, "derivatives",
                          self.name, format.format(sub=sub)))

    @staticmethod
    def create(name, root, create_subs=False):

        pipeline_root = join(root, "derivatives", name)

        # create folder
        os.makedirs(pipeline_root, exist_ok=True)

        # create pipeline.json
        with open(join(pipeline_root, "pipeline.json"), "x") as file:
            json.dump({"Name": name, "Processes": [],
                       "Masks": dict()}, file, indent=4)

        # create dataset_description.json
        with open(join(pipeline_root, "dataset_description.json"), "x") as file:
            json.dump(
                {
                    "Name": name,
                    "BIDSVersion": bids.__version__,
                    "PipelineDescription": {
                        "Name": name,
                        "Version": "",
                        "CodeURL": ""
                    },
                    "CodeURL": "",
                    "HowToAcknowledge": ""
                },
                file,
                indent=4
            )

        pipeline = Pipeline(name, root)

        if create_subs:
            pipeline.create_subject_folders(pipeline.layout.get_subjects())

        return pipeline
示例#6
0

# Now, let's build the 24 covariates related to head motion. We include the 6 realignment parameters that have been standardized. In addition, we add their quadratic, their derivative, and the square of their derivative.
# 
# We can create a quick visualization to see what the overall pattern is across the different regressors.

# In[35]:


def make_motion_covariates(mc, tr):
    z_mc = zscore(mc)
    all_mc = pd.concat([z_mc, z_mc**2, z_mc.diff(), z_mc.diff()**2], axis=1)
    all_mc.fillna(value=0, inplace=True)
    return Design_Matrix(all_mc, sampling_freq=1/tr)

tr = layout.get_tr()
mc_cov = make_motion_covariates(mc, tr)

sns.heatmap(mc_cov)


# Now let's try to find some spikes in the data. This is performed by finding TRs that exceed a global mean threshold and also that exceed an overall average intensity change by a threshold.  We are using an arbitrary cutoff of 3 standard deviations as a threshold.
# 
# First, let's plot the average signal intensity across all voxels over time.

# In[36]:


plt.figure(figsize=(15,3))
plt.plot(np.mean(data.data, axis=1), linewidth=3)
plt.xlabel('Time', fontsize=18)
示例#7
0
def main(subject,
         sourcedata,
         derivatives):
    source_layout = BIDSLayout(sourcedata, validate=False, derivatives=False)
    fmriprep_layout = BIDSLayout(
        op.join(derivatives, 'fmriprep'), validate=False)

    bold = fmriprep_layout.get(subject=subject,
                               extension='func.gii')
    bold = sorted([e for e in bold if 'fsaverage6' in e.filename],
                  key=lambda x: x.run)

    reg = re.compile('.*_space-(?P<space>.+)_desc.*')

    fmriprep_layout_df = fmriprep_layout.to_df()
    fmriprep_layout_df = fmriprep_layout_df[~fmriprep_layout_df.subject.isnull(
    )]
    fmriprep_layout_df['subject'] = fmriprep_layout_df.subject.astype(int)
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.suffix, ['regressors'])]
    fmriprep_layout_df = fmriprep_layout_df[np.in1d(
        fmriprep_layout_df.extension, ['tsv'])]
    fmriprep_layout_df = fmriprep_layout_df.set_index(
        ['subject', 'run'])

    events_df = source_layout.to_df()
    events_df = events_df[events_df.suffix == 'events']
    events_df['subject'] = events_df['subject'].astype(int)
    events_df = events_df.set_index(['subject', 'run'])

    tr = source_layout.get_tr(bold[0].path)

    base_dir = op.join(derivatives, 'glm_stim1_trialwise_surf', f'sub-{subject}', 'func')

    if not op.exists(base_dir):
        os.makedirs(base_dir)

    for b in bold:
        run = b.entities['run']
        hemi = b.entities['suffix']
    #     print(run)

        confounds_ = fmriprep_layout_df.loc[(
            subject, run), 'path'].iloc[0]
        confounds_ = pd.read_csv(confounds_, sep='\t')
        confounds_ = confounds_[to_include].fillna(method='bfill')

        events_ = events_df.loc[(subject, run), 'path']
        events_ = pd.read_csv(events_, sep='\t')
        events_['trial_type'] = events_['trial_type'].apply(
            lambda x: 'stim2' if x.startswith('stim2') else x)

        events_['onset'] += tr

        # Split up over trials
        stim1_events = events_[events_.trial_type.apply(lambda x: x.startswith('stim1'))]
        def number_trials(d):
            return pd.Series(['{}.{}'.format(e, i+1) for i, e in enumerate(d)],
                             index=d.index)
            
        stim1_events['trial_type'] = stim1_events.groupby('trial_type').trial_type.apply(number_trials)
        events_.loc[stim1_events.index, 'trial_type'] = stim1_events['trial_type']

        frametimes = np.arange(0, tr*len(confounds_), tr)

        pca = PCA(n_components=7)
        confounds_ -= confounds_.mean(0)
        confounds_ /= confounds_.std(0)
        confounds_pca = pca.fit_transform(confounds_[to_include])

        X = make_first_level_design_matrix(frametimes,
                                           events_,
                                           add_regs=confounds_pca.values)

        Y = surface.load_surf_data(b.path).T
        Y = (Y / Y.mean(0) * 100)
        Y -= Y.mean(0)

        fit = run_glm(Y, X, noise_model='ols')
        r = fit[1][0.0]
        betas = pd.DataFrame(r.theta, index=X.columns)

        stim1 = []

        for stim in 5, 7, 10, 14, 20, 28:
            for trial in range(1, 7):
                stim1.append(betas.loc[f'stim1-{stim}.{trial}'])

        result = pd.concat(stim1, 1).T

        pes = nb.gifti.GiftiImage(header=nb.load(b.path).header,
                                  darrays=[nb.gifti.GiftiDataArray(result)])

        pes.to_filename(
            op.join(base_dir,
                f'sub-{subject}_run-{run}_desc-stims1_hemi-{hemi}.pe.gii'))