OSCR

An fMRI examination of the role of the Locus Coeruleus in state regulation in ADHD.

Code ↔ Paper

2 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 2 matches
  1. [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. [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

  1. # %% [markdown]
  2. # # Analysis pipeline for questionnaire, behavioral and LC data of ADHD experiment with fMRI
  3. #
  4. # Leonhard H. Drescher, Ghent University, 2022-2025
  5. #
  6. # #### Short description of the experiment:
  7. # Participants: Adults with ADHD (n = 27) and adults without any psychiatric diagnosis (n = 28).
  8. #
  9. # 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).
  10. #
  11. # 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).
  12. #
  13. # License: CC-BY
  14. # %%
  15. import pandas as pd
  16. import numpy as np
  17. import os, sys
  18. import matplotlib
  19. from matplotlib import pyplot as plt
  20. import seaborn as sns
  21. import glob
  22. import re
  23. from plotnine import *
  24. import scipy
  25. # %% [markdown]
  26. # ## Questionnaire data
  27. # Note that scoring was implemented during data collection, so sum and scale scores are already available in the raw data.
  28. # %%
  29. #Load main questionnaire data
  30. quest_df = pd.DataFrame(pd.read_csv('/quest_df.csv')) #set path
  31. #Load asrs-srd data and merge into questionnaire dataframe
  32. asrs_df = pd.DataFrame(pd.read_excel('/asrs-srd.xlsx')) #set path
  33. quest_df = pd.merge(quest_df, asrs_df, on='id', validate="one_to_one")
  34. #Make the ASR t-scores (normed) numeric only, as the values contain a clinical label
  35. quest_df['DSMDepr_t_num'] = quest_df['DSMDepr_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  36. quest_df['DSMAnx_t_num'] = quest_df['DSMAnx_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  37. quest_df['DSMADHDtot_t_num'] = quest_df['DSMADHDtot_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  38. quest_df['T_subst_t_num'] = quest_df['T_subst_t'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  39. quest_df['T_subst_a_num'] = quest_df['T_subst_a'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  40. quest_df['T_subst_d_num'] = quest_df['T_subst_d'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  41. quest_df['Mean_subst_num'] = quest_df['Mean_subst'].astype('str').str.extractall('(\d+)').unstack().sum(axis=1).astype(int)
  42. #Get M(SD) or median (min-max) per group and questionnaire scale + respective group difference statistics test
  43. print('Questionnaire descriptives per group: \n')
  44. print('Age M:')
  45. print(str(quest_df.groupby(['Group'])['age'].mean()),'\n')
  46. print('Age SD:')
  47. print(str(quest_df.groupby(['Group'])['age'].std()),'\n')
  48. print('Age t-test:')
  49. print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['age'],
  50. quest_df[quest_df['Group'] == 'CTRL']['age']), '\n')
  51. print('IQ M:')
  52. print(str(quest_df.groupby(['Group'])['WASI-Tot'].mean()),'\n')
  53. print('IQ SD:')
  54. print(str(quest_df.groupby(['Group'])['WASI-Tot'].std()),'\n')
  55. print('IQ t-test:')
  56. print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['WASI-Tot'],
  57. quest_df[quest_df['Group'] == 'CTRL']['WASI-Tot']), '\n')
  58. print('SRS M:')
  59. print(str(quest_df.groupby(['Group'])['SRS_total'].mean()),'\n')
  60. print('SRS SD:')
  61. print(str(quest_df.groupby(['Group'])['SRS_total'].std()),'\n')
  62. print('SRS t-test:')
  63. print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['SRS_total'],
  64. quest_df[quest_df['Group'] == 'CTRL']['SRS_total']), '\n')
  65. print('State regulation (SRD) M:')
  66. print(str(quest_df.groupby(['Group'])['sum_SRD'].mean()),'\n')
  67. print('State regulation (SRD) SD:')
  68. print(str(quest_df.groupby(['Group'])['sum_SRD'].std()),'\n')
  69. print('State regulation (SRD) t-test:')
  70. print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['sum_SRD'],
  71. quest_df[quest_df['Group'] == 'CTRL']['sum_SRD']), '\n')
  72. print('short-ASRS M:')
  73. print(str(quest_df.groupby(['Group'])['short_ASRS'].mean()),'\n')
  74. print('short-ASRS SD:')
  75. print(str(quest_df.groupby(['Group'])['short_ASRS'].std()),'\n')
  76. print('short-ASRS t-test:')
  77. print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['short_ASRS'],
  78. quest_df[quest_df['Group'] == 'CTRL']['short_ASRS']), '\n')
  79. print('full-ASRS M:')
  80. print(str(quest_df.groupby(['Group'])['full_ASRS'].mean()),'\n')
  81. print('full-ASRS SD:')
  82. print(str(quest_df.groupby(['Group'])['full_ASRS'].std()),'\n')
  83. print('full-ASRS t-test:')
  84. print(scipy.stats.ttest_ind(quest_df[quest_df['Group'] == 'ADHD']['full_ASRS'],
  85. quest_df[quest_df['Group'] == 'CTRL']['full_ASRS']), '\n')
  86. print('ASR ADHD Median:')
  87. print(str(quest_df.groupby(['Group'])['DSMADHDtot_t_num'].median()),'\n')
  88. print('ASR ADHD min-max: \n ADHD:')
  89. print(quest_df[quest_df['Group'] == 'ADHD']['DSMADHDtot_t_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['DSMADHDtot_t_num'].max())
  90. print('CTRL:')
  91. print(quest_df[quest_df['Group'] == 'CTRL']['DSMADHDtot_t_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['DSMADHDtot_t_num'].max(),'\n')
  92. print('ASR ADHD Mann Whitney U test:')
  93. print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['DSMADHDtot_t_num'],
  94. quest_df[quest_df['Group'] == 'CTRL']['DSMADHDtot_t_num']), '\n')
  95. print('ASR Depression Median:')
  96. print(str(quest_df.groupby(['Group'])['DSMDepr_t_num'].median()),'\n')
  97. print('ASR Depression min-max: \n ADHD:')
  98. print(quest_df[quest_df['Group'] == 'ADHD']['DSMDepr_t_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['DSMDepr_t_num'].max())
  99. print('CTRL:')
  100. print(quest_df[quest_df['Group'] == 'CTRL']['DSMDepr_t_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['DSMDepr_t_num'].max(),'\n')
  101. print('ASR Depression Mann Whitney U test:')
  102. print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['DSMDepr_t_num'],
  103. quest_df[quest_df['Group'] == 'CTRL']['DSMDepr_t_num']), '\n')
  104. print('ASR Anxiety Median:')
  105. print(str(quest_df.groupby(['Group'])['DSMAnx_t_num'].median()),'\n')
  106. print('ASR Anxiety min-max: \n ADHD:')
  107. print(quest_df[quest_df['Group'] == 'ADHD']['DSMAnx_t_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['DSMAnx_t_num'].max())
  108. print('CTRL:')
  109. print(quest_df[quest_df['Group'] == 'CTRL']['DSMAnx_t_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['DSMAnx_t_num'].max(),'\n')
  110. print('ASR Anxiety Mann Whitney U test:')
  111. print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['DSMAnx_t_num'],
  112. quest_df[quest_df['Group'] == 'CTRL']['DSMAnx_t_num']), '\n')
  113. print('ASR Substance Abuse Median:')
  114. print(str(quest_df.groupby(['Group'])['Mean_subst_num'].median()),'\n')
  115. print('ASR Substance Abuse min-max: \n ADHD:')
  116. print(quest_df[quest_df['Group'] == 'ADHD']['Mean_subst_num'].min(), '-', quest_df[quest_df['Group'] == 'ADHD']['Mean_subst_num'].max())
  117. print('CTRL:')
  118. print(quest_df[quest_df['Group'] == 'CTRL']['Mean_subst_num'].min(), '-', quest_df[quest_df['Group'] == 'CTRL']['Mean_subst_num'].max(),'\n')
  119. print('ASR Substance Abuse Mann Whitney U test:')
  120. print(scipy.stats.mannwhitneyu(quest_df[quest_df['Group'] == 'ADHD']['Mean_subst_num'],
  121. quest_df[quest_df['Group'] == 'CTRL']['Mean_subst_num']), '\n')
  122. #Export the spreadsheet
  123. #quest_df.to_csv('/quest_df.csv') #set path
  124. # %%
  125. #Cronbach's alpha calculations on some questionnaires
  126. #Create filters for both questionnaires
  127. filter_ASRS = [col for col in quest_df if col.startswith('ASRS[')]
  128. filter_SRD = [col for col in quest_df if col.startswith('SRD[')]
  129. #Remove A's from values
  130. quest_df = quest_df.replace({'A0':1, 'A1':2, 'A2':3, 'A3':4, 'A4':5})
  131. # Transform the df into a correlation matrix
  132. def cronbach_alpha(df):
  133. df_corr = df.corr()
  134. # Calculate N (number of questions)
  135. N = df.shape[1]
  136. # Calculate R
  137. # Loop through the columns and append every relevant correlation to an array called "rs".
  138. rs = np.array([])
  139. for i, col in enumerate(df_corr.columns):
  140. sum_ = df_corr[col][i+1:].values
  141. rs = np.append(sum_, rs)
  142. mean_r = np.mean(rs) # Calculate the mean of rs
  143. # Cronbach's Alpha formula
  144. cronbach_alpha = (N * mean_r) / (1 + (N - 1) * mean_r)
  145. return cronbach_alpha
  146. print("Cronbach's alpha for the full ASRS: ", np.round(cronbach_alpha(quest_df[filter_ASRS]),4))
  147. print("Cronbach's alpha for the short ASRS: ", np.round(cronbach_alpha(quest_df[filter_ASRS[:6]]),4))
  148. print("Cronbach's alpha for the SRD: ", np.round(cronbach_alpha(quest_df[filter_SRD]),4))
  149. # %% [markdown]
  150. # ## Behavioral data
  151. # %%
  152. path = "/Behavioral_rawdata" #insert path to folder containing behavioral raw data files (Psychopy output)
  153. os.chdir(path)
  154. csv_files = glob.glob(path + "/*.csv")
  155. valid_files = []
  156. #remove files with less than 100 MB as they don't contain valid data
  157. for f in csv_files:
  158. if os.stat(f).st_size > 100000:
  159. valid_files.append(f)
  160. #One exception when last condition was restarted (t2): append as well!
  161. #Code not included since it contains sensitive references
  162. #In another subject, correct missing '-' in default ppt number structure (sub-xxxx)!
  163. #Create preliminary data frame
  164. bdf = []
  165. for filename in valid_files:
  166. df = pd.read_csv(filename, index_col=None, header=0)
  167. bdf.append(df)
  168. behavioral_df = pd.concat(bdf, axis=0, ignore_index=True)
  169. #Also correct wrongly inserted string from one participant nr!
  170. #remove all rows that are not trials
  171. behavioral_df = behavioral_df[~behavioral_df['cond'].isnull()]
  172. # Create group variable ['Group']
  173. # %%
  174. #Extract and check RT data
  175. #Extract rt_df with only correct trials
  176. # Create functions for percentiles
  177. def q25(x):
  178. return x.quantile(0.25)
  179. def q75(x):
  180. return x.quantile(0.75)
  181. rt_df = behavioral_df[(behavioral_df['trialtype']==2) & (behavioral_df['buttonBox_3.corr']==1)].groupby(
  182. ['participant', 'Group', 'cond'], as_index=False).agg({'buttonBox_3.rt': [q25, np.median, q75, np.mean, np.std]})
  183. #Make the column names nice again
  184. rt_df.columns = rt_df.columns.to_flat_index()
  185. rt_df.columns = ['subject', 'Group', 'Condition', 'rt_q25', 'rt_median', 'rt_q75', 'rt_mean', 'rt_std']
  186. # Calculate quartile-based coefficient of variability (IQR / median)
  187. rt_df['qcv'] = (rt_df['rt_q75'] - rt_df['rt_q25']) / rt_df['rt_median']
  188. rt_df['cv'] = (rt_df['rt_std'] / rt_df['rt_mean'])
  189. #Save rt_df for statistical analysis (long format)
  190. # rt_df.to_csv('/rt_df.csv') #set path
  191. #Save rt_df for statistical analysis (wide format)
  192. # rt_df1 = rt_df.pivot_table(index=['subject', 'Group'],
  193. # columns='condition',
  194. # values=['rt_q25', 'rt_median','rt_q75','rt_mean','rt_std','qcv','cv'])
  195. # rt_df1.columns = rt_df1.columns.to_series().str.join('_')
  196. # rt_df1.reset_index()
  197. # rt_df1.to_csv('/rt_df_wide.csv') #set path
  198. #Change condition values so they show up nicely
  199. rt_df["Condition"] = rt_df["Condition"].replace({
  200. 'fast':'Fast',
  201. 'mod':'Moderate',
  202. 'slow':'Slow'
  203. })
  204. # Interaction plots RT and RTV. Error bars are +- 1 standard error around mean.
  205. fig1 = plt.subplots(1, 2, figsize=(8,4))
  206. #sns.set_style("whitegrid")
  207. plt.subplot(121)
  208. sns.pointplot('Condition', 'rt_median', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  209. data=rt_df, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68)
  210. plt.ylabel("Reaction time (median)")
  211. plt.subplot(122)
  212. sns.pointplot('Condition', 'qcv', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  213. data=rt_df, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68)
  214. plt.ylabel("Reaction time variability (IQR/median)")
  215. plt.tight_layout()
  216. plt.show()
  217. # Print number of observations and means
  218. rt_nobs = rt_df.groupby(['Group', 'Condition'])['rt_median'].count()
  219. rt_md_means = rt_df.groupby(['Group', 'Condition'])['rt_median'].mean()
  220. qcv_means = rt_df.groupby(['Group', 'Condition'])['qcv'].mean()
  221. print(f"number of observations:\n{rt_nobs} \nrt_md means: \n{rt_md_means} \nqcv_means: {qcv_means}")
  222. # %%
  223. #Normality testing for each of the participants' RT data per condition
  224. from scipy.stats import shapiro
  225. rt_distr_df1 = behavioral_df[(behavioral_df['trialtype']==2) & (behavioral_df['buttonBox_3.corr']==1)]
  226. rt_distr_df = rt_distr_df1[['cond', 'buttonBox_3.rt', 'participant']]
  227. rt_distr_df
  228. # Initialize counter for non-normal distributions
  229. non_normal_count = 0
  230. # Iterate over each participant and each condition
  231. for participant in rt_distr_df['participant'].unique():
  232. participant_data = rt_distr_df[rt_distr_df['participant'] == participant]
  233. for condition in participant_data['cond'].unique():
  234. condition_data = participant_data[participant_data['cond'] == condition]['buttonBox_3.rt']
  235. # Perform Shapiro-Wilk test
  236. stat, p_value = shapiro(condition_data)
  237. # Output the results
  238. print(f"Shapiro-Wilk test for participant {participant} in condition {condition}:")
  239. print(f"Statistic: {stat}, p-value: {p_value}")
  240. # Check for non-normal distribution
  241. if p_value < 0.05:
  242. non_normal_count += 1
  243. print("\n")
  244. # Output summary
  245. print(f"Number of tests with non-normal distribution: {non_normal_count} out of 165")
  246. # %%
  247. #Extract and check commission error data
  248. #Unstacking and restacking prevents conditions with zero errors from being ignored
  249. com_error_df = behavioral_df[(behavioral_df['trialtype']==1) & (behavioral_df['buttonBox_3.corr']==0)].groupby(
  250. ['participant', 'Group', 'cond']).agg({'buttonBox_3.corr':'count'}).unstack(fill_value=0).stack().reset_index()
  251. com_error_df.rename(columns={'buttonBox_3.corr':'com_error_count'}, inplace=True)
  252. # Correct for number of trials by inserting them into the dataframe
  253. trial_count_df = behavioral_df.groupby(['participant', 'cond']).agg(
  254. {'buttonBox_3.corr':'count'}).unstack(fill_value=0).stack().reset_index()
  255. trial_count_df.rename(columns={'buttonBox_3.corr':'total'}, inplace=True)
  256. com_error_df = pd.merge(com_error_df, trial_count_df, on=['participant', 'cond'], validate="one_to_one")
  257. com_error_df['com_err_percent'] = (com_error_df['com_error_count'] / com_error_df['total'])*100
  258. #Save com_error_df for statistical analysis
  259. # com_error_df.to_csv('/com_error_df.csv') #set path
  260. #Save com_error_df for statistical analysis (wide format)
  261. # com_error_df1 = com_error_df.pivot_table(index=['participant', 'Group'],
  262. # columns='cond',
  263. # values=['com_error_count', 'total','com_err_percent'])
  264. # com_error_df1.columns = com_error_df1.columns.to_series().str.join('_')
  265. # com_error_df1.reset_index()
  266. # com_error_df1.to_csv('/com_error_df_wide.csv') #set path
  267. # Plot the data. Error bars are +- 1 standard error around mean.
  268. fig2 = plt.subplots(1, 1, figsize=(7,7))
  269. sns.pointplot('cond', 'com_err_percent', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  270. data=com_error_df, dodge=True, order=["fast", "mod", "slow"], ci=68)
  271. plt.title('Commission errors')
  272. plt.show()
  273. # Print number of observations and means.
  274. com_nobs = com_error_df.groupby(['Group', 'cond'])['com_err_percent'].count()
  275. com_means = com_error_df.groupby(['Group', 'cond'])['com_err_percent'].mean()
  276. print(f"number of observations: \n{com_nobs} \nmeans: \n{com_means}")
  277. # %%
  278. #SOLELY AS A CHECK OF TASK ENGAGEMENT (omission errors; very low error numbers expected)
  279. om_error_df = behavioral_df[(behavioral_df['trialtype']==2) & (behavioral_df['buttonBox_3.corr']==0)].groupby(
  280. ['participant', 'Group', 'cond']).agg({'buttonBox_3.corr':'count'}).unstack(fill_value=0).stack().reset_index()
  281. om_error_df.rename(columns={'buttonBox_3.corr':'om_error_count'}, inplace=True)
  282. om_error_df = pd.merge(om_error_df, trial_count_df, on=['participant', 'cond'], validate="one_to_one")
  283. om_error_df['om_err_percent'] = (om_error_df['om_error_count'] / om_error_df['total'])*100
  284. # Plot the data. Error bars are +- 1 standard error around mean.
  285. fig3 = plt.subplots(1, 1, figsize=(7,7))
  286. sns.pointplot('cond', 'om_err_percent', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  287. data=om_error_df, dodge=True, order=["fast", "mod", "slow"], ci=68)
  288. plt.title('Omission errors')
  289. plt.show()
  290. # Print number of observations and means.
  291. om_nobs = om_error_df.groupby(['Group', 'cond'])['om_err_percent'].count()
  292. om_means = om_error_df.groupby(['Group', 'cond'])['om_err_percent'].mean()
  293. print(f"number of observations: \n{om_nobs} \nmeans: \n{om_means}")
  294. # %% [markdown]
  295. # ## Create onset (struct) files for SPM12
  296. # -The code below creates onset files from Psychopy behavioral data, to be used in Matlab/SPM12 for FMRI analysis (struct format). <br>
  297. # -Subsequent SPM12 processing steps were done manually or with simple code loops (not included). MarsBaR exctraction done with code from the manual.
  298. # %%
  299. # Step 1: Convert all behavioral datasets to _events.csv datasets, which is simply a list of onset times
  300. path = "/Behavioral_rawdata/" #set path
  301. os.chdir(path)
  302. csv_files = glob.glob(path + "/*.csv")
  303. valid_files = []
  304. #remove files with less than 100 MB
  305. for f in csv_files:
  306. if os.stat(f).st_size > 100000:
  307. valid_files.append(f)
  308. #Append also the restarted last condition of the task of one subject!
  309. #loop over all subjects and trials
  310. for filename in valid_files:
  311. with open(filename, 'r') as f:
  312. print(f'converting {filename}')
  313. origfile = pd.read_csv(filename, index_col=None, header=0)
  314. for i in range(len(origfile)):
  315. # create participant variable
  316. subject_nr = origfile['participant'][1]
  317. #Correct missing dash in one subject name!
  318. #Continue operation
  319. if not np.isnan(origfile['fc1.started'][i]): #baseline_interval nr 1 (before each condition)
  320. newrow0 = pd.DataFrame({"stimulus.started": origfile['fc1.started'][i],
  321. "participant": origfile['participant'][i],
  322. "cond":origfile['cond'][i],
  323. "trialtype": 31},
  324. index=[i-0.25]) #trick to insert row without messing with original index
  325. origfile = pd.concat([origfile, newrow0])
  326. if np.isnan(origfile['stimulus.stopped'][i-1]): #first onset of experiment (scanner trigger)
  327. newrow1 = pd.DataFrame({"stimulus.started": origfile['start_exp_text.started'][i],
  328. "participant": origfile['participant'][i],
  329. "trialtype": 0}, #trialtype 0 means it will be discarded
  330. index=[i-0.75]) #-0.75 to move it above the baseline interval row
  331. origfile = pd.concat([origfile, newrow1])
  332. # At the start of a new condition (not the first one), and previous trial end time is available
  333. elif (not np.isnan(origfile['stimulus.stopped'][i-1])) & (origfile['isi_fc.stopped'][i-1] != 'None'):
  334. newrow2 = pd.DataFrame({"stimulus.started": origfile['isi_fc.stopped'][i-1],
  335. "participant": origfile['participant'][i],
  336. "trialtype": 0},
  337. index=[i-0.5])
  338. origfile = pd.concat([origfile, newrow2])
  339. # New condition but isi_fc.stopped[i-1] is None. Trial at i-1 is discarded as no end time is available.
  340. elif (not np.isnan(origfile['stimulus.stopped'][i-1])) & (origfile['isi_fc.stopped'][i-1] == 'None'):
  341. origfile['trialtype'][i-1] = 0
  342. if not np.isnan(origfile['fc2.started'][i]): #baseline_interval nr 2
  343. newrow3 = pd.DataFrame({"stimulus.started": origfile['fc2.started'][i],
  344. "participant": origfile['participant'][i],
  345. "cond":origfile['cond'][i],
  346. "trialtype": 32},
  347. index=[i-0.25])
  348. origfile = pd.concat([origfile, newrow3])
  349. #Insert final line to origfile to mark recordings after the final trial (trialtype 0)
  350. if filename != path + special_case:
  351. if (i == 500) & (origfile['isi_fc.stopped'][500] != 'None') & (filename != path + special_case):
  352. newrow4 = pd.DataFrame({"stimulus.started": float(origfile['isi_fc.stopped'][500]),
  353. "participant": origfile['participant'][i],
  354. "trialtype": 0},
  355. index=[i+1])
  356. origfile = pd.concat([origfile, newrow4])
  357. # Here again, the last trial isi_fc.stopped is sometimes None
  358. elif (i == 500) & (origfile['isi_fc.stopped'][500] == 'None'):
  359. origfile['trialtype'][i-1] = 0
  360. else: #special case where one condition had to be scanned again
  361. if (i == 150):
  362. newrow4 = pd.DataFrame({"stimulus.started": float(origfile['isi_fc.stopped'][150]),
  363. "participant": origfile['participant'][i],
  364. "trialtype": 0},
  365. index=[i+1])
  366. origfile = pd.concat([origfile, newrow4])
  367. # Insert all the new lines into their correct place based on their index
  368. origfile = origfile.sort_index()
  369. # Remove practice trials and correct stimulus.started variable
  370. origfile = origfile.iloc[11: , :]
  371. start_time = origfile['stimulus.started'][10.25]
  372. origfile['stimulus.started'] = origfile['stimulus.started'].astype(float).subtract(start_time)
  373. # Create 'name' variable based on trialtype index
  374. origfile['name'] = ""
  375. origfile['name'] = np.where((origfile['trialtype']==1) & (origfile['cond']=='fast') & (origfile['buttonBox_3.corr']==1),
  376. "fast_standard_corr", origfile['name'])
  377. origfile['name'] = np.where((origfile['trialtype']==2) & (origfile['cond']=='fast') & (origfile['buttonBox_3.corr']==1),
  378. "fast_target_corr", origfile['name'])
  379. origfile['name'] = np.where((origfile['trialtype']==1) & (origfile['cond']=='mod') & (origfile['buttonBox_3.corr']==1),
  380. "mod_standard_corr", origfile['name'])
  381. origfile['name'] = np.where((origfile['trialtype']==2) & (origfile['cond']=='mod') & (origfile['buttonBox_3.corr']==1),
  382. "mod_target_corr", origfile['name'])
  383. origfile['name'] = np.where((origfile['trialtype']==1) & (origfile['cond']=='slow') & (origfile['buttonBox_3.corr']==1),
  384. "slow_standard_corr", origfile['name'])
  385. origfile['name'] = np.where((origfile['trialtype']==2) & (origfile['cond']=='slow') & (origfile['buttonBox_3.corr']==1),
  386. "slow_target_corr", origfile['name'])
  387. origfile['name'] = np.where((origfile['trialtype']==31) & (origfile['cond']=='fast'),
  388. "bl1_fast", origfile['name'])
  389. origfile['name'] = np.where((origfile['trialtype']==32) & (origfile['cond']=='fast'),
  390. "bl2_fast", origfile['name'])
  391. origfile['name'] = np.where((origfile['trialtype']==31) & (origfile['cond']=='mod'),
  392. "bl1_mod", origfile['name'])
  393. origfile['name'] = np.where((origfile['trialtype']==32) & (origfile['cond']=='mod'),
  394. "bl2_mod", origfile['name'])
  395. origfile['name'] = np.where((origfile['trialtype']==31) & (origfile['cond']=='slow'),
  396. "bl1_slow", origfile['name'])
  397. origfile['name'] = np.where((origfile['trialtype']==32) & (origfile['cond']=='slow'),
  398. "bl2_slow", origfile['name'])
  399. # "rubbish" is the label used for all data that is not in a (correct) trial or in a baseline interval
  400. origfile['name'] = np.where(origfile['buttonBox_3.corr']==0, "rubbish", origfile['name'])
  401. origfile['name'] = np.where(origfile['trialtype']==0, "rubbish", origfile['name'])
  402. # Rename onset variable
  403. origfile = origfile.rename(columns={"stimulus.started":"onset"})
  404. # Keep only name and onset columns
  405. origfile = origfile[['name', 'onset']]
  406. origfile.to_csv(f'/{subject_nr}_events.csv') #set path
  407. # %%
  408. # Step 2: Convert the events file into an excel in the correct format for struct file conversion in Matlab
  409. # IMPORTANT: This code only works if there are not already "transformed" files in the path
  410. path = "" #set path
  411. os.chdir(path)
  412. all_files = glob.glob(path + "/*.csv")
  413. # Make list of all possible trial names
  414. names= ['bl1_fast', 'bl1_mod', 'bl1_slow', 'bl2_fast', 'bl2_mod', 'bl2_slow',
  415. 'fast_standard_corr', 'fast_target_corr', 'mod_standard_corr', 'mod_target_corr',
  416. 'slow_standard_corr', 'slow_target_corr','rubbish']
  417. for file in all_files:
  418. print(f'converting {file}')
  419. df = pd.read_csv(file)
  420. # Append each onset value to a column that has the respective trialtype name
  421. new_df = pd.DataFrame(columns = names)
  422. for i in range(len(df)):
  423. if df['name'][i] in names:
  424. new_df = new_df.append({df['name'][i]:df['onset'][i]}, ignore_index=True)
  425. # Now drop all the NaNs
  426. new_df = new_df.apply(lambda x: pd.Series(x.dropna().values))
  427. # Save the file
  428. new_df.to_excel(f'{file[:-4]}_transformed.xls')
  429. # %% [markdown]
  430. # ## LC data
  431. # Starts with individual LC beta value .csv files extracted from MarsBaR using standard code from the manual
  432. # %%
  433. path = "" #set path
  434. os.chdir(path)
  435. # list all csvs in path
  436. all_files = glob.glob(path + "/*.csv")
  437. # list all var names
  438. names = ['subject_nr', 'bl1_fast', 'bl1_mod', 'bl1_slow', 'bl2_fast', 'bl2_mod', 'bl2_slow',
  439. 'fast_standard', 'fast_target', 'mod_standard', 'mod_target',
  440. 'slow_standard', 'slow_target','rubbish','constant']
  441. # make df
  442. beta_df = pd.DataFrame(columns = names)
  443. for index, file in enumerate(all_files):
  444. # read subject number out of the file name
  445. subject_nr = file[-8:-4]
  446. # list beta values
  447. row = pd.read_csv(file, header=None)[0].to_list()
  448. # add subject nr at the beginning of the list
  449. row.insert(0, subject_nr)
  450. # add as a row to the df
  451. beta_df.loc[index]=row
  452. # The following code not included since it contains sensitive references
  453. # Fixed split dataset for one subject
  454. # Created group variable
  455. # Variables normalised and saved as spreadsheet used for statistical analysis in JASP
  456. # %%
  457. # Make plots of (normalised) LC data
  458. LC_df_load = pd.DataFrame(pd.read_csv('/full_LC_beta_values_normalised.csv')) #set path
  459. # drop unnecessary vars
  460. LC_dfx = LC_df_load.drop(['Unnamed: 0','rubbish', 'constant'], axis=1)
  461. # rename target vars
  462. 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"})
  463. 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()
  464. #Rename labels
  465. LC_df_long["Condition"] = LC_df_long["Condition"].replace({
  466. 'fast':'Fast',
  467. 'mod':'Moderate',
  468. 'slow':'Slow'
  469. })
  470. #Plot phasic LC data
  471. fig4 = plt.subplots(1, 2, figsize=(8,4))
  472. sns.set_style("whitegrid")
  473. plt.subplot(121)
  474. sns.pointplot('Condition', 'target', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  475. data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-2, 2))
  476. plt.ylabel("Phasic LC activity: target trials (beta values)")
  477. plt.legend(loc='lower left')
  478. plt.subplot(122)
  479. sns.pointplot('Condition', 'standard', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  480. data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-2, 2))
  481. plt.ylabel("Phasic LC activity: standard trials (beta values)")
  482. plt.tight_layout()
  483. plt.show()
  484. # %%
  485. #Plot tonic LC data
  486. fig5 = plt.subplots(1, 2, figsize=(8,4))
  487. sns.set_style("whitegrid")
  488. plt.subplot(121)
  489. sns.pointplot('Condition', 'bl1', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  490. data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-8, 8))
  491. plt.ylabel("Tonic LC activity pre-condition (beta values)")
  492. plt.subplot(122)
  493. sns.pointplot('Condition', 'bl2', hue='Group', err_style='bars', capsize = .1, errwidth=1.5,
  494. data=LC_df_long, palette=['darkred', 'dodgerblue'], dodge=True, order=["Fast", "Moderate", "Slow"], ci=68).set(ylim=(-8, 8))
  495. plt.ylabel("Tonic LC activity mid-condition (beta values)")
  496. plt.tight_layout()
  497. plt.show()
  498. # %% [markdown]
  499. # ## Assessing movement parameters of the fmriprep output
  500. # #### Get average standardised DVARS and Framewise Displacement values per participant
  501. # %%
  502. import os
  503. import pandas as pd
  504. # Set path to the fmriprep output directory
  505. fmriprep_output_path = r'\fmriprep_output'
  506. # Create an empty dataframe to store the results
  507. results_df = pd.DataFrame(columns=['Subject', 'Average_std_dvars', 'SD_std_dvars','Average_framewise_displacement', 'SD_framewise_displacement'])
  508. # Loop through subject folders
  509. for subject_folder in os.listdir(fmriprep_output_path):
  510. subject_path = os.path.join(fmriprep_output_path, subject_folder)
  511. # Check if the item in the directory is a directory
  512. if os.path.isdir(subject_path) and subject_folder.startswith('sub-'):
  513. subject_number = subject_folder.split('-')[1]
  514. # Define the path to the func directory
  515. func_path = os.path.join(subject_path, 'func')
  516. # Loop through files in the func directory
  517. for filename in os.listdir(func_path):
  518. if filename.endswith('_task-tdt_run-1_desc-confounds_timeseries.tsv'):
  519. file_path = os.path.join(func_path, filename)
  520. # Read the TSV file into a DataFrame
  521. df = pd.read_csv(file_path, delimiter='\t')
  522. # Calculate the average and standard deviation for 'std_dvars' and 'framewise_displacement'
  523. avg_std_dvars = df['std_dvars'].mean()
  524. sd_std_dvars = df['std_dvars'].std()
  525. avg_framewise_displacement = df['framewise_displacement'].mean()
  526. sd_framewise_displacement = df['framewise_displacement'].std()
  527. # Append results to the dataframe
  528. results_df = results_df.append({
  529. 'Subject': subject_number,
  530. 'Average_std_dvars': avg_std_dvars,
  531. 'SD_std_dvars': sd_std_dvars,
  532. 'Average_framewise_displacement': avg_framewise_displacement,
  533. 'SD_framewise_displacement': sd_framewise_displacement
  534. }, ignore_index=True)
  535. # Save the results to an output file
  536. 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

  1. Department of Experimental Clinical and Health Psychology, Ghent University, Ghent, Belgium
  2. University Psychiatric Center KU Leuven, Kortenberg, Belgium
  3. Department of Health, Medical, and Neuropsychology, Leiden University, Leiden, The Netherlands
  4. Liverpool John Moores University, Liverpool, United Kingdom
  5. Department of Experimental Psychology, Ghent University, Ghent, Belgium
Institutions: Ghent University (Belgium); KU Leuven (Belgium); Leiden University (Netherlands); Liverpool John Moores University (United Kingdom)
Journal: Imaging neuroscience (Cambridge, Mass.), volume 4, article IMAG.a.1200
Dates: received 27 March 2025; accepted 16 March 2026; published online 13 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1162/imag.a.1200 · PMID 41993143 · PMCID PMC13081741 · OpenAlex W7138886814
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), ADHD (population)
Methods: Statistics, Preprocessing, fMRI & imaging, Physiology & signal measures
Keywords: ADHD, fMRI, state regulation, Locus Coeruleus, event rate
Topic: Attention Deficit Hyperactivity Disorder (Psychiatry and Mental health, Medicine), according to OpenAlex
Funding: Fonds Wetenschappelijk Onderzoek (G057218N); Vlaamse regering
Citations: not cited yet (Europe PMC); 98 references in the paper

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

License: CC-BY-4.0
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 6b9cf4aebb1eec9da54c9f5a7b1a7786a0535a68, 12 January 2026
Languages: Jupyter (1)
Size: 5 files, 1 script
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README, license file, 1 notebook
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (1 file), NumPy (1 file), pandas (1 file), SciPy (1 file), seaborn (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
3 files

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://github.com/l-drescher/codes_fMRI_LC_ADHD.

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://doi.org/10.1162/imag.a.1200

BibTeX

@article{drescher2026fmri,
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/imag.a.1200},
url = {https://doi.org/10.1162/imag.a.1200},
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/04/13
VL - 4
SP - IMAG.a.1200
SN - 2837-6056
PB - MIT Press
DO - 10.1162/imag.a.1200
UR - https://doi.org/10.1162/imag.a.1200
LA - en
ER -

CSL-JSON

{
"id": "10.1162/imag.a.1200",
"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": "Imaging Neurosci (Camb)",
"volume": "4",
"page": "IMAG.a.1200",
"DOI": "10.1162/imag.a.1200",
"PMID": "41993143",
"PMCID": "PMC13081741",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://doi.org/10.1162/imag.a.1200",
"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 one
In 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 reports
In 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 neuroscience
In common: SciPy, NumPy, 4 references
[4] doi:10.1126/sciadv.adz6495
Pupil-linked arousal heterogeneously modulates cell-type-specific sensory processing.
Journal: Science advances
In 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 mapping
In 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 psychiatry
In 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 communications
In 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.

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.