OSCR

Neurovascular effects of tadalafil in patients with cerebral small vessel disease: ETLAS-2 substudy.

Code ↔ Paper

6 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 6 matches
  1. [1] § Materials and methods › MRI analysis › Dual-echo pCASL MRI ↔ dual_echo_pcasl_ETLAS2_scripts.py, lines 625–714 · score 0.79 · design matrix, hypercapnia response, visual stimulation, convolved, Gamma, cosine
  2. [2] § Materials and methods › MRI ↔ dp_pcasl_tools.py, lines 213–232 · score 0.68 · diffusion weighted, arterial transit, DP pCASL, attenuated, exchange
  3. [3] § Materials and methods › MRI analysis › BOLD fMRI ↔ dual_echo_pcasl_ETLAS2_scripts.py, lines 508–623 · score 0.67 · fMRI, design matrix, motion corrected, FWHM, HRF, smoothed
  4. [4] § Materials and methods › MRI analysis › Dual-echo pCASL MRI ↔ dual_echo_pcasl_ETLAS2_scripts.py, lines 438–475 · score 0.64 · partition coefficient, efficiency, relaxation, slice, delay, post
  5. [5] § Materials and methods › Dual-echo pCASL MRI ↔ dual_echo_pcasl_ETLAS2_scripts.py, lines 508–623 · score 0.60 · dual echo pCASL, Visual stimulation, CO2, repetition, matrix, duration
  6. [6] § Materials and methods › Statistical analysis ↔ dp_pcasl_tools.py, lines 30–74 · score 0.55 · GLM fitting, DP pCASL, Tools, pipelining

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

Python · 714 lines · 35 KB · CC-BY-4.0 · 4 matches

  1. """Dual-echo pCASL processing scripts for ETLAS2 dataset.
  2. This module contains core processing pipelines used in the ETLAS2 paper:
  3. Neurovascular effects of tadalafil in patients with
  4. cerebral small vessel disease: ETLAS-2 substudy
  5. - motion correction and template registration for dual-echo pCASL (echo 1 + echo 2)
  6. - CBF scaling image generation
  7. - first-level GLM fitting for calibrated fMRI with hypercapnia/visual stimuli
  8. The functions expect BIDS-like subject/session folder structure for input data.
  9. """
  10. import ants
  11. import pandas as pd
  12. import antspynet
  13. import numpy as np
  14. import json
  15. import os
  16. import nilearn.glm as glm
  17. import nibabel as nib
  18. from scipy.stats import gamma
  19. import shutil
  20. def motion_correction_to_template(subject, rawdata_folder, dual_echo_pcasl_folder, transformations_folder, template_fn):
  21. """Run rigid registration of pCASL/M0 to T1w and MNI template.
  22. This function performs motion correction on dual-echo pCASL data using control
  23. volumes, registers the corrected series to anatomical T1w and template space,
  24. and applies the full transform chain to both echo 1 and echo 2 volumes.
  25. Scaling images for CBF quantification are also computed and warped to template.
  26. Optionally processes follow-up session data with longitudinal T1 alignment.
  27. Parameters:
  28. - subject: subject label (e.g. sub-001)
  29. - rawdata_folder: base input folder with BIDS-like structure
  30. - dual_echo_pcasl_folder: output folder for processed pCASL images
  31. - transformations_folder: output folder for transform matrices
  32. - template_fn: full path to target template image (e.g. MNI)
  33. """
  34. # ------------------------------------------------------------------
  35. # Load input images from baseline session (T1w, pCASL echo1/echo2, M0 scans)
  36. # - T1 is anatomical reference
  37. # - pcasl_e1/e2 are perfusion series requiring motion correction
  38. # - m0_e1/e2 are baseline M0 acquisitions; averaged across time for scaling
  39. # ------------------------------------------------------------------
  40. t1 = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-baseline', 'anat', f'{subject}_ses-baseline_T1w.nii.gz'))
  41. pcasl_e1 = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-baseline', 'perf', f'{subject}_ses-baseline_echo-1_asl.nii.gz'))
  42. pcasl_e2 = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-baseline', 'perf', f'{subject}_ses-baseline_echo-2_asl.nii.gz'))
  43. # M0 average across all M0 timepoints for each echo.
  44. m0_e1 = ants.get_average_of_timeseries(ants.image_read(os.path.join(rawdata_folder, subject, 'ses-baseline', 'perf', f'{subject}_ses-baseline_echo-1_m0scan.nii.gz')))
  45. m0_e2 = ants.get_average_of_timeseries(ants.image_read(os.path.join(rawdata_folder, subject, 'ses-baseline', 'perf', f'{subject}_ses-baseline_echo-2_m0scan.nii.gz')))
  46. template = ants.image_read(template_fn)
  47. # ------------------------------------------------------------------
  48. # Perform motion correction using control volumes only (avoid label-control contrast drift).
  49. # 1) select even frames (control) from echo1
  50. # 2) rigid motion correct to a midpoint reference
  51. # 3) get motion parameters (FD) for later QC/o output
  52. # ------------------------------------------------------------------
  53. pcasl_e1_con = ants.list_to_ndimage(pcasl_e1, ants.ndimage_to_list(pcasl_e1)[0::2])
  54. pcasl_e1_con_registration_init = ants.motion_correction(pcasl_e1_con, aff_metric='meansquares')
  55. pcasl_e1_con_registration = ants.motion_correction(
  56. pcasl_e1_con,
  57. fixed=ants.get_average_of_timeseries(pcasl_e1_con_registration_init['motion_corrected']),
  58. aff_metric='meansquares'
  59. )
  60. # Reference for subsequent registration is average motion-corrected control series.
  61. reference = ants.get_average_of_timeseries(pcasl_e1_con_registration['motion_corrected'])
  62. # Brain extraction for bias correction. This mask is used to avoid background influence.
  63. reference_mask = antspynet.utilities.brain_extraction(reference, modality='bold')
  64. ref_tmp = ants.image_clone(reference)
  65. ref_tmp[reference_mask < 0.1] = np.nan
  66. # N4 bias correction to reduce intensity non-uniformity before registration.
  67. reference_n4 = ants.n4_bias_field_correction(reference / np.nanmean(ref_tmp.numpy(), axis=(0,1)), mask=reference_mask > 0.1)
  68. # Register to anatomical T1 and M0 to reference
  69. pcasl_to_t1 = ants.registration(
  70. t1,
  71. reference_n4,
  72. type_of_transform='Rigid',
  73. aff_metric='mattes',
  74. initial_transform='Identity',
  75. aff_iterations=(200),
  76. aff_shrink_factors=(1),
  77. aff_smoothing_sigmas=(0)
  78. )
  79. pcasl_xfms = pcasl_e1_con_registration['motion_parameters']
  80. m0_to_pcasl = ants.registration(
  81. reference_n4,
  82. m0_e1,
  83. type_of_transform='Rigid',
  84. aff_metric='mattes',
  85. initial_transform='Identity',
  86. aff_iterations=(200),
  87. aff_shrink_factors=(1),
  88. aff_smoothing_sigmas=(0)
  89. )
  90. # Prepare a reference 4D image in template field-of-view for resampling.
  91. # We copy orientation from template while maintaining ASL time dimension shape.
  92. direction = pcasl_e1.direction.copy()
  93. direction[0:3,0:3] = template.direction
  94. reference_image = ants.from_numpy(
  95. np.zeros(template.shape + (pcasl_e1.shape[-1],)),
  96. origin=template.origin + (pcasl_e1.origin[-1],),
  97. spacing=template.spacing + (pcasl_e1.spacing[-1],),
  98. direction=direction
  99. )
  100. # The T1w-to-MNI transform is external (SAMSEG / preprocessing output).
  101. t1_to_template_xfm_fn = os.path.join(transformations_folder, subject, f'{subject}_ses-baseline_T1w-to-MNI.mat')
  102. # Apply chain of transforms to each pCASL timepoint:
  103. # 1) subject T1 -> template (inverse because we are moving data to template)
  104. # 2) pCASL->T1 from registration
  105. # 3) volume-level motion transform per volume (from motion correction)
  106. # This is done for both echos.
  107. pcasl_e1_mc = ants.list_to_ndimage(reference_image, [
  108. ants.apply_transforms(template,
  109. ants.ndimage_to_list(pcasl_e1)[i],
  110. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], pcasl_xfms[int(i/2)][0]],
  111. whichtoinvert=[True, False, False],
  112. interpolator='linear')
  113. for i in range(len(ants.ndimage_to_list(pcasl_e1)))
  114. ])
  115. pcasl_e2_mc = ants.list_to_ndimage(reference_image, [
  116. ants.apply_transforms(template,
  117. ants.ndimage_to_list(pcasl_e2)[i],
  118. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], pcasl_xfms[int(i/2)][0]],
  119. whichtoinvert=[True, False, False],
  120. interpolator='linear')
  121. for i in range(len(ants.ndimage_to_list(pcasl_e2)))])
  122. # Calculate ASL scaling from M0 (mgr to perfusion units).
  123. # This is needed later to convert posted ASL signal to quantitative CBF.
  124. scaling_e1 = calculate_cbf_scaling(m0_e1, os.path.join(rawdata_folder, subject, 'ses-baseline', 'perf', f'{subject}_ses-baseline_echo-1_asl.json'))
  125. scaling_e2 = calculate_cbf_scaling(m0_e2, os.path.join(rawdata_folder, subject, 'ses-baseline', 'perf', f'{subject}_ses-baseline_echo-1_asl.json'))
  126. # Warp M0 volumes into template space for consistent quantification.
  127. m0_e1_mc = ants.apply_transforms(
  128. template,
  129. m0_e1,
  130. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  131. whichtoinvert=[True, False, False],
  132. interpolator='linear'
  133. )
  134. m0_e2_mc = ants.apply_transforms(
  135. template,
  136. m0_e2,
  137. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  138. whichtoinvert=[True, False, False],
  139. interpolator='linear'
  140. )
  141. # Upsample/dilate mask areas to reduce edge artifacts in scaling division.
  142. # This is an ExploreASL-inspired heuristic with erode+dilate combination.
  143. m0_mask = antspynet.utilities.brain_extraction(m0_e2, modality='bold')
  144. m0_mask_eroded = ants.morphology(m0_mask, 'erode', 1, mtype='grayscale')
  145. m0_mask_dilated = ants.morphology(m0_mask, 'dilate', 1, mtype='grayscale')
  146. scaling_e1_dilated = (
  147. scaling_e1 * ants.smooth_image(m0_mask_eroded, 1)
  148. + ants.morphology(scaling_e1 * (m0_mask > 0.999), 'dilate', 1, mtype='grayscale')
  149. * ants.smooth_image(m0_mask_dilated - m0_mask_eroded, 1)
  150. )
  151. scaling_e2_dilated = (
  152. scaling_e2 * ants.smooth_image(m0_mask_eroded, 1)
  153. + ants.morphology(scaling_e2 * (m0_mask > 0.999), 'dilate', 1, mtype='grayscale')
  154. * ants.smooth_image(m0_mask_dilated - m0_mask_eroded, 1)
  155. )
  156. # Reproject dilated scaling images to template space.
  157. scaling_e1_mc = ants.apply_transforms(
  158. template,
  159. scaling_e1_dilated,
  160. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  161. whichtoinvert=[True, False, False],
  162. interpolator='linear'
  163. )
  164. scaling_e2_mc = ants.apply_transforms(
  165. template,
  166. scaling_e2_dilated,
  167. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  168. whichtoinvert=[True, False, False],
  169. interpolator='linear'
  170. )
  171. # Warp a brain mask to template space and binarize after interp.
  172. m0_mask_mc = (
  173. ants.apply_transforms(
  174. template,
  175. m0_mask,
  176. [t1_to_template_xfm_fn, pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  177. whichtoinvert=[True, False, False],
  178. interpolator='genericLabel'
  179. ) > 0.999
  180. )
  181. # Save transform files for later QC and reuse.
  182. # - asl-to-T1 uses pcasl_to_t1 full forward transform
  183. # - m0-to-asl uses m0_to_pcasl transform
  184. # - per-volume motion transforms are stored in asl_mc/
  185. os.makedirs(os.path.join(transformations_folder, subject), exist_ok=True)
  186. os.makedirs(os.path.join(transformations_folder, subject, 'asl_mc'), exist_ok=True)
  187. shutil.copyfile(pcasl_to_t1['fwdtransforms'][0], os.path.join(transformations_folder, subject, f'{subject}_ses-baseline_asl-to-T1w.mat'))
  188. shutil.copyfile(m0_to_pcasl['fwdtransforms'][0], os.path.join(transformations_folder, subject, f'{subject}_ses-baseline_m0scan-to-asl.mat'))
  189. for i, xfm in enumerate(pcasl_xfms):
  190. shutil.copyfile(xfm[0], os.path.join(transformations_folder, subject, 'asl_mc', f'{subject}_ses-baseline_asl-moco_{i:03}.mat'))
  191. # Ensure output directory exists and store final processed images.
  192. out_basename = os.path.join(dual_echo_pcasl_folder, subject, 'ses-baseline', f'{subject}_ses-baseline')
  193. os.makedirs(os.path.join(dual_echo_pcasl_folder, subject, 'ses-baseline'), exist_ok=True)
  194. scaling_e1.to_filename(out_basename + '_echo-1_scaling.nii.gz')
  195. scaling_e1_dilated.to_filename(out_basename + '_echo-1_scaling_dilated.nii.gz')
  196. scaling_e1_mc.to_filename(out_basename + '_rec-moco_echo-1_scaling.nii.gz')
  197. scaling_e2.to_filename(out_basename + '_echo-2_scaling.nii.gz')
  198. scaling_e2_dilated.to_filename(out_basename + '_echo-2_scaling_dilated.nii.gz')
  199. scaling_e2_mc.to_filename(out_basename + '_rec-moco_echo-2_scaling.nii.gz')
  200. # Save output volumes from baseline run
  201. pcasl_e1_mc.to_filename(out_basename + '_rec-moco_echo-1_asl.nii.gz')
  202. pcasl_e2_mc.to_filename(out_basename + '_rec-moco_echo-2_asl.nii.gz')
  203. m0_e1_mc.to_filename(out_basename + '_rec-moco_echo-1_m0scan.nii.gz')
  204. m0_e2_mc.to_filename(out_basename + '_rec-moco_echo-2_m0scan.nii.gz')
  205. m0_mask_mc.to_filename(out_basename + '_rec-moco_brainmask.nii.gz')
  206. # Save framewise displacement (repeated for label/control alternation) for QC.
  207. motion = np.zeros(len(pcasl_e1_con_registration['FD']) * 2)
  208. motion[0::2] = pcasl_e1_con_registration['FD']
  209. motion[1::2] = pcasl_e1_con_registration['FD']
  210. np.savetxt(out_basename + '_rec-moco_framewisedisplacement.txt', motion)
  211. # Optional follow-up session processing. If present, process follow-up data similarly to baseline.
  212. # Follow-up pipeline replicates motion correction + template registration chain,
  213. # then adds follow-up->baseline T1 alignment for longitudinal consistency.
  214. dual_session = os.path.exists(os.path.join(rawdata_folder, subject, 'ses-followup', 'perf'))
  215. if dual_session:
  216. # Load baseline and follow-up anatomical volumes.
  217. t1_baseline = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-baseline', 'anat', f'{subject}_ses-baseline_T1w.nii.gz'))
  218. t1 = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-followup', 'anat', f'{subject}_ses-followup_T1w.nii.gz'))
  219. # Load follow-up pCASL echo series and M0 scans.
  220. pcasl_e1 = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-followup', 'perf', f'{subject}_ses-followup_echo-1_asl.nii.gz'))
  221. pcasl_e2 = ants.image_read(os.path.join(rawdata_folder, subject, 'ses-followup', 'perf', f'{subject}_ses-followup_echo-2_asl.nii.gz'))
  222. m0_e1 = ants.get_average_of_timeseries(ants.image_read(os.path.join(rawdata_folder, subject, 'ses-followup', 'perf', f'{subject}_ses-followup_echo-1_m0scan.nii.gz')))
  223. m0_e2 = ants.get_average_of_timeseries(ants.image_read(os.path.join(rawdata_folder, subject, 'ses-followup', 'perf', f'{subject}_ses-followup_echo-2_m0scan.nii.gz')))
  224. # Motion correction on follow-up echo1 control frames.
  225. pcasl_e1_con = ants.list_to_ndimage(pcasl_e1, ants.ndimage_to_list(pcasl_e1)[0::2])
  226. pcasl_e1_con_registration_init = ants.motion_correction(pcasl_e1_con, aff_metric='meansquares')
  227. pcasl_e1_con_registration = ants.motion_correction(
  228. pcasl_e1_con,
  229. fixed=ants.get_average_of_timeseries(pcasl_e1_con_registration_init['motion_corrected']),
  230. aff_metric='meansquares'
  231. )
  232. # Build follow-up reference image and bias correction mask.
  233. reference = ants.get_average_of_timeseries(pcasl_e1_con_registration['motion_corrected'])
  234. reference_mask = antspynet.utilities.brain_extraction(reference, modality='bold')
  235. ref_tmp = ants.image_clone(reference)
  236. ref_tmp[reference_mask < 0.1] = np.nan
  237. reference_n4 = ants.n4_bias_field_correction(reference / np.nanmean(ref_tmp.numpy(), axis=(0,1)), mask=reference_mask > 0.1)
  238. # Register follow-up reference to follow-up T1 and M0 to reference.
  239. pcasl_to_t1 = ants.registration(
  240. t1,
  241. reference_n4,
  242. type_of_transform='Rigid',
  243. aff_metric='mattes',
  244. initial_transform='Identity',
  245. aff_iterations=(200),
  246. aff_shrink_factors=(1),
  247. aff_smoothing_sigmas=(0)
  248. )
  249. pcasl_xfms = pcasl_e1_con_registration['motion_parameters']
  250. m0_to_pcasl = ants.registration(
  251. reference_n4,
  252. m0_e1,
  253. type_of_transform='Rigid',
  254. aff_metric='mattes',
  255. initial_transform='Identity',
  256. aff_iterations=(200),
  257. aff_shrink_factors=(1),
  258. aff_smoothing_sigmas=(0)
  259. )
  260. # Register follow-up T1 to baseline T1, enabling longitudinal transform chain.
  261. t1_followup_to_baseline = ants.registration(
  262. t1_baseline,
  263. t1,
  264. type_of_transform='Rigid',
  265. aff_metric='mattes',
  266. initial_transform='Identity',
  267. aff_iterations=(200),
  268. aff_shrink_factors=(1),
  269. aff_smoothing_sigmas=(0)
  270. )
  271. # Prepare workspace for follow-up pCASL resampling in template space.
  272. direction = pcasl_e1.direction.copy()
  273. direction[0:3,0:3] = template.direction
  274. reference_image = ants.from_numpy(
  275. np.zeros(template.shape + (pcasl_e1.shape[-1],)),
  276. origin=template.origin + (pcasl_e1.origin[-1],),
  277. spacing=template.spacing + (pcasl_e1.spacing[-1],),
  278. direction=direction
  279. )
  280. # Apply full transform chain to follow-up echo1 and echo2 volumes:
  281. # template <- (T1_baseline<-T1_followup <-- pcasl_t1 <-- pcasl_vol_moco).
  282. pcasl_e1_mc = ants.list_to_ndimage(reference_image, [
  283. ants.apply_transforms(
  284. template,
  285. ants.ndimage_to_list(pcasl_e1)[i],
  286. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], pcasl_xfms[int(i/2)][0]],
  287. whichtoinvert=[True, False, False, False],
  288. interpolator='linear'
  289. )
  290. for i in range(len(ants.ndimage_to_list(pcasl_e1)))
  291. ])
  292. pcasl_e2_mc = ants.list_to_ndimage(reference_image, [
  293. ants.apply_transforms(
  294. template,
  295. ants.ndimage_to_list(pcasl_e2)[i],
  296. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], pcasl_xfms[int(i/2)][0]],
  297. whichtoinvert=[True, False, False, False],
  298. interpolator='linear'
  299. )
  300. for i in range(len(ants.ndimage_to_list(pcasl_e2)))
  301. ])
  302. # Recompute scaling for follow-up M0 images and warp to template.
  303. scaling_e1 = calculate_cbf_scaling(m0_e1, os.path.join(rawdata_folder, subject, 'ses-followup', 'perf', f'{subject}_ses-followup_echo-1_asl.json'))
  304. scaling_e2 = calculate_cbf_scaling(m0_e2, os.path.join(rawdata_folder, subject, 'ses-followup', 'perf', f'{subject}_ses-followup_echo-1_asl.json'))
  305. m0_e1_mc = ants.apply_transforms(
  306. template,
  307. m0_e1,
  308. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  309. interpolator='linear',
  310. whichtoinvert=[True, False, False, False]
  311. )
  312. m0_e2_mc = ants.apply_transforms(
  313. template,
  314. m0_e2,
  315. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  316. interpolator='linear',
  317. whichtoinvert=[True, False, False, False]
  318. )
  319. # Dilate follow-up scale masks and intensity maps in the same manner as baseline.
  320. m0_mask = antspynet.utilities.brain_extraction(m0_e2, modality='bold')
  321. m0_mask_eroded = ants.morphology(m0_mask, 'erode', 1, mtype='grayscale')
  322. m0_mask_dilated = ants.morphology(m0_mask, 'dilate', 1, mtype='grayscale')
  323. scaling_e1_dilated = (
  324. scaling_e1 * ants.smooth_image(m0_mask_eroded, 1)
  325. + ants.morphology(scaling_e1 * (m0_mask > 0.999), 'dilate', 1, mtype='grayscale')
  326. * ants.smooth_image(m0_mask_dilated - m0_mask_eroded, 1)
  327. )
  328. scaling_e2_dilated = (
  329. scaling_e2 * ants.smooth_image(m0_mask_eroded, 1)
  330. + ants.morphology(scaling_e2 * (m0_mask > 0.999), 'dilate', 1, mtype='grayscale')
  331. * ants.smooth_image(m0_mask_dilated - m0_mask_eroded, 1)
  332. )
  333. scaling_e1_mc = ants.apply_transforms(
  334. template,
  335. scaling_e1_dilated,
  336. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  337. whichtoinvert=[True, False, False, False],
  338. interpolator='linear'
  339. )
  340. scaling_e2_mc = ants.apply_transforms(
  341. template,
  342. scaling_e2_dilated,
  343. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  344. whichtoinvert=[True, False, False, False],
  345. interpolator='linear'
  346. )
  347. m0_mask_mc = (
  348. ants.apply_transforms(
  349. template,
  350. m0_mask,
  351. [t1_to_template_xfm_fn, t1_followup_to_baseline['fwdtransforms'][0], pcasl_to_t1['fwdtransforms'][0], m0_to_pcasl['fwdtransforms'][0]],
  352. interpolator='genericLabel',
  353. whichtoinvert=[True, False, False, False]
  354. ) > 0.999
  355. )
  356. # Save follow-up transforms and output files (same patterns as baseline)
  357. os.makedirs(os.path.join(transformations_folder, subject), exist_ok=True)
  358. os.makedirs(os.path.join(transformations_folder, subject, 'asl_mc'), exist_ok=True)
  359. shutil.copyfile(t1_followup_to_baseline['fwdtransforms'][0], os.path.join(transformations_folder, subject, f'{subject}_followup-T1w-to-baseline-T1w.mat'))
  360. shutil.copyfile(pcasl_to_t1['fwdtransforms'][0], os.path.join(transformations_folder, subject, f'{subject}_ses-followup_asl-to-T1w.mat'))
  361. shutil.copyfile(m0_to_pcasl['fwdtransforms'][0], os.path.join(transformations_folder, subject, f'{subject}_ses-followup_m0scan-to-asl.mat'))
  362. for i, xfm in enumerate(pcasl_xfms):
  363. shutil.copyfile(xfm[0], os.path.join(transformations_folder, subject, 'asl_mc', f'{subject}_ses-followup_asl-moco_{i:03}.mat'))
  364. out_basename = os.path.join(dual_echo_pcasl_folder, subject, 'ses-followup', f'{subject}_ses-followup')
  365. os.makedirs(os.path.join(dual_echo_pcasl_folder, subject, 'ses-followup'), exist_ok=True)
  366. scaling_e1.to_filename(out_basename + '_echo-1_scaling.nii.gz')
  367. scaling_e1_dilated.to_filename(out_basename + '_echo-1_scaling_dilated.nii.gz')
  368. scaling_e1_mc.to_filename(out_basename + '_rec-moco_echo-1_scaling.nii.gz')
  369. scaling_e2.to_filename(out_basename + '_echo-2_scaling.nii.gz')
  370. scaling_e2_dilated.to_filename(out_basename + '_echo-2_scaling_dilated.nii.gz')
  371. scaling_e2_mc.to_filename(out_basename + '_rec-moco_echo-2_scaling.nii.gz')
  372. pcasl_e1_mc.to_filename(out_basename + '_rec-moco_echo-1_asl.nii.gz')
  373. pcasl_e2_mc.to_filename(out_basename + '_rec-moco_echo-2_asl.nii.gz')
  374. m0_e1_mc.to_filename(out_basename + '_rec-moco_echo-1_m0scan.nii.gz')
  375. m0_e2_mc.to_filename(out_basename + '_rec-moco_echo-2_m0scan.nii.gz')
  376. m0_mask_mc.to_filename(out_basename + '_rec-moco_brainmask.nii.gz')
  377. motion = np.zeros(len(pcasl_e1_con_registration['FD']) * 2)
  378. motion[0::2] = pcasl_e1_con_registration['FD']
  379. motion[1::2] = pcasl_e1_con_registration['FD']
  380. np.savetxt(out_basename + '_rec-moco_framewisedisplacement.txt', motion)
  381. def calculate_cbf_scaling(m0, sidecar_fn, blood_t1=1.65, lambda_full=0.9):
  382. """Compute voxel-wise pCASL scaling from M0 image and BIDS sidecar metadata.
  383. This function uses the general kinetic model from Alsop et al., MRM 2000
  384. (DOI: 10.1002/mrm.10559), applying a single-bolus correction and
  385. optional slice-timing support.
  386. Parameters:
  387. - m0: M0 image (anatomical reference)
  388. - sidecar_fn: path to BIDS JSON sidecar with pCASL acquisition parameters
  389. - blood_t1: T1 relaxation time of blood (seconds)
  390. - lambda_full: brain-blood partition coefficient
  391. Returns:
  392. - scaling: voxel-wise scaling image in units ml/100g/min (same geometry as m0 input)
  393. """
  394. sidecar = json.load(open(sidecar_fn))
  395. # Extract pCASL parameters from sidecar metadata.
  396. inversion_eff = sidecar.get('LabelingEfficiency', 0.95)
  397. bolus_duration = sidecar.get('LabelingDuration', 1.65)
  398. postlabeling_delay = sidecar.get('PostLabelingDelay', 2.0)
  399. slice_time = sidecar.get('SliceTiming', np.zeros(m0.shape[2]))
  400. # Convert slice timing to seconds if needed and add post-labeling delay.
  401. slice_time = np.array(slice_time, dtype=float)
  402. if slice_time.max() > 4:
  403. slice_time *= 0.001
  404. slice_time += postlabeling_delay
  405. # Apply kinetic model (Eq. 1 in Alsop et al., MRM 2000) to compute scaling.
  406. # Output is in ml/100g/min.
  407. scaling = m0 * 2.0 * inversion_eff * blood_t1 * (1.0 / lambda_full) * (
  408. np.exp(-slice_time / blood_t1) - np.exp(-(bolus_duration + slice_time) / blood_t1)
  409. ) / 6000.0
  410. return scaling
  411. def hypercapnia_response_function(tr, oversampling=16, onset=0.0, time_length=240):
  412. """Generate a canonical hypercapnia response function using a gamma distribution.
  413. Parameters:
  414. - tr: repetition time (seconds)
  415. - oversampling: factor for temporal upsampling of the response function
  416. - onset: delay before response begins (seconds)
  417. - time_length: total duration of response function (seconds)
  418. Returns:
  419. - response_function: normalized gamma PDF used as HRF for hypercapnia regressors
  420. """
  421. # Calculate temporal resolution after oversampling
  422. dt = tr / oversampling
  423. # Create time vector with oversampled resolution, then shift by onset delay
  424. time_stamps = np.linspace(0, time_length, np.rint(time_length / dt).astype(int))
  425. time_stamps -= onset
  426. # Gamma function parameters estimated from hypercapnia data
  427. # delay: shape parameter (governs rise time)
  428. # dispersion: scale parameter (governs width/duration of response)
  429. delay = 1.55
  430. dispersion = 33
  431. # Compute gamma probability density function and normalize to sum to 1
  432. response_function = gamma.pdf(time_stamps, delay, scale=dispersion, loc=0)
  433. response_function /= response_function.sum()
  434. return response_function
  435. def fit_calibrated_fmri_glm(subject, session, rawdata_folder, dual_echo_pcasl_folder):
  436. """Fit calibrated fMRI GLM for one subject/session using echo 1/2 outputs.
  437. This function uses pre-processed, motion-corrected pCASL images and session
  438. event timings to estimate task- and hypercapnia-related responses.
  439. """
  440. # ------------------------------------------------------------------
  441. # File I/O and initial metadata loading
  442. # ------------------------------------------------------------------
  443. output_folder = os.path.join(dual_echo_pcasl_folder, subject, session, 'glm_results')
  444. os.makedirs(output_folder, exist_ok=True)
  445. sidecar = json.load(open(os.path.join(rawdata_folder, subject, session, 'perf', f'{subject}_{session}_echo-1_asl.json')))
  446. pcasl_e1 = nib.load(os.path.join(dual_echo_pcasl_folder, subject, session, f'{subject}_{session}_rec-moco_echo-1_asl.nii.gz'))
  447. pcasl_e2 = nib.load(os.path.join(dual_echo_pcasl_folder, subject, session, f'{subject}_{session}_rec-moco_echo-2_asl.nii.gz'))
  448. events = pd.read_table(os.path.join(rawdata_folder, subject, session, 'perf', f'{subject}_{session}_echo-1_events.tsv'))
  449. context = pd.read_csv(os.path.join(rawdata_folder, subject, session, 'perf', f'{subject}_{session}_echo-1_aslcontext.tsv'), sep='\t')
  450. motion = np.loadtxt(os.path.join(dual_echo_pcasl_folder, subject, session, f'{subject}_{session}_rec-moco_framewisedisplacement.txt'))
  451. # ------------------------------------------------------------------
  452. # Ensure t_r is in seconds and construct frame times for design matrix.
  453. # ------------------------------------------------------------------
  454. t_r = sidecar['RepetitionTime']
  455. if t_r > 1000:
  456. t_r /= 1000.0
  457. n_frames = sidecar['TotalAcquiredPairs'] * 2
  458. frame_times = np.arange(n_frames) * t_r
  459. # ------------------------------------------------------------------
  460. # Construct first-level GLM design matrix for main task and hypercapnia.
  461. # ------------------------------------------------------------------
  462. X1 = glm.first_level.make_first_level_design_matrix(
  463. frame_times,
  464. events=events[events['trial_type'] != 'Hypercapnia, 5% CO2'],
  465. drift_model='cosine',
  466. high_pass=0.001,
  467. add_regs=pd.DataFrame(motion, columns=['FD']),
  468. hrf_model='glover'
  469. )
  470. # In pCASL, label/control toggles; encode as +1 / -1 for ASL difference.
  471. X1['ASL'] = np.array((context == 'label') * -1)
  472. # Hypercapnia event regressor, using a custom response function.
  473. onset = events[events['trial_type'] == 'Hypercapnia, 5% CO2']['onset'].values
  474. duration = events[events['trial_type'] == 'Hypercapnia, 5% CO2']['duration'].values
  475. amplitude = np.ones_like(onset)
  476. exp_condition = np.vstack((onset, duration, amplitude))
  477. # Custom hypercapnia response function estimated from the patient group data.
  478. HC_reg = glm.first_level.compute_regressor(
  479. exp_condition,
  480. hypercapnia_response_function,
  481. frame_times
  482. )
  483. X1['Hypercapnia'] = HC_reg[0]
  484. # pCASL contrast terms for interactions with label/control pattern.
  485. X1['ASL Hypercapnia'] = X1['Hypercapnia'] * X1['ASL']
  486. X1['ASL visual stimulation'] = X1['visual stimulation'] * X1['ASL']
  487. X1['ASL visual stimulation, hypercapnia'] = X1['visual stimulation, hypercapnia'] * X1['ASL']
  488. X1.to_csv(os.path.join(output_folder, f'design_matrix.csv'))
  489. # Set up GLM
  490. pcasl_glm = glm.first_level.FirstLevelModel(
  491. noise_model='ar1',
  492. standardize=False,
  493. minimize_memory=False,
  494. signal_scaling=False,
  495. mask_img=False,
  496. n_jobs=-1,
  497. smoothing_fwhm=None
  498. )
  499. # Fit GLM to echo-1 data
  500. glm_e1_results = pcasl_glm.fit(pcasl_e1, design_matrices=X1)
  501. # Compute and write all contrasts, echo-1
  502. for contrast in glm_e1_results.design_matrices_[0].keys():
  503. results = glm_e1_results.compute_contrast(contrast, output_type='all')
  504. for k in results.keys():
  505. results[k].to_filename(os.path.join(output_folder, f'glm_e1_{contrast.replace(" ", "_").replace(",", "")}_{k}.nii.gz'))
  506. # Read nuisance regressors, echo-1
  507. drift1_e1 = ants.image_read(os.path.join(output_folder, "glm_e1_drift_1_effect_size.nii.gz"))
  508. drift2_e1 = ants.image_read(os.path.join(output_folder, "glm_e1_drift_2_effect_size.nii.gz"))
  509. FD_e1 = ants.image_read(os.path.join(output_folder, "glm_e1_FD_effect_size.nii.gz"))
  510. # Regress out nuisances, echo-1
  511. pcasl_e1_nn = ants.image_read(os.path.join(dual_echo_pcasl_folder, subject, session, f'{subject}_{session}_rec-moco_echo-1_asl.nii.gz'))
  512. for i in range(pcasl_e1.shape[-1]):
  513. pcasl_e1_nn[:, :, :, i] = pcasl_e1_nn[:,:,:,i] - (drift1_e1 * X1['drift_1'].array[i] + drift2_e1 * X1['drift_2'].array[i] + FD_e1 * X1['FD'].array[i]).numpy()
  514. pcasl_e1_nn.to_filename(os.path.join(output_folder, f'pcasl_e1_nuisance-regressed.nii.gz'))
  515. # Fit GLM to echo-2 data
  516. glm_e2_results = pcasl_glm.fit(pcasl_e2, design_matrices=X1)
  517. # Compute and write all contrasts, echo-2
  518. for contrast in glm_e2_results.design_matrices_[0].keys():
  519. results = glm_e2_results.compute_contrast(contrast, output_type='all')
  520. for k in results.keys():
  521. results[k].to_filename(os.path.join(output_folder, f'glm_e2_{contrast.replace(" ", "_").replace(",", "")}_{k}.nii.gz'))
  522. # Read nuisance regressors, echo-2
  523. drift1_e2 = ants.image_read(os.path.join(output_folder, "glm_e2_drift_1_effect_size.nii.gz"))
  524. drift2_e2 = ants.image_read(os.path.join(output_folder, "glm_e2_drift_2_effect_size.nii.gz"))
  525. FD_e2 = ants.image_read(os.path.join(output_folder, "glm_e2_FD_effect_size.nii.gz"))
  526. # Regress out nuisances, echo-2
  527. pcasl_e2_nn = ants.image_read(os.path.join(dual_echo_pcasl_folder, subject, session, f'{subject}_{session}_rec-moco_echo-2_asl.nii.gz'))
  528. for i in range(pcasl_e1.shape[-1]):
  529. pcasl_e2_nn[:, :, :, i] = pcasl_e2_nn[:, :, :, i] - (drift1_e2 * X1['drift_1'].array[i] + drift2_e2 * X1['drift_2'].array[i] + FD_e2 * X1['FD'].array[i]).numpy()
  530. pcasl_e2_nn.to_filename(os.path.join(output_folder, f'pcasl_e2_nuisance-regressed.nii.gz'))
  531. def get_design(framewise_displacement):
  532. """Construct an experimental design matrix for the dual-echo experiment.
  533. This design is tied to the original scan protocol (640 time points, 4.5s TR,
  534. specific hypercapnia and visual stimulation onsets). pCASL alternates label
  535. and control volumes along the time axis, so we create regressors with doubled
  536. time dimension (640) from underlying event definitions (320 points) and
  537. distributed on every second frame.
  538. """
  539. # Apply framewise displacement from motion correction to both repeated echoes.
  540. # Input framewise_displacement is length 320, output vector length 640: one FD
  541. # entry for each echo volume (even and odd indexes).
  542. fd = np.zeros(640)
  543. fd[0::2] = framewise_displacement
  544. fd[1::2] = framewise_displacement
  545. design = glm.first_level.make_first_level_design_matrix(
  546. np.arange(640) * 2.25, # correct TR value for data series
  547. drift_model='cosine',
  548. high_pass=0.001,
  549. add_regs=pd.DataFrame(fd, columns=['framewise_displacement'])
  550. ).drop('constant', axis=1)
  551. # Define event timing at the 320-sample (non-echo duplicated) level.
  552. time_stamps = np.arange(320) * 4.5
  553. # Hypercapnia stimulus blocks: 64 ON / 64 OFF / 64 ON ...
  554. hc_stim = np.array([0]*64 + [1]*64 + [0]*64 + [1]*64 + [0]*64)
  555. # Visual task blocks: 32s OFF, 8s ON, repeated 5 times.
  556. vis_stim = np.array(([0]*32 + [1]*8 + [0]*8 + [1]*8 + [0]*8) * 5)
  557. # Data-driven kernels for ASL/BOLD responses, based on earlier model estimates.
  558. vis_hrf_asl = [0, 0.7210, 0.2563, 0.0216, 0.0010]
  559. vis_hrf_bold = [0, 0.8545, 0.2876, -0.0532, -0.0643, -0.0206, -0.0035, -0.0004]
  560. # Use a gamma function for the hypercapnia response (same model as hypercapnia_response_function)
  561. hc_hrf = gamma.pdf(time_stamps, 1.55, scale=33, loc=0)
  562. hc_hrf /= hc_hrf.sum()
  563. # Convolve condition time-series with HRF and split into echo 1/echo 2 (
  564. # control-label alternation) rows.
  565. hc_resp = np.zeros(640)
  566. hc_resp[0::2] = np.convolve(hc_stim, hc_hrf)[0:320]
  567. hc_resp[1::2] = np.convolve(hc_stim, hc_hrf)[0:320]
  568. vis_resp_nc_asl = np.zeros(640)
  569. vis_resp_nc_asl[0::2] = np.convolve(vis_stim * (1 - hc_stim), vis_hrf_asl)[0:320]
  570. vis_resp_nc_asl[1::2] = np.convolve(vis_stim * (1 - hc_stim), vis_hrf_asl)[0:320]
  571. vis_resp_hc_asl = np.zeros(640)
  572. vis_resp_hc_asl[0::2] = np.convolve(vis_stim * hc_stim, vis_hrf_asl)[0:320]
  573. vis_resp_hc_asl[1::2] = np.convolve(vis_stim * hc_stim, vis_hrf_asl)[0:320]
  574. vis_resp_nc_bold = np.zeros(640)
  575. vis_resp_nc_bold[0::2] = np.convolve(vis_stim * (1 - hc_stim), vis_hrf_bold)[0:320]
  576. vis_resp_nc_bold[1::2] = np.convolve(vis_stim * (1 - hc_stim), vis_hrf_bold)[0:320]
  577. vis_resp_hc_bold = np.zeros(640)
  578. vis_resp_hc_bold[0::2] = np.convolve(vis_stim * hc_stim, vis_hrf_bold)[0:320]
  579. vis_resp_hc_bold[1::2] = np.convolve(vis_stim * hc_stim, vis_hrf_bold)[0:320]
  580. # Multiplicative label/control regressors for echo-specific contrast (ASL-style).
  581. c_e1 = [1, 0, 1, 0] * 160
  582. c_e2 = [0, 1, 0, 1] * 160
  583. l_e1 = [0, 0, -1, 0] * 160
  584. l_e2 = [0, 0, 0, -1] * 160
  585. design['01_constant_e1'] = c_e1
  586. design['02_constant_e2'] = c_e2
  587. design['03_label_e1'] = l_e1
  588. design['04_label_e2'] = l_e2
  589. design['05_hypercapnia_constant_e1'] = hc_resp * c_e1
  590. design['06_hypercapnia_constant_e2'] = hc_resp * c_e2
  591. design['07_hypercapnia_label_e1'] = hc_resp * l_e1
  592. design['08_hypercapnia_label_e2'] = hc_resp * l_e2
  593. design['09_visualresponse_constant_e1'] = vis_resp_nc_bold * c_e1
  594. design['10_visualresponse_constant_e2'] = vis_resp_nc_bold * c_e2
  595. design['11_visualresponse_label_e1'] = vis_resp_nc_asl * l_e1
  596. design['12_visualresponse_label_e2'] = vis_resp_nc_asl * l_e2
  597. design['13_visualresponse_hypercapnia_constant_e1'] = vis_resp_hc_bold * c_e1
  598. design['14_visualresponse_hypercapnia_constant_e2'] = vis_resp_hc_bold * c_e2
  599. design['15_visualresponse_hypercapnia_label_e1'] = vis_resp_hc_asl * l_e1
  600. design['16_visualresponse_hypercapnia_label_e2'] = vis_resp_hc_asl * l_e2
  601. return design

dual_echo_pcasl_ETLAS2_scripts.py, under CC-BY-4.0 · at the source

Overview

Authors: Joakim Ölmestig1,2,3, Kristian N Mortensen2, Birgitte Fagerlund4,5, Nadia Naveed6, Mette M Nordling6, Hanne Christensen3,7, Helle K Iversen3,8, Mai B Poulsen9, Hartwig R Siebner2,3,7, Christina Kruuse1,3,10
  1. Neurovascular Research Unit, Department of Neurology, Copenhagen University Hospital—Herlev and Gentofte, Herlev 2730, Denmark
  2. Danish Research Centre for Magnetic Resonance, Department of Radiology and Nuclear Medicine, Copenhagen University Hospital—Amager and Hvidovre, Hvidovre 2650, Denmark
  3. Department of Clinical Medicine, University of Copenhagen, Copenhagen 2200, Denmark
  4. Child and Adolescent Mental Health Center, Copenhagen University Hospital, Mental Health Services CPH, Hellerup 2900, Denmark
  5. Department of Psychology, University of Copenhagen, Copenhagen 1353, Denmark
  6. Department of Radiology, Copenhagen University Hospital—Herlev and Gentofte, Herlev 2730, Denmark
  7. Department of Neurology, Copenhagen University Hospital—Bispebjerg and Frederiksberg, Copenhagen 2400, Denmark
  8. Department of Neurology, Copenhagen University Hospital—Rigshospitalet, Glostrup 2600, Denmark
  9. Department of Neurology, Copenhagen University Hospital—North Zealand, Hillerød 3400, Denmark
  10. Department of Brain and Spinal Cord Injury, Neuroscience Centre, Copenhagen University Hospital—Rigshospitalet, Glostrup 2600, Denmark
Institutions: University of Copenhagen (Denmark); Amager Hospital (Denmark); Herlev Hospital (Denmark); Copenhagen University Hospital (Denmark); Gentofte Hospital (Denmark); Mental Health Services (Denmark); Bispebjerg Hospital (Denmark); Rigshospitalet (Denmark)
Journal: Brain communications, volume 8, issue 4, article fcag309
Dates: received 3 December 2025; accepted 22 July 2026; published online 13 August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1093/braincomms/fcag309 · PMID 42643719 · PMCID PMC13504703 · OpenAlex W7202401237
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), fMRI (modality), human (organism), stroke (population), clinical / translational (subfield)
Methods: Statistics, fMRI & imaging
Keywords: cerebral blood flow, cerebrovascular reactivity, neurovascular coupling, pCASL, fMRI
Journal subjects: Clinical Trial
Topic: Acute Ischemic Stroke Management (Epidemiology, Medicine), according to OpenAlex
Funding: Novo Nordisk Foundation—Investigator Initiated Clinical Trials (NNF20OC0063912); Foundation for Research in Neurology; Frimodt-Heineke Foundation; Herlev and Gentofte Hospitals Research Fund
Citations: not cited yet (Europe PMC); 26 references in the paper

Abstract

Cerebral small vessel disease is a major contributor to stroke and cognitive impairment, often associated with reduced cerebral blood flow and cerebrovascular reactivity. In this study, we used MRI to investigate if daily treatment with the phosphodiesterase-5 inhibitor tadalafil over 3 months improved cerebral blood flow and cerebrovascular reactivity in patients with cerebral small vessel disease.

Using a randomized, placebo-controlled, parallel-group design, this MRI sub-study was prospectively performed as part of the Effect of Tadalafil in Lacunar Stroke 2 trial, which included a total of 76 participants, of whom 60 were included in the per-protocol analysis. Patients with cerebral small vessel disease and prior stroke or transient ischaemic attack received either tadalafil (20 mg/day) or placebo for 3 months and completed the study. Neurovascular outcomes were assessed at baseline and at the 3-month follow-up using dual-echo pseudo-continuous arterial spin labelling MRI to evaluate reactivity to hypercapnia and visual stimulation. Endpoints included changes in whole-brain baseline cerebral blood flow and cerebrovascular reactivity, visual stimulation response, and neurovascular coupling index. In addition, we investigated arterial transit time measured using diffusion-prepared pseudo-continuous arterial spin labelling and changes in event-related sensory blood-oxygen-level-dependent responses in the primary somatosensory cortex (S1) after stimulation of the index finger.

In total, 60 participants (median age: 66.5, 14 females) were included in this per-protocol sub-study analysis (28 on tadalafil and 32 on placebo). Forty-two pseudo-continuous arterial spin labelling datasets (20 on tadalafil and 22 on placebo), 53 blood-oxygen-level-dependent functional MRI datasets (26 on tadalafil and 27 on placebo), and 45 arterial transit time datasets (22 on tadalafil and 23 on placebo) were of sufficient quality to be included in the analysis. Baseline whole-brain cerebral blood flow was 25.4 ± 5.1 ml/100 g/min. Compared to placebo, tadalafil significantly increased whole-brain cerebral blood flow with a mean group difference of 3.33 ± 0.97 ml/100 g/min at follow-up (P = 0.001). None of the other MRI-based outcome metrics differed between groups.

Three months of tadalafil treatment significantly enhanced whole-brain cerebral blood flow in patients with cerebral small vessel disease without altering cerebrovascular reactivity, neurovascular coupling, arterial transit time, and event-related blood-oxygen-level-dependent responses. The clinical significance of a tadalafil-induced increase in cerebral blood flow remains to be determined in prospective studies covering longer treatment periods.

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 6 matches between paragraphs and lines of code.

Zenodo 19233779

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Languages: Python (4), MATLAB (1)
Size: 7 files, 5 scripts
Software Heritage: not checked
Found in: “Data availability”
Holds: README, environment (requirements.txt)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: ANTs (2 files), Nilearn (2 files), NumPy (2 files), pandas (2 files), SciPy (2 files), Image Processing Toolbox (1 file), Statistics and Machine Learning Toolbox (1 file), NiBabel (1 file), scikit-learn (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
6 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;
  • 5 scripts, each with its path and the digest of its content;
  • 6 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 availability

The data that support the findings of this study are available from the corresponding author upon reasonable request. Python and MATLAB scripts used for MRI processing and data collection are available at: https://doi.org/10.5281/zenodo.19233779.

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, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 10 authors, 5 keywords, 4 funders, 26 references.

Cite

This paper

Ölmestig, J., Mortensen, K. N., Fagerlund, B., Naveed, N., Nordling, M. M., Christensen, H., Iversen, H. K., Poulsen, M. B., Siebner, H. R., & Kruuse, C. (2026). Neurovascular effects of tadalafil in patients with cerebral small vessel disease: ETLAS-2 substudy. Brain communications, 8(4), fcag309. https://doi.org/10.1093/braincomms/fcag309

BibTeX

@article{olmestig2026neurovascular,
author = {Ölmestig, Joakim and Mortensen, Kristian N and Fagerlund, Birgitte and Naveed, Nadia and Nordling, Mette M and Christensen, Hanne and Iversen, Helle K and Poulsen, Mai B and Siebner, Hartwig R and Kruuse, Christina},
title = {{Neurovascular effects of tadalafil in patients with cerebral small vessel disease: ETLAS-2 substudy}},
journal = {Brain communications},
year = {2026},
month = aug,
volume = {8},
number = {4},
pages = {fcag309},
publisher = {Oxford University Press},
issn = {2632-1297},
doi = {10.1093/braincomms/fcag309},
url = {https://doi.org/10.1093/braincomms/fcag309},
pmid = {42643719},
pmcid = {PMC13504703}
}

RIS

TY - JOUR
AU - Ölmestig, Joakim
AU - Mortensen, Kristian N
AU - Fagerlund, Birgitte
AU - Naveed, Nadia
AU - Nordling, Mette M
AU - Christensen, Hanne
AU - Iversen, Helle K
AU - Poulsen, Mai B
AU - Siebner, Hartwig R
AU - Kruuse, Christina
TI - Neurovascular effects of tadalafil in patients with cerebral small vessel disease: ETLAS-2 substudy
T2 - Brain communications
J2 - Brain Commun
PY - 2026
DA - 2026/08/13
VL - 8
IS - 4
SP - fcag309
SN - 2632-1297
PB - Oxford University Press
DO - 10.1093/braincomms/fcag309
UR - https://doi.org/10.1093/braincomms/fcag309
LA - en
ER -

CSL-JSON

{
"id": "10.1093/braincomms/fcag309",
"type": "article-journal",
"title": "Neurovascular effects of tadalafil in patients with cerebral small vessel disease: ETLAS-2 substudy",
"container-title": "Brain communications",
"author": [
{
"family": "Ölmestig",
"given": "Joakim"
},
{
"family": "Mortensen",
"given": "Kristian N"
},
{
"family": "Fagerlund",
"given": "Birgitte"
},
{
"family": "Naveed",
"given": "Nadia"
},
{
"family": "Nordling",
"given": "Mette M"
},
{
"family": "Christensen",
"given": "Hanne"
},
{
"family": "Iversen",
"given": "Helle K"
},
{
"family": "Poulsen",
"given": "Mai B"
},
{
"family": "Siebner",
"given": "Hartwig R"
},
{
"family": "Kruuse",
"given": "Christina"
}
],
"container-title-short": "Brain Commun",
"volume": "8",
"issue": "4",
"page": "fcag309",
"DOI": "10.1093/braincomms/fcag309",
"PMID": "42643719",
"PMCID": "PMC13504703",
"ISSN": "2632-1297",
"publisher": "Oxford University Press",
"URL": "https://doi.org/10.1093/braincomms/fcag309",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
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.pbio.3003856 [code]
Aging and metabolism contribute separately to brain-body health.
Journal: PLoS biology
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools, structural MRI / diffusion, clinical / translational
[2] doi:10.1038/s41467-026-73668-y [code]
Convergent and divergent brain-cognition development in early adolescence.
Journal: Nature communications
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools, fMRI
[3] doi:10.1016/j.xcrm.2026.102943 [code]
Parent-of-origin effects in Alzheimer's liability dissociate neurocognitive and cardiovascular traits in at-risk individuals.
Journal: Cell reports. Medicine
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools, clinical / translational
[4] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools, clinical / translational
[5] doi:10.1038/s41467-026-76452-0 [code]
Music evokes shared neural representations of imagined narratives across sensory modalities.
Journal: Nature communications
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools
[6] doi:10.1038/s41467-026-71568-9 [code]
Convergent and selective representations of pain, appetitive processes, aversive processes, and cognitive control in the insula.
Journal: Nature communications
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools
[7] doi:10.1038/s41398-026-04025-2 [code]
Brain energetic landscapes shape state dysregulation in major depressive disorder: a morphological network controllability perspective.
Journal: Translational psychiatry
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools
[8] doi:10.1038/s41467-026-71151-2 [code]
Common and distinct neural correlates of social interaction processing and theory of mind in narratives.
Journal: Nature communications
In common: ANTs, Nilearn, Image Processing Toolbox, 6 other tools
[9] doi:10.1002/epi.70296 [code]
Fully automated three-dimensional deep learning-based magnetic resonance imaging segmentation of brain cavities in epilepsy surgery.
Journal: Epilepsia
In common: ANTs, Nilearn, Image Processing Toolbox, 5 other tools, structural MRI / diffusion, clinical / translational
[10] doi:10.1038/s41467-026-76812-w [code]
Assessing molecular, cellular and transcriptomic bases of laminar perfusion and cytoarchitecture coupling in the human cortex.
Journal: Nature communications
In common: ANTs, Nilearn, NiBabel, 5 other tools, stroke, structural MRI / diffusion

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.