An fMRI examination of the role of the Locus Coeruleus in state regulation in ADHD.
The 2 matches
- [1] § Method › MRI data acquisition and analysis ↔ Analysis_jupyter_notebook.ipynb, lines 641–690 · score 0.78 · framewise displacement, standardized DVARS, fMRIPrep, confound, desc, motion
- [2] § Method › Participants and prescreening ↔ Analysis_jupyter_notebook.ipynb, lines 27–155 · score 0.75 · substance abuse, scale score, anxiety, depression, SRS, DSM
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 691 lines · 31 KB · CC-BY-4.0 · 2 matches
- # %% [markdown]
- # # Analysis pipeline for questionnaire, behavioral and LC data of ADHD experiment with fMRI
- #
- # Leonhard H. Drescher, Ghent University, 2022-2025
- #
- # #### Short description of the experiment:
- # Participants: Adults with ADHD (n = 27) and adults without any psychiatric diagnosis (n = 28).
- #
- # Questionnaire scales: ADHD Self-Report Scale with 18 questions, screener consists of first 6 questions, (ASRS, Kessler et al. 2005), State Regulation Deficit Questionnaire (SRDQ), Social Responsiveness Scale (SRS, Constantino et al., 2003), Adult Self-Report (Achenbach et al., 2003), Edinburgh Handedness Inventory, WASI-II FSIQ-2 two-subtests IQ estimate (Vocabulary and Matrix Reasoning), DIVA-5 diagnostic semi-structured interview (Kooij et al., 2007).
- #
- # Task: Target detection/oddball task with 30% target rate. Three conditions: slow event rate (ER) (8 s average), moderate ER (4 s), fast ER (2 s). Target stimuli were the letter Q, standard trials were the letter O. Trials appeared for 300 ms on screen and were then replaced by a fixation cross. In the beginning and middle of each ER condition there was a 20 s baseline interval with only the fixation cross on screen. Task based on Metin et al. (2015).
- #
- # License: CC-BY
- # %%
- import pandas as pd
- import numpy as np
- import os, sys
- import matplotlib
- from matplotlib import pyplot as plt
- import seaborn as sns
- import glob
- import re
- from plotnine import *
- import scipy
- # %% [markdown]
- # ## Questionnaire data
- # Note that scoring was implemented during data collection, so sum and scale scores are already available in the raw data.
- # %%
- #Load main questionnaire data
- quest_df = pd.DataFrame(pd.read_csv('/quest_df.csv')) #set path
- #Load asrs-srd data and merge into questionnaire dataframe
- asrs_df = pd.DataFrame(pd.read_excel('/asrs-srd.xlsx')) #set path
- quest_df = pd.merge(quest_df, asrs_df, on='id', validate="one_to_one")
- #Make the ASR t-scores (normed) numeric only, as the values contain a clinical label
- quest_df['DSMDepr_t_num'] = quest_df['DSMDepr_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- quest_df['DSMAnx_t_num'] = quest_df['DSMAnx_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- quest_df['DSMADHDtot_t_num'] = quest_df['DSMADHDtot_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- quest_df['T_subst_t_num'] = quest_df['T_subst_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- quest_df['T_subst_a_num'] = quest_df['T_subst_a'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- quest_df['T_subst_d_num'] = quest_df['T_subst_d'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- quest_df['Mean_subst_num'] = quest_df['Mean_subst'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
- #Get M(SD) or median (min-max) per group and questionnaire scale + respective group difference statistics test
- print('Questionnaire descriptives per group: \n')
- print('Age M:')
- print(str(quest_df.groupby(['Group'])['age'].mean()),'\n')
- print('Age SD:')
- print(str(quest_df.groupby(['Group'])['age'].std()),'\n')
- print('Age t-test:')
- print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['age'],
- quest_df[quest_df['Group'] == 'CTRL']['age']), '\n')
- print('IQ M:')
- print(str(quest_df.groupby(['Group'])['WASI-Tot'].mean()),'\n')
- print('IQ SD:')
- print(str(quest_df.groupby(['Group'])['WASI-Tot'].std()),'\n')
- print('IQ t-test:')
- print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['WASI-Tot'],
- quest_df[quest_df['Group'] == 'CTRL']['WASI-Tot']), '\n')
- print('SRS M:')
- print(str(quest_df.groupby(['Group'])['SRS_total'].mean()),'\n')
- print('SRS SD:')
- print(str(quest_df.groupby(['Group'])['SRS_total'].std()),'\n')
- print('SRS t-test:')
- print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['SRS_total'],
- quest_df[quest_df['Group'] == 'CTRL']['SRS_total']), '\n')
- print('State regulation (SRD) M:')
- print(str(quest_df.groupby(['Group'])['sum_SRD'].mean()),'\n')
- print('State regulation (SRD) SD:')
- print(str(quest_df.groupby(['Group'])['sum_SRD'].std()),'\n')
- print('State regulation (SRD) t-test:')
- print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['sum_SRD'],
- quest_df[quest_df['Group'] == 'CTRL']['sum_SRD']), '\n')
- print('short-ASRS M:')
- print(str(quest_df.groupby(['Group'])['short_ASRS'].mean()),'\n')
- print('short-ASRS SD:')
- print(str(quest_df.groupby(['Group'])['short_ASRS'].std()),'\n')
- print('short-ASRS t-test:')
- print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['short_ASRS'],
- quest_df[quest_df['Group'] == 'CTRL']['short_ASRS']), '\n')
- print('full-ASRS M:')
- print(str(quest_df.groupby(['Group'])['full_ASRS'].mean()),'\n')
- print('full-ASRS SD:')
- print(str(quest_df.groupby(['Group'])['full_ASRS'].std()),'\n')
- print('full-ASRS t-test:')
- print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['full_ASRS'],
- quest_df[quest_df['Group'] == 'CTRL']['full_ASRS']), '\n')
- print('ASR ADHD Median:')
- print(str(quest_df.groupby(['Group'])['DSMADHDtot_t_num'].median()),'\n')
- print('ASR ADHD min-max: \n ADHD:')
- print(quest_df[quest_df['Group'] == 'ADHD']['DSMADHDtot_t_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['DSMADHDtot_t_num'].max())
- print('CTRL:')
- print(quest_df[quest_df['Group'] == 'CTRL']['DSMADHDtot_t_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['DSMADHDtot_t_num'].max(),'\n')
- print('ASR ADHD Mann Whitney U test:')
- print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['DSMADHDtot_t_num'],
- quest_df[quest_df['Group'] == 'CTRL']['DSMADHDtot_t_num']), '\n')
- print('ASR Depression Median:')
- print(str(quest_df.groupby(['Group'])['DSMDepr_t_num'].median()),'\n')
- print('ASR Depression min-max: \n ADHD:')
- print(quest_df[quest_df['Group'] == 'ADHD']['DSMDepr_t_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['DSMDepr_t_num'].max())
- print('CTRL:')
- print(quest_df[quest_df['Group'] == 'CTRL']['DSMDepr_t_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['DSMDepr_t_num'].max(),'\n')
- print('ASR Depression Mann Whitney U test:')
- print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['DSMDepr_t_num'],
- quest_df[quest_df['Group'] == 'CTRL']['DSMDepr_t_num']), '\n')
- print('ASR Anxiety Median:')
- print(str(quest_df.groupby(['Group'])['DSMAnx_t_num'].median()),'\n')
- print('ASR Anxiety min-max: \n ADHD:')
- print(quest_df[quest_df['Group'] == 'ADHD']['DSMAnx_t_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['DSMAnx_t_num'].max())
- print('CTRL:')
- print(quest_df[quest_df['Group'] == 'CTRL']['DSMAnx_t_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['DSMAnx_t_num'].max(),'\n')
- print('ASR Anxiety Mann Whitney U test:')
- print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['DSMAnx_t_num'],
- quest_df[quest_df['Group'] == 'CTRL']['DSMAnx_t_num']), '\n')
- print('ASR Substance Abuse Median:')
- print(str(quest_df.groupby(['Group'])['Mean_subst_num'].median()),'\n')
- print('ASR Substance Abuse min-max: \n ADHD:')
- print(quest_df[quest_df['Group'] == 'ADHD']['Mean_subst_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['Mean_subst_num'].max())
- print('CTRL:')
- print(quest_df[quest_df['Group'] == 'CTRL']['Mean_subst_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['Mean_subst_num'].max(),'\n')
- print('ASR Substance Abuse Mann Whitney U test:')
- print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['Mean_subst_num'],
- quest_df[quest_df['Group'] == 'CTRL']['Mean_subst_num']), '\n')
- #Export the spreadsheet
- #quest_df.to_csv('/quest_df.csv') #set path
- # %%
- #Cronbach's alpha calculations on some questionnaires
- #Create filters for both questionnaires
- filter_ASRS = [col for col in quest_df if col.startswith('ASRS[')]
- filter_SRD = [col for col in quest_df if col.startswith('SRD[')]
- #Remove A's from values
- quest_df = quest_df.replace({'A0':1, 'A1':2, 'A2':3, 'A3':4, 'A4':5})
- # Transform the df into a correlation matrix
- def cronbach_alpha(df):
- df_corr = df.corr()
- # Calculate N (number of questions)
- N = df.shape[1]
- # Calculate R
- # Loop through the columns and append every relevant correlation to an array called "rs".
- rs = np.array([])
- for i, col in enumerate(df_corr.columns):
- sum_ = df_corr[col][i+1:].values
- rs = np.append(sum_, rs)
- mean_r = np.mean(rs) # Calculate the mean of rs
- # Cronbach's Alpha formula
- cronbach_alpha = (N * mean_r) / (1 + (N - 1) * mean_r)
- return cronbach_alpha
- print("Cronbach's alpha for the full ASRS: ", np.round(cronbach_alpha(quest_df[filter_ASRS]),4))
- print("Cronbach's alpha for the short ASRS: ", np.round(cronbach_alpha(quest_df[filter_ASRS[:6]]),4))
- print("Cronbach's alpha for the SRD: ", np.round(cronbach_alpha(quest_df[filter_SRD]),4))
- # %% [markdown]
- # ## Behavioral data
- # %%
- path = "/Behavioral_rawdata" #insert path to folder containing behavioral raw data files (Psychopy output)
- os.chdir(path)
- csv_files = glob.glob(path + "/*.csv")
- valid_files = []
- #remove files with less than 100 MB as they don't contain valid data
- for f in csv_files:
- if os.stat(f).st_size > 100000:
- valid_files.append(f)
- #One exception when last condition was restarted (t2): append as well!
- #Code not included since it contains sensitive references
- #In another subject, correct missing '-' in default ppt number structure (sub-xxxx)!
- #Create preliminary data frame
- bdf = []
- for filename in valid_files:
- df = pd.read_csv(filename, index_col=None, header=0)
- bdf.append(df)
- behavioral_df = pd.concat(bdf, axis=0, ignore_index=True)
- #Also correct wrongly inserted string from one participant nr!
- #remove all rows that are not trials
- behavioral_df = behavioral_df[~behavioral_df['cond'].isnull()]
- # Create group variable ['Group']
- # %%
- #Extract and check RT data
- #Extract rt_df with only correct trials
- # Create functions for percentiles
- def q25(x):
- return x.quantile(0.25)
- def q75(x):
- return x.quantile(0.75)
- rt_df = behavioral_df[(behavioral_df['trialtype']==2) & (behavioral_df['buttonBox_3.corr']==1)].groupby(
- ['participant', 'Group', 'cond'], as_index=False).agg({'buttonBox_3.rt': [q25, np.median, q75, np.mean, np.std]})
- #Make the column names nice again
- rt_df.columns = rt_df.columns.to_flat_index()
- rt_df.columns = ['subject', 'Group', 'Condition', 'rt_q25', 'rt_median', 'rt_q75', 'rt_mean', 'rt_std']
- # Calculate quartile-based coefficient of variability (IQR / median)
- rt_df['qcv'] = (rt_df['rt_q75'] - rt_df['rt_q25']) / rt_df['rt_median']
- rt_df['cv'] = (rt_df['rt_std'] / rt_df['rt_mean'])
- #Save rt_df for statistical analysis (long format)
- # rt_df.to_csv('/rt_df.csv') #set path
- #Save rt_df for statistical analysis (wide format)
- # rt_df1 = rt_df.pivot_table(index=['subject', 'Group'],
- # columns='condition',
- # values=['rt_q25', 'rt_median','rt_q75','rt_mean','rt_std','qcv','cv'])
- # rt_df1.columns = rt_df1.columns.to_series().str.join('_')
- # rt_df1.reset_index()
- # rt_df1.to_csv('/rt_df_wide.csv') #set path
- #Change condition values so they show up nicely
- rt_df["Condition"] = rt_df["Condition"].replace({
- 'fast':'Fast',
- 'mod':'Moderate',
- 'slow':'Slow'
- })
- # Interaction plots RT and RTV. Error bars are +- 1 standard error around mean.
- fig1 = plt.subplots(1, 2, figsize=(8,4))
- #sns.set_style("whitegrid")
- plt.subplot(121)
- sns.pointplot('Condition', 'rt_median', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=rt_df, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68)
- plt.ylabel("Reaction time (median)")
- plt.subplot(122)
- sns.pointplot('Condition', 'qcv', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=rt_df, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68)
- plt.ylabel("Reaction time variability (IQR/median)")
- plt.tight_layout()
- plt.show()
- # Print number of observations and means
- rt_nobs = rt_df.groupby(['Group', 'Condition'])['rt_median'].count()
- rt_md_means = rt_df.groupby(['Group', 'Condition'])['rt_median'].mean()
- qcv_means = rt_df.groupby(['Group', 'Condition'])['qcv'].mean()
- print(f"number of observations:\n{rt_nobs} \nrt_md means: \n{rt_md_means} \nqcv_means: {qcv_means}")
- # %%
- #Normality testing for each of the participants' RT data per condition
- from scipy.stats import shapiro
- rt_distr_df1 = behavioral_df[(behavioral_df['trialtype']==2) & (behavioral_df['buttonBox_3.corr']==1)]
- rt_distr_df = rt_distr_df1[['cond', 'buttonBox_3.rt', 'participant']]
- rt_distr_df
- # Initialize counter for non-normal distributions
- non_normal_count = 0
- # Iterate over each participant and each condition
- for participant in rt_distr_df['participant'].unique():
- participant_data = rt_distr_df[rt_distr_df['participant'] == participant]
- for condition in participant_data['cond'].unique():
- condition_data = participant_data[participant_data['cond'] == condition]['buttonBox_3.rt']
- # Perform Shapiro-Wilk test
- stat, p_value = shapiro(condition_data)
- # Output the results
- print(f"Shapiro-Wilk test for participant {participant} in condition {condition}:")
- print(f"Statistic: {stat}, p-value: {p_value}")
- # Check for non-normal distribution
- if p_value < 0.05:
- non_normal_count += 1
- print("\n")
- # Output summary
- print(f"Number of tests with non-normal distribution: {non_normal_count} out of 165")
- # %%
- #Extract and check commission error data
- #Unstacking and restacking prevents conditions with zero errors from being ignored
- com_error_df = behavioral_df[(behavioral_df['trialtype']==1) & (behavioral_df['buttonBox_3.corr']==0)].groupby(
- ['participant', 'Group', 'cond']).agg({'buttonBox_3.corr':'count'}).unstack(fill_value=0).stack().reset_index()
- com_error_df.rename(columns={'buttonBox_3.corr':'com_error_count'}, inplace=True)
- # Correct for number of trials by inserting them into the dataframe
- trial_count_df = behavioral_df.groupby(['participant', 'cond']).agg(
- {'buttonBox_3.corr':'count'}).unstack(fill_value=0).stack().reset_index()
- trial_count_df.rename(columns={'buttonBox_3.corr':'total'}, inplace=True)
- com_error_df = pd.merge(com_error_df, trial_count_df, on=['participant', 'cond'], validate="one_to_one")
- com_error_df['com_err_percent'] = (com_error_df['com_error_count'] / com_error_df['total'])*100
- #Save com_error_df for statistical analysis
- # com_error_df.to_csv('/com_error_df.csv') #set path
- #Save com_error_df for statistical analysis (wide format)
- # com_error_df1 = com_error_df.pivot_table(index=['participant', 'Group'],
- # columns='cond',
- # values=['com_error_count', 'total','com_err_percent'])
- # com_error_df1.columns = com_error_df1.columns.to_series().str.join('_')
- # com_error_df1.reset_index()
- # com_error_df1.to_csv('/com_error_df_wide.csv') #set path
- # Plot the data. Error bars are +- 1 standard error around mean.
- fig2 = plt.subplots(1, 1, figsize=(7,7))
- sns.pointplot('cond', 'com_err_percent', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=com_error_df, dodge=True, order=["fast", "mod", "slow"], ci=68)
- plt.title('Commission errors')
- plt.show()
- # Print number of observations and means.
- com_nobs = com_error_df.groupby(['Group', 'cond'])['com_err_percent'].count()
- com_means = com_error_df.groupby(['Group', 'cond'])['com_err_percent'].mean()
- print(f"number of observations: \n{com_nobs} \nmeans: \n{com_means}")
- # %%
- #SOLELY AS A CHECK OF TASK ENGAGEMENT (omission errors; very low error numbers expected)
- om_error_df = behavioral_df[(behavioral_df['trialtype']==2) & (behavioral_df['buttonBox_3.corr']==0)].groupby(
- ['participant', 'Group', 'cond']).agg({'buttonBox_3.corr':'count'}).unstack(fill_value=0).stack().reset_index()
- om_error_df.rename(columns={'buttonBox_3.corr':'om_error_count'}, inplace=True)
- om_error_df = pd.merge(om_error_df, trial_count_df, on=['participant', 'cond'], validate="one_to_one")
- om_error_df['om_err_percent'] = (om_error_df['om_error_count'] / om_error_df['total'])*100
- # Plot the data. Error bars are +- 1 standard error around mean.
- fig3 = plt.subplots(1, 1, figsize=(7,7))
- sns.pointplot('cond', 'om_err_percent', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=om_error_df, dodge=True, order=["fast", "mod", "slow"], ci=68)
- plt.title('Omission errors')
- plt.show()
- # Print number of observations and means.
- om_nobs = om_error_df.groupby(['Group', 'cond'])['om_err_percent'].count()
- om_means = om_error_df.groupby(['Group', 'cond'])['om_err_percent'].mean()
- print(f"number of observations: \n{om_nobs} \nmeans: \n{om_means}")
- # %% [markdown]
- # ## Create onset (struct) files for SPM12
- # -The code below creates onset files from Psychopy behavioral data, to be used in Matlab/SPM12 for FMRI analysis (struct format). <br>
- # -Subsequent SPM12 processing steps were done manually or with simple code loops (not included). MarsBaR exctraction done with code from the manual.
- # %%
- # Step 1: Convert all behavioral datasets to _events.csv datasets, which is simply a list of onset times
- path = "/Behavioral_rawdata/" #set path
- os.chdir(path)
- csv_files = glob.glob(path + "/*.csv")
- valid_files = []
- #remove files with less than 100 MB
- for f in csv_files:
- if os.stat(f).st_size > 100000:
- valid_files.append(f)
- #Append also the restarted last condition of the task of one subject!
- #loop over all subjects and trials
- for filename in valid_files:
- with open(filename, 'r') as f:
- print(f'converting {filename}')
- origfile = pd.read_csv(filename, index_col=None, header=0)
- for i in range(len(origfile)):
- # create participant variable
- subject_nr = origfile['participant'][1]
- #Correct missing dash in one subject name!
- #Continue operation
- if not np.isnan(origfile['fc1.started'][i]): #baseline_interval nr 1 (before each condition)
- newrow0 = pd.DataFrame({"stimulus.started": origfile['fc1.started'][i],
- "participant": origfile['participant'][i],
- "cond":origfile['cond'][i],
- "trialtype": 31},
- index=[i-0.25]) #trick to insert row without messing with original index
- origfile = pd.concat([origfile, newrow0])
- if np.isnan(origfile['stimulus.stopped'][i-1]): #first onset of experiment (scanner trigger)
- newrow1 = pd.DataFrame({"stimulus.started": origfile['start_exp_text.started'][i],
- "participant": origfile['participant'][i],
- "trialtype": 0}, #trialtype 0 means it will be discarded
- index=[i-0.75]) #-0.75 to move it above the baseline interval row
- origfile = pd.concat([origfile, newrow1])
- # At the start of a new condition (not the first one), and previous trial end time is available
- elif (not np.isnan(origfile['stimulus.stopped'][i-1])) & (origfile['isi_fc.stopped'][i-1] != 'None'):
- newrow2 = pd.DataFrame({"stimulus.started": origfile['isi_fc.stopped'][i-1],
- "participant": origfile['participant'][i],
- "trialtype": 0},
- index=[i-0.5])
- origfile = pd.concat([origfile, newrow2])
- # New condition but isi_fc.stopped[i-1] is None. Trial at i-1 is discarded as no end time is available.
- elif (not np.isnan(origfile['stimulus.stopped'][i-1])) & (origfile['isi_fc.stopped'][i-1] == 'None'):
- origfile['trialtype'][i-1] = 0
- if not np.isnan(origfile['fc2.started'][i]): #baseline_interval nr 2
- newrow3 = pd.DataFrame({"stimulus.started": origfile['fc2.started'][i],
- "participant": origfile['participant'][i],
- "cond":origfile['cond'][i],
- "trialtype": 32},
- index=[i-0.25])
- origfile = pd.concat([origfile, newrow3])
- #Insert final line to origfile to mark recordings after the final trial (trialtype 0)
- if filename != path + special_case:
- if (i == 500) & (origfile['isi_fc.stopped'][500] != 'None') & (filename != path + special_case):
- newrow4 = pd.DataFrame({"stimulus.started": float(origfile['isi_fc.stopped'][500]),
- "participant": origfile['participant'][i],
- "trialtype": 0},
- index=[i+1])
- origfile = pd.concat([origfile, newrow4])
- # Here again, the last trial isi_fc.stopped is sometimes None
- elif (i == 500) & (origfile['isi_fc.stopped'][500] == 'None'):
- origfile['trialtype'][i-1] = 0
- else: #special case where one condition had to be scanned again
- if (i == 150):
- newrow4 = pd.DataFrame({"stimulus.started": float(origfile['isi_fc.stopped'][150]),
- "participant": origfile['participant'][i],
- "trialtype": 0},
- index=[i+1])
- origfile = pd.concat([origfile, newrow4])
- # Insert all the new lines into their correct place based on their index
- origfile = origfile.sort_index()
- # Remove practice trials and correct stimulus.started variable
- origfile = origfile.iloc[11: , :]
- start_time = origfile['stimulus.started'][10.25]
- origfile['stimulus.started'] = origfile['stimulus.started'].astype(float).subtract(start_time)
- # Create 'name' variable based on trialtype index
- origfile['name'] = ""
- origfile['name'] = np.where((origfile['trialtype']==1) & (origfile['cond']=='fast') & (origfile['buttonBox_3.corr']==1),
- "fast_standard_corr", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==2) & (origfile['cond']=='fast') & (origfile['buttonBox_3.corr']==1),
- "fast_target_corr", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==1) & (origfile['cond']=='mod') & (origfile['buttonBox_3.corr']==1),
- "mod_standard_corr", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==2) & (origfile['cond']=='mod') & (origfile['buttonBox_3.corr']==1),
- "mod_target_corr", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==1) & (origfile['cond']=='slow') & (origfile['buttonBox_3.corr']==1),
- "slow_standard_corr", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==2) & (origfile['cond']=='slow') & (origfile['buttonBox_3.corr']==1),
- "slow_target_corr", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==31) & (origfile['cond']=='fast'),
- "bl1_fast", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==32) & (origfile['cond']=='fast'),
- "bl2_fast", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==31) & (origfile['cond']=='mod'),
- "bl1_mod", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==32) & (origfile['cond']=='mod'),
- "bl2_mod", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==31) & (origfile['cond']=='slow'),
- "bl1_slow", origfile['name'])
- origfile['name'] = np.where((origfile['trialtype']==32) & (origfile['cond']=='slow'),
- "bl2_slow", origfile['name'])
- # "rubbish" is the label used for all data that is not in a (correct) trial or in a baseline interval
- origfile['name'] = np.where(origfile['buttonBox_3.corr']==0, "rubbish", origfile['name'])
- origfile['name'] = np.where(origfile['trialtype']==0, "rubbish", origfile['name'])
- # Rename onset variable
- origfile = origfile.rename(columns={"stimulus.started":"onset"})
- # Keep only name and onset columns
- origfile = origfile[['name', 'onset']]
- origfile.to_csv(f'/{subject_nr}_events.csv') #set path
- # %%
- # Step 2: Convert the events file into an excel in the correct format for struct file conversion in Matlab
- # IMPORTANT: This code only works if there are not already "transformed" files in the path
- path = "" #set path
- os.chdir(path)
- all_files = glob.glob(path + "/*.csv")
- # Make list of all possible trial names
- names= ['bl1_fast', 'bl1_mod', 'bl1_slow', 'bl2_fast', 'bl2_mod', 'bl2_slow',
- 'fast_standard_corr', 'fast_target_corr', 'mod_standard_corr', 'mod_target_corr',
- 'slow_standard_corr', 'slow_target_corr','rubbish']
- for file in all_files:
- print(f'converting {file}')
- df = pd.read_csv(file)
- # Append each onset value to a column that has the respective trialtype name
- new_df = pd.DataFrame(columns = names)
- for i in range(len(df)):
- if df['name'][i] in names:
- new_df = new_df.append({df['name'][i]:df['onset'][i]}, ignore_index=True)
- # Now drop all the NaNs
- new_df = new_df.apply(lambda x: pd.Series(x.dropna().values))
- # Save the file
- new_df.to_excel(f'{file[:-4]}_transformed.xls')
- # %% [markdown]
- # ## LC data
- # Starts with individual LC beta value .csv files extracted from MarsBaR using standard code from the manual
- # %%
- path = "" #set path
- os.chdir(path)
- # list all csvs in path
- all_files = glob.glob(path + "/*.csv")
- # list all var names
- names = ['subject_nr', 'bl1_fast', 'bl1_mod', 'bl1_slow', 'bl2_fast', 'bl2_mod', 'bl2_slow',
- 'fast_standard', 'fast_target', 'mod_standard', 'mod_target',
- 'slow_standard', 'slow_target','rubbish','constant']
- # make df
- beta_df = pd.DataFrame(columns = names)
- for index, file in enumerate(all_files):
- # read subject number out of the file name
- subject_nr = file[-8:-4]
- # list beta values
- row = pd.read_csv(file, header=None)[0].to_list()
- # add subject nr at the beginning of the list
- row.insert(0, subject_nr)
- # add as a row to the df
- beta_df.loc[index]=row
- # The following code not included since it contains sensitive references
- # Fixed split dataset for one subject
- # Created group variable
- # Variables normalised and saved as spreadsheet used for statistical analysis in JASP
- # %%
- # Make plots of (normalised) LC data
- LC_df_load = pd.DataFrame(pd.read_csv('/full_LC_beta_values_normalised.csv')) #set path
- # drop unnecessary vars
- LC_dfx = LC_df_load.drop(['Unnamed: 0','rubbish', 'constant'], axis=1)
- # rename target vars
- LC_df = LC_dfx.rename(columns={"fast_target": "target_fast", "mod_target": "target_mod", "slow_target": "target_slow", "fast_standard": "standard_fast", "mod_standard": "standard_mod", "slow_standard": "standard_slow"})
- LC_df_long = pd.wide_to_long(LC_df1, stubnames = ['bl1', 'bl2', 'target', 'standard'], i=['subject_nr', 'Group'], j = 'Condition', sep='_', suffix=r'\w+').reset_index()
- #Rename labels
- LC_df_long["Condition"] = LC_df_long["Condition"].replace({
- 'fast':'Fast',
- 'mod':'Moderate',
- 'slow':'Slow'
- })
- #Plot phasic LC data
- fig4 = plt.subplots(1, 2, figsize=(8,4))
- sns.set_style("whitegrid")
- plt.subplot(121)
- sns.pointplot('Condition', 'target', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-2, 2))
- plt.ylabel("Phasic LC activity: target trials (beta values)")
- plt.legend(loc='lower left')
- plt.subplot(122)
- sns.pointplot('Condition', 'standard', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-2, 2))
- plt.ylabel("Phasic LC activity: standard trials (beta values)")
- plt.tight_layout()
- plt.show()
- # %%
- #Plot tonic LC data
- fig5 = plt.subplots(1, 2, figsize=(8,4))
- sns.set_style("whitegrid")
- plt.subplot(121)
- sns.pointplot('Condition', 'bl1', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-8, 8))
- plt.ylabel("Tonic LC activity pre-condition (beta values)")
- plt.subplot(122)
- sns.pointplot('Condition', 'bl2', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
- data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-8, 8))
- plt.ylabel("Tonic LC activity mid-condition (beta values)")
- plt.tight_layout()
- plt.show()
- # %% [markdown]
- # ## Assessing movement parameters of the fmriprep output
- # #### Get average standardised DVARS and Framewise Displacement values per participant
- # %%
- import os
- import pandas as pd
- # Set path to the fmriprep output directory
- fmriprep_output_path = r'\fmriprep_output'
- # Create an empty dataframe to store the results
- results_df = pd.DataFrame(columns=['Subject', 'Average_std_dvars', 'SD_std_dvars','Average_framewise_displacement', 'SD_framewise_displacement'])
- # Loop through subject folders
- for subject_folder in os.listdir(fmriprep_output_path):
- subject_path = os.path.join(fmriprep_output_path, subject_folder)
- # Check if the item in the directory is a directory
- if os.path.isdir(subject_path) and subject_folder.startswith('sub-'):
- subject_number = subject_folder.split('-')[1]
- # Define the path to the func directory
- func_path = os.path.join(subject_path, 'func')
- # Loop through files in the func directory
- for filename in os.listdir(func_path):
- if filename.endswith('_task-tdt_run-1_desc-confounds_timeseries.tsv'):
- file_path = os.path.join(func_path, filename)
- # Read the TSV file into a DataFrame
- df = pd.read_csv(file_path, delimiter='\t')
- # Calculate the average and standard deviation for 'std_dvars' and 'framewise_displacement'
- avg_std_dvars = df['std_dvars'].mean()
- sd_std_dvars = df['std_dvars'].std()
- avg_framewise_displacement = df['framewise_displacement'].mean()
- sd_framewise_displacement = df['framewise_displacement'].std()
- # Append results to the dataframe
- results_df = results_df.append({
- 'Subject': subject_number,
- 'Average_std_dvars': avg_std_dvars,
- 'SD_std_dvars': sd_std_dvars,
- 'Average_framewise_displacement': avg_framewise_displacement,
- 'SD_framewise_displacement': sd_framewise_displacement
- }, ignore_index=True)
- # Save the results to an output file
- results_df.to_csv('/Motion_subject_averages.csv', index=False) #set path
Analysis_jupyter_notebook.ipynb at commit 6b9cf4a, under CC-BY-4.0 · at the source
Overview
- Department of Experimental Clinical and Health Psychology, Ghent University, Ghent, Belgium
- University Psychiatric Center KU Leuven, Kortenberg, Belgium
- Department of Health, Medical, and Neuropsychology, Leiden University, Leiden, The Netherlands
- Liverpool John Moores University, Liverpool, United Kingdom
- Department of Experimental Psychology, Ghent University, Ghent, Belgium
Abstract
The state regulation deficit account of attention-deficit hyperactivity disorder (ADHD) posits that symptoms and performance deficits associated with ADHD are context-dependent and explained by a deficit in arousal regulation. Research into this topic has often used event rate manipulations to induce different arousal states, and has demonstrated deficits at both overstimulating and understimulating event rate levels. Although existing research has provided strong support for the state regulation deficit account, little is known about the neurobiological substrate of state regulation deficits. An important candidate brain network is the Locus Coeruleus-noradrenergic (LC-NE) system, which has been hypothesized by several researchers to play a key role in state regulation deficits in ADHD. In the current study, we examined, for the first time, the role of the LC in state regulation deficits in ADHD using high-resolution fMRI scans. We presented a target detection task at three event rate levels (fast, moderate, slow) to adults with (n = 27) and without ADHD (n = 28), with 20-s resting intervals at the start and the middle of each event rate condition. No group difference was found for performance, whereas results indicated significantly higher self-reports of state regulation deficits in daily life in the ADHD group. Anatomically guided region-of-interest analyses based on a high-resolution turbo-spin-echo anatomical scan of the pons region indicated an overall lower LC activity during resting intervals in the ADHD group, irrespective of event rate. Event-related LC activity was not impacted by event rate or by group. Our results, therefore, support the notion of a general “underarousal” in ADHD, but do not confirm a relationship between LC activity and behavior, raising doubts on a direct implication of the LC-NE system in state regulation deficits in ADHD.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.
l-drescher/codes_fMRI_LC_ADHD
6b9cf4aebb1eec9da54c9f5a7b1a7786a0535a68, 12 January 2026Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
3 files
- Analysis_jupyter_noteboo
k.ipynb — Jupyter, 691 lines, 2 matches - LICENSE — License, 396 lines
- README.md — Text, 9 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 1 script, each with its path and the digest of its content;
- 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data and Code Availability
Experimental data may be shared upon approval by the Ethics Committee of Ghent University Hospital and the completion of a GDPR-conform data sharing agreement. The paradigm and analysis code is publicly available on GitHub: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 29 September 2026: the first record
Recorded: type, language, journal, volume, pages, dates, 6 authors, 5 keywords, 2 funders, 96 references.
Cite
This paper
Drescher, L. H., Hall, J. M., Eayrs, J. O., Krebs, R. M., Boehler, C. N., & Wiersema, J. R. (2026). An fMRI examination of the role of the Locus Coeruleus in state regulation in ADHD. Imaging neuroscience (Cambridge, Mass.), 4, IMAG.a.1200. https://
BibTeX
@article{drescher2026fmr
author = {Drescher, Leonhard H and Hall, Julie M and Eayrs, Joshua O and Krebs, Ruth M and Boehler, C Nico and Wiersema, Jan R},
title = {{An fMRI examination of the role of the Locus Coeruleus in state regulation in ADHD}},
journal = {Imaging neuroscience (Cambridge, Mass.)},
year = {2026},
month = apr,
volume = {4},
pages = {IMAG.a.1200},
publisher = {MIT Press},
issn = {2837-6056},
doi = {10.1162/
url = {https://
pmid = {41993143},
pmcid = {PMC13081741}
}
RIS
TY - JOUR
AU - Drescher, Leonhard H
AU - Hall, Julie M
AU - Eayrs, Joshua O
AU - Krebs, Ruth M
AU - Boehler, C Nico
AU - Wiersema, Jan R
TI - An fMRI examination of the role of the Locus Coeruleus in state regulation in ADHD
T2 - Imaging neuroscience (Cambridge, Mass.)
J2 - Imaging Neurosci (Camb)
PY - 2026
DA - 2026/
VL - 4
SP - IMAG.a.1200
SN - 2837-6056
PB - MIT Press
DO - 10.1162/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1162/
"type": "article-journal",
"title": "An fMRI examination of the role of the Locus Coeruleus in state regulation in ADHD",
"container-title": "Imaging neuroscience (Cambridge, Mass.)",
"author": [
{
"family": "Drescher",
"given": "Leonhard H"
},
{
"family": "Hall",
"given": "Julie M"
},
{
"family": "Eayrs",
"given": "Joshua O"
},
{
"family": "Krebs",
"given": "Ruth M"
},
{
"family": "Boehler",
"given": "C Nico"
},
{
"family": "Wiersema",
"given": "Jan R"
}
],
"container-title-short":
"volume": "4",
"page": "IMAG.a.1200",
"DOI": "10.1162/
"PMID": "41993143",
"PMCID": "PMC13081741",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
13
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1371/journal.pone.0355165 [code]
- Pupillary dynamics during hands-off L2 driving and transitions of control under high cognitive load.Journal: PloS oneIn common: 8 references
- [2] doi:10.1038/s41598-026-46731-3 [code]
- Investigating the test-retest reliability and behavioral correlates of pupil responses in the n-back task.Journal: Scientific reportsIn common: 5 references
- [3] doi:10.1111/ejn.70543 [code]
- Neural Oscillations Track Subjective and Pupillary Arousal During Naturalistic Movie Viewing.Journal: The European journal of neuroscienceIn common: SciPy, NumPy, 4 references
- [4] doi:10.1126/sciadv.adz6495
- Pupil-linked arousal heterogeneously modulates cell-type-specific sensory processing.Journal: Science advancesIn common: 5 references
- [5] doi:10.1002/hbm.70496 [code]
- Transdiagnostic Profiles of BOLD Signal Variability in Autism and Schizophrenia Spectrum Disorders: Associations With Cognition and Functioning.Journal: Human brain mappingIn common: seaborn, pandas, SciPy, 2 other tools, fMRI, 2 references
- [6] doi:10.1038/s41398-026-04080-9 [code]
- Altered flow of information in attention-deficit/
hyperactivity disorder. Journal: Translational psychiatryIn common: pandas, SciPy, Matplotlib, 1 other tool, ADHD, 2 references - [7] doi:10.1093/braincomms/fcag270 [code]
- Neural correlates of emotion processing and regulation dissociate frontal and temporal lobe epilepsy.Journal: Brain communicationsIn common: seaborn, pandas, SciPy, 2 other tools, fMRI, 2 references
- [8] doi:10.1162/nol.a.271 [code]
- Compositional Complexity in Text and Images.Journal: Neurobiology of language (Cambridge, Mass.)In common: seaborn, pandas, SciPy, 2 other tools, fMRI, 2 references
- [9] doi:10.1162/imag.a.1286 [code]
- Behavioral imitation with artificial neural networks leads to personalized models of brain dynamics during videogame play.Journal: Imaging neuroscience (Cambridge, Mass.)In common: seaborn, pandas, SciPy, 2 other tools, fMRI, 2 references
- [10] doi:10.1162/imag.a.1256 [code]
- Gamer in the scanner: Event-related analysis of fMRI activity during retro videogame play guided by automated annotations of game content.Journal: Imaging neuroscience (Cambridge, Mass.)In common: seaborn, pandas, SciPy, 2 other tools, fMRI, 2 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 1 script, and 2 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:4eb4dcbf62fb69fa…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
