OSCR

Autonomic indicators of self-transcendence: insights from the numadelic VR paradigm.

Code ↔ Paper

3 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 3 matches
  1. [1] § Materials and Methods › Physiological data collection › Data preprocessing ↔ 3ECG_RespProcessing_UPDATED.ipynb, lines 452–498 · score 0.66 · moving median, signal envelope, outliers, window, amplitude
  2. [2] § Materials and Methods › Physiological data collection › Data preprocessing ↔ 3ECG_RespProcessing_UPDATED.ipynb, lines 43–46 · score 0.65 · ecg_process, peak detection, ECG signal, preprocessing
  3. [3] § Materials and Methods › Physiological data collection › Data preprocessing ↔ 3ECG_RespProcessing_UPDATED.ipynb, lines 43–46 · score 0.53 · peak detection, ECG signals, preprocessed, quality

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 · 521 lines · 16 KB · no license · 3 matches

  1. # %%
  2. #pip install
  3. #pip install
  4. #pip install neurokit2 hrv-analysis PyWavelets mne astropy numpy
  5. # %%
  6. import neurokit2 as nk
  7. import pandas as pd
  8. import matplotlib.pyplot as plt
  9. import numpy as np
  10. import os
  11. import scipy.io
  12. from pathlib import Path
  13. from scipy.stats import variation
  14. import multiprocessing
  15. plt.rcParams['figure.figsize'] = 15, 5
  16. f=180 #sampling frequency
  17. # %% [markdown]
  18. # ## ECG data preprocessing ##
  19. # %%
  20. Type = 'VR' # VR or Control
  21. Session = 'Session4'
  22. Shimmer = 'S4'
  23. Section = 'Baseline'
  24. #Section= 'Medit'
  25. #Section ='PostMedit'
  26. relative_dir = os.path.join('..', 'Ripple','Processed_data', Type, Session, Shimmer, Section)
  27. # Read the CSV file using the relative path
  28. csv_file_path = os.path.join(relative_dir, f'{Section}.csv')
  29. data = pd.read_csv(csv_file_path)
  30. data.columns = data.columns.str.extract(r'(ECG.*)', expand=False)
  31. ecg=data['ECG_LA_RA_24BIT_BPF_CAL'] # NB: change to LL_RA instaed of LA_RA for Session 4, S3 due to inverted electrodes
  32. # %%
  33. #This function runs different preprocessing steps: Cleaning (using ecg_clean()), peak detection (using ecg_peaks()), heart rate calculation (using signal_rate()), signal quality assessment (using ecg_quality()), QRS complex delineation (using ecg_delineate()), and cardiac phase determination (using ecg_phase()).
  34. ecg_signals, info = nk.ecg_process(ecg, sampling_rate=f) # default method 'neurokit'
  35. nk.ecg_plot(ecg_signals, info)
  36. # %%
  37. #Zhao et al. (2018) method of quality assessment
  38. ecg_clean=ecg_signals['ECG_Clean']
  39. nk.ecg_quality(ecg_clean,
  40. sampling_rate=f,
  41. method="zhao2018",
  42. approach="fuzzy")
  43. # %% [markdown]
  44. # ## Extract HR and RR times ##
  45. # %%
  46. #detect ECG R peaks and check data
  47. Rpeaks, info = nk.ecg_peaks(ecg_signals['ECG_Clean'], sampling_rate=f, method='neurokit', correct_artifacts=True, show=True)
  48. # %%
  49. #Identify and Correct Peaks using "Kubios" Method
  50. rpeaks_uncorrected = nk.ecg_findpeaks(ecg_signals['ECG_Clean'], method="pantompkins", sampling_rate=f)
  51. info, rpeaks_corrected = nk.signal_fixpeaks(
  52. rpeaks_uncorrected, sampling_rate=f, iterative=True, method="Kubios", show=True)
  53. # %%
  54. # Visualize Artifact Correction
  55. rate_corrected = nk.signal_rate(rpeaks_corrected, desired_length=len(ecg))
  56. rate_uncorrected = nk.signal_rate(rpeaks_uncorrected, desired_length=len(ecg))
  57. nk.signal_plot(
  58. [rate_uncorrected, rate_corrected],
  59. labels=["Heart Rate Uncorrected", "Heart Rate Corrected"]
  60. )
  61. # %%
  62. # Extract R peaks indices
  63. r_peak_indices = rpeaks_corrected
  64. r_peak_times = r_peak_indices / f # Convert indices to time by dividing by the sampling rate
  65. r_peak_amplitudes = ecg_signals['ECG_Clean'].iloc[r_peak_indices]
  66. HR = ecg_signals['ECG_Rate'].iloc[r_peak_indices]
  67. # Refine R peak amplitudes, in case some R peaks are not detected at maximum peak amplitude
  68. for i, index in enumerate(r_peak_indices):
  69. window_start = max(0, index - int(0.3 * f)) # 0.3 seconds before
  70. window_end = min(len(ecg_signals['ECG_Clean']), index + int(0.3 * f)) # 0.3 seconds after
  71. local_maxima = np.max(ecg_signals['ECG_Clean'].iloc[window_start:window_end])
  72. if r_peak_amplitudes.iloc[i] < local_maxima:
  73. r_peak_amplitudes.iloc[i] = local_maxima
  74. # Create a DataFrame
  75. df_rpeaks = pd.DataFrame({
  76. 'Time': r_peak_times,
  77. 'Amplitude': r_peak_amplitudes,
  78. 'HR': HR
  79. })
  80. # Calculate RR intervals
  81. df_rpeaks['rri'] = df_rpeaks['Time'].diff()
  82. # Function to filter R peaks based on local median RR intervals
  83. def filter_r_peaks(df, window_size=10, threshold=0.5):
  84. filtered_indices = []
  85. for i in range(len(df)):
  86. if i < window_size:
  87. filtered_indices.append(i)
  88. continue
  89. local_median_rri = df['rri'].iloc[i-window_size:i].median()
  90. if df['rri'].iloc[i] >= threshold * local_median_rri:
  91. filtered_indices.append(i)
  92. return df.iloc[filtered_indices].reset_index(drop=True)
  93. # Apply the filtering function
  94. df_rpeaks_filtered = filter_r_peaks(df_rpeaks)
  95. # Update r_peak_indices based on the filtered DataFrame
  96. filtered_indices = df_rpeaks_filtered.index.to_list()
  97. r_peak_indices_filtered = r_peak_indices[filtered_indices]
  98. # %%
  99. # apply R peaks correction to ECG_R_peaks column in ECG signal
  100. ecg_signals['ECG_R_Peaks']=0
  101. # Set the values to 1 at the R-peaks indices
  102. ecg_signals.loc[r_peak_indices_filtered, 'ECG_R_Peaks'] = 1
  103. # %%
  104. # Plotting in 1-minute chunks for visualiation of R peaks detection
  105. chunk_size = f * 60 # Number of samples per 1 minute
  106. num_chunks = int(len(ecg_signals['ECG_Clean']) / chunk_size)
  107. time = np.linspace(0, len(ecg_signals['ECG_Clean']) / f, len(ecg_signals['ECG_Clean']))
  108. plt.figure(figsize=(12, 6 * num_chunks))
  109. for i in range(num_chunks):
  110. start = i * chunk_size
  111. end = (i + 1) * chunk_size
  112. chunk_time = time[start:end]
  113. chunk_signal = ecg_signals['ECG_Clean'][start:end]
  114. chunk_r_peaks = df_rpeaks_filtered[(df_rpeaks_filtered['Time'] >= chunk_time[0]) & (df_rpeaks_filtered['Time'] < chunk_time[-1])]
  115. plt.subplot(num_chunks, 1, i + 1)
  116. plt.plot(chunk_time, chunk_signal, label='ECG Signal')
  117. plt.plot(chunk_r_peaks['Time'], chunk_r_peaks['Amplitude'], 'ro', label='R Peaks')
  118. plt.xlabel('Time (s)')
  119. plt.ylabel('Amplitude')
  120. plt.legend()
  121. plt.title(f'ECG Signal with R Peaks (Minute {i + 1})')
  122. plt.tight_layout()
  123. plt.show()
  124. # %%
  125. #Visualise RR intervals after correction
  126. plt.plot(df_rpeaks_filtered['rri'], label='RR intervals')
  127. # %%
  128. #save Rpeaks files
  129. csv_file_path = os.path.join(relative_dir, 'HRV', 'Rpeaks_time_HR_rri.csv')
  130. df_rpeaks_filtered.to_csv(csv_file_path, index=False)
  131. #save Rpeaks time in text format for SAI/PAI indexes estimation
  132. Rpeaks_time= df_rpeaks_filtered['Time'].values # delete the first nan value before saving as txt
  133. txt_file_path = os.path.join(relative_dir, 'HRV', 'RPeaks.txt')
  134. np.savetxt(txt_file_path, Rpeaks_time, fmt='%f', header='', comments='')
  135. # %% [markdown]
  136. # ## Estimate HRV parameters ##
  137. # %%
  138. import statistics
  139. HR=df_rpeaks['HR']
  140. hrv_indices = nk.hrv(ecg_signals['ECG_R_Peaks'], sampling_rate=f, show=True)
  141. hrv_indices['HR'] = statistics.mean(HR)
  142. hrv_indices['SD HR']=statistics.stdev(HR)
  143. hrv_indices
  144. # %%
  145. csv_file_path = os.path.join(relative_dir,'HRV', 'HRVmetrics.csv')
  146. hrv_indices.to_csv(csv_file_path, index=False)
  147. # %% [markdown]
  148. # ## Preprocessing of respiratory signal ##
  149. # %%
  150. resp=data['ECG_RESP_24BIT_LPF_CAL'] # name to be updated
  151. #def normalize_resp(resp_series):
  152. #return (resp_series - np.mean(resp_series)) / np.std(resp_series)
  153. #resp_norm = normalize_resp(resp)
  154. # %%
  155. #This is the processing of Respiratory signal
  156. Resp, info = nk.rsp_process(resp, sampling_rate=f, method='khodadad2018', method_rvt='harrison2021', report="text")
  157. fig = nk.rsp_plot(Resp, info)
  158. # %%
  159. nk.signal_plot(Resp['RSP_Raw'])
  160. nk.signal_plot(Resp['RSP_Clean'])
  161. # %%
  162. csv_file_path = os.path.join(relative_dir, 'Resp', 'RespTimeserie.csv')
  163. Resp.to_csv(csv_file_path, index=False)
  164. # %% [markdown]
  165. # ## Analysis of repiratory signal parameters ##
  166. # %%
  167. # for Baseline and post Baseline/riple analysis is done on 5 min readings and 1 min epochs
  168. # for Ripple / Baseline, analysis on 1min epochs
  169. rsp_analysis= nk.rsp_analyze(Resp, sampling_rate=f)
  170. #rsp_analysis= nk.rsp_intervalrelated(Resp, sampling_rate=f)
  171. RespAmp=Resp['RSP_Amplitude']
  172. rsp_analysis['RSP_Amplitude'] = statistics.mean(RespAmp)
  173. csv_file_path = os.path.join(relative_dir, 'Resp', 'RespParameters.csv')
  174. rsp_analysis.to_csv(csv_file_path, index=False)
  175. rsp_analysis
  176. # %% [markdown]
  177. # ## Repiratory sinus Arythmia (RSA) ##
  178. # %%
  179. #Run only if respiratory signal is good quality
  180. rsa_metrics= nk.hrv_rsa(ecg_signals, Resp, sampling_rate=f, continuous=False) #
  181. rsa_metrics_df=pd.DataFrame({'RSA_P2T_Mean': rsa_metrics['RSA_P2T_Mean'],
  182. 'RSA_P2T_Mean_log': rsa_metrics['RSA_P2T_Mean_log'],
  183. 'RSA_P2T_SD': rsa_metrics['RSA_P2T_SD'],
  184. 'RSA_P2T_NoRSA': rsa_metrics['RSA_P2T_NoRSA'],
  185. 'RSA_PorgesBohrer': rsa_metrics['RSA_PorgesBohrer'],
  186. 'RSA_Gates_Mean': rsa_metrics['RSA_Gates_Mean'],
  187. 'RSA_Gates_Mean_log':rsa_metrics['RSA_Gates_Mean_log'],
  188. 'RSA_Gates_SD':rsa_metrics['RSA_Gates_SD']
  189. }, index=[0])
  190. rsa_metrics_df
  191. # %%
  192. # Get RSA as a continuous signal
  193. rsa = nk.hrv_rsa(ecg_signals, Resp, sampling_rate=f, continuous=True)
  194. # %%
  195. csv_file_path = os.path.join(relative_dir,'HRV', 'rsa_average.csv')
  196. rsa_metrics_df.to_csv(csv_file_path, index=False)
  197. csv_file_path = os.path.join(relative_dir,'HRV', 'rsa_continuous.csv')
  198. rsa.to_csv(csv_file_path, index=False)
  199. # %% [markdown]
  200. # ## Combine data of interest into 1 dataframe ##
  201. # %%
  202. Resp_REvents= Resp.iloc[r_peak_indices_filtered]
  203. Resp_REvents=Resp_REvents.reset_index(drop=True)
  204. rsa_REvents= rsa.iloc[r_peak_indices_filtered]
  205. rsa_REvents=rsa_REvents.reset_index(drop=True)
  206. # %%
  207. #Create a data frame for R events with R time, (RRI), HR, Resp, Resp ecg (EDR), resp rate, RSA
  208. Index= np.arange(len(Resp_REvents))
  209. rsa_REvents_df= pd.DataFrame(rsa_REvents)
  210. REvents_variables=[]
  211. REvents_variables=pd.DataFrame({'HR': df_rpeaks_filtered['HR'],
  212. 'Rpeaks_time': df_rpeaks_filtered['Time'],
  213. 'Rpeaks_amplitude': df_rpeaks_filtered['Amplitude'],
  214. 'rri': df_rpeaks_filtered['rri'],
  215. 'Resp': Resp_REvents['RSP_Clean'],
  216. 'Resp rate': Resp_REvents['RSP_Rate'],
  217. 'Resp amp': Resp_REvents['RSP_Amplitude'],
  218. 'RSA_P2T': rsa_REvents_df['RSA_P2T'],
  219. 'RSA_Gates': rsa_REvents_df['RSA_Gates'] })
  220. REvents_variables = REvents_variables.reset_index(drop=True)
  221. REvents_variables
  222. # %%
  223. # Step added to filter out resp data out of normal range (i.e points >1)
  224. # Replace values in 'Column1' that are greater than 1 with NaN
  225. REvents_variables['Resp amp'] = REvents_variables['Resp amp'].apply(lambda x: np.nan if x > 1 else x)
  226. # %%
  227. nk.signal_plot([REvents_variables['rri'], REvents_variables['Resp'], REvents_variables['RSA_Gates']], standardize=True)
  228. # %% [markdown]
  229. # ## CALCULATE RMSSD WITH SLIDING WINDOWS ##
  230. # %%
  231. #RMMSSD computed on 30 data points (R peaks), with a step of one R peak
  232. window_size = 30 # 30
  233. step_size = 1 # 1
  234. rri=REvents_variables['rri']
  235. def calculate_rmssd(rr, window_size, step_size):
  236. rmssd = {}
  237. hr = 60000/rr
  238. for start in range(0, len(rr) - window_size, step_size):
  239. window = rr[start:start + window_size]
  240. rmssd[start]= np.sqrt(np.mean(np.square(np.diff(window*1000))))
  241. return rmssd
  242. RMSSD_windowed=calculate_rmssd(rri, 30, 1)
  243. RMSSD_windowed_df=pd.DataFrame({'RMSSD':RMSSD_windowed})
  244. RMSSD_windowed_df
  245. # Add 30 Nan values at the begining
  246. # Create a DataFrame of 30 NaN values
  247. nan_values = pd.DataFrame(np.nan, index=range(30), columns=RMSSD_windowed_df.columns)
  248. # Concatenate the NaN values DataFrame with the original DataFrame
  249. RMSSD_windowed_df_nan = pd.concat([nan_values, RMSSD_windowed_df], ignore_index=True)
  250. RMSSD_windowed_df_nan
  251. # %%
  252. # Add RMSSD time serie to R events variables of interest
  253. REvents_variables['RMSSD']=RMSSD_windowed_df_nan['RMSSD']
  254. REvents_variables
  255. # %% [markdown]
  256. # ## Code to calculate the SAI/ PAI indexes ##
  257. # %%
  258. import requests
  259. import re
  260. import time
  261. # Define the relative directory and the input/output file paths
  262. txt_file_path = os.path.join(relative_dir, 'HRV', 'RPeaks.txt')
  263. output_file_path = os.path.join(relative_dir,'HRV', 'SAI_PAI.txt')
  264. url = 'https://neurostat-mit.appspot.com/'
  265. values = {'worker': 'saipai'}
  266. # Step 1: Open and read the input file
  267. with open(txt_file_path, 'r') as file:
  268. files = {'myFile': file}
  269. # Step 2: Upload the file and get the response URL
  270. r = requests.post(url + 'upload', files=files, data=values)
  271. if not r.ok:
  272. raise Exception("Failed to upload the file.")
  273. resp_url = re.findall(r'url=([^"]*)"', r.text)
  274. if len(resp_url) != 1:
  275. raise Exception("Failed to retrieve the response URL.")
  276. # Step 3: Poll the server to get the result
  277. while True:
  278. r = requests.get(url + resp_url[0])
  279. if not r.ok:
  280. raise Exception("Failed to retrieve the result.")
  281. if not r.text.startswith('<'):
  282. result = r.text
  283. break
  284. time.sleep(1)
  285. # Step 4: Save the result to the output file
  286. with open(output_file_path, 'w') as output_file:
  287. output_file.write(result)
  288. print(f"Result saved to: {output_file_path}")
  289. # %%
  290. ## add SAI and PAI to the key variables
  291. file_path = os.path.join(relative_dir,'HRV', 'SAI_PAI.txt')
  292. # Read the text file into a DataFrame
  293. # Adjust the delimiter parameter if your file has a different delimiter (e.g., ',' for comma-separated values)
  294. df = pd.read_csv(file_path, delimiter='\t', header=None)
  295. # Rename the columns
  296. df.columns = ['SAI', 'PAI', 'SAI/PAI']
  297. #add a first row of nan vaulues to match the format of REvents variable
  298. # Create a DataFrame with a single row of NaN values
  299. nan_row = pd.DataFrame([[np.nan, np.nan, np.nan]], columns=df.columns)
  300. # Concatenate the NaN row with the original DataFrame
  301. df = pd.concat([nan_row, df], ignore_index=True)
  302. df
  303. # %%
  304. REvents_variables['SAI']= df['SAI']
  305. REvents_variables['PAI']= df['PAI']
  306. REvents_variables['SAI/PAI']= df['SAI/PAI']
  307. REvents_variables
  308. # %% [markdown]
  309. # ## Code to Calculate HRV amplitude ##
  310. # %%
  311. #resample sample every 1 s
  312. # Convert 'Rpeaks_time' to datetime and set it as the index
  313. REvents_variables['Timestamp']= REvents_variables['Rpeaks_time']
  314. REvents_variables['Timestamp'] = pd.to_datetime(REvents_variables['Timestamp'], unit='s')
  315. REvents_variables.set_index('Timestamp', inplace=True)
  316. # Resample the DataFrame to have a 1-second sampling frequency
  317. resampled_df = REvents_variables.resample('1s').mean()
  318. # Interpolate to fill missing values
  319. resampled_df = resampled_df.interpolate(method='linear')
  320. resampled_df
  321. # %%
  322. import numpy as np
  323. import matplotlib.pyplot as plt
  324. from scipy.signal import find_peaks
  325. # Generate a sample signal (sinusoidal with some noise)
  326. signal = resampled_df['rri'].values
  327. t = resampled_df.index
  328. # Find local maxima and minima
  329. peaks, _ = find_peaks(signal)
  330. troughs, _ = find_peaks(-signal)
  331. # Calculate the median of 5 local maxima and minima to avoid outliers
  332. def moving_median(data, window_size=5):
  333. medians = []
  334. for i in range(len(data)):
  335. window = data[max(0, i - window_size // 2):min(len(data), i + window_size // 2 + 1)]
  336. medians.append(np.median(window))
  337. return np.array(medians)
  338. upper_envelope = moving_median(signal[peaks])
  339. lower_envelope = moving_median(signal[troughs])
  340. # Interpolate the envelopes to match the length of the original signal
  341. upper_envelope_interp = np.interp(t, t[peaks], upper_envelope)
  342. lower_envelope_interp = np.interp(t, t[troughs], lower_envelope)
  343. # Compute the width of the envelope at each time point
  344. envelope_width = upper_envelope_interp - lower_envelope_interp
  345. resampled_df['RRIEnveloppe']=envelope_width
  346. resampled_df['LowerEnveloppe']=lower_envelope_interp
  347. resampled_df['UpperEnloppe']= upper_envelope_interp
  348. # Plot the original signal, envelope, and width
  349. plt.figure(figsize=(12, 6))
  350. plt.plot(t, signal, label='Original Signal')
  351. plt.plot(t, upper_envelope_interp, label='Upper Envelope', linestyle='--')
  352. plt.plot(t, lower_envelope_interp, label='Lower Envelope', linestyle='--')
  353. plt.fill_between(t, lower_envelope_interp, upper_envelope_interp, color='gray', alpha=0.3, label='Envelope Width')
  354. plt.xlabel('Time')
  355. plt.ylabel('Amplitude')
  356. plt.legend()
  357. plt.title('Signal Envelope and Width')
  358. plt.show()
  359. # %%
  360. resampled_df
  361. # %%
  362. csv_file_path = os.path.join(relative_dir,'REvents_ALLvariables.csv')
  363. resampled_df.to_csv(csv_file_path, index=False)
  364. # %%
  365. # %%
  366. # %%
  367. # %%
  368. # %%

3ECG_RespProcessing_UPDATED.ipynb at commit 4614383, no license · at the source

Overview

Authors: Valerie Bonnelle1,2, Giulia Parola3, Catherine Andreu1, Joseph L Hardy2,4, Justin Wall2, Christopher Timmermann5,6, Ausiàs Cebolla7, Maja Wrzesien7, David R Glowacki1
  1. Intangible Realities Laboratory, Centro Singular de Investigación en Tecnoloxías Intelixentes, Rúa de Jenaro de la Fuente Domínguez, Campus Vida of the Universidade de Santiago de Compostela (USC), 15782 Santiago de Compostela, A Coruña, Spain
  2. Numadelic Labs, 2253 Carquinez Avenue, El Cerrito, CA 94530, United States
  3. Polibienestar Research Institute, University of Valencia, Edificio de Institutos de Investigación Calle Serpis, 2946022 Valencia, Spain
  4. aNUma, Inc., 4040 Civic Center Dr., San Rafael, CA 94903, United States
  5. Department of Experimental Psychology, University College London, 26 Bedford Way, London WC1H 0AP, United Kingdom
  6. Centre for Psychedelic Research, Department of Brain Sciences, Imperial College London, The Commonwealth Building, Hammersmith Hospital, Du Cane Road, London W12 0NN, United Kingdom
  7. Department of Personality, Evaluation and Psychological Treatments, University of Valencia, Avenida Blasco Ibáñez, 21, 46010 Valencia, Spain
Institutions: Universidade de Santiago de Compostela (Spain); University College London (United Kingdom); Universitat de València (Spain); Hammersmith Hospital (United Kingdom); Imperial College London (United Kingdom)
Journal: Neuroscience of consciousness, volume 2026, issue 1, article niag009
Dates: received 24 September 2025; accepted 10 March 2026; published online 22 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1093/nc/niag009 · PMID 42028122 · PMCID PMC13099402 · OpenAlex W4416154274
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), cognitive (subfield)
Methods: Spectral & time-frequency, Statistics, Preprocessing, Connectivity, Smoothing, state filtering, decompositions, Evoked potentials, Physiology & signal measures
Keywords: consciousness, embodiment, states of consciousness
Journal subjects: Aware & Alive: Embodied and Phenomenological Perspectives on Consciousness
Topic: Psychedelics and Drug Studies (Clinical Psychology, Psychology), according to OpenAlex
Funding: Generalitat Valenciana (CISEJI/2022/46)
Citations: not cited yet (Europe PMC); 136 references in the paper

Abstract

Self-transcendent experiences (STEs) offer profound and beneficial shifts in perspective, yet remain largely inaccessible outside elite contemplative or pharmacological contexts. Although neural measures have advanced our understanding of these states, their cost and limited ecological validity restrict broader application. This study evaluates heart rate variability (HRV) amplitude, a measure reflecting dynamic sympathovagal engagement, as a cost-effective and sustainable physiological marker of STE during ‘numadelic’ virtual reality (VR) experiences designed to dissolve self-boundaries and foster embodied presence. Building on previous work showing (i) associations between non-ordinary states of consciousness (NOSC) and autonomic activity during psychedelic administration, and (ii) comparable STE intensity in non-drug numadelic VR, we tested whether HRV amplitude reflects STE depth and relates to affective and relational outcomes during numadelic VR. Ninety-six participants engaged in guided meditation either in numadelic VR or a non-VR audio-guided group format. Cardiac and respiratory data were recorded during the session, alongside pre- and post-meditation psychological assessments. Findings show that HRV amplitude measured during numadelic VR correlates with subjective STE ratings, as well as compassion traits, and emotional improvement following practice. Reanalysis of data from a prior psychedelic study further supports the relevance of this measure across different methods of inducing NOSCs. These results advance the psychophysiological mapping of STEs and identify HRV amplitude as a promising real-time biomarker that may help guide participants toward self-transcendent states within adaptive environments. By integrating contemplative science with immersive design, this work contributes to scalable tools that broaden access to and deepen understanding of STEs.

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

vbonnelle/Ripple_Study_Bonnelle2025

License: none: the authors keep all their rights
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 4614383aea0274a035651fae3fa067aa4cab4ea2, 16 December 2025
Languages: Jupyter (1)
Size: 13 files, 1 script
Software Heritage: not archived
Found in: “Data availability”
Holds: 1 notebook
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (1 file), NeuroKit2 (1 file), NumPy (1 file), pandas (1 file), SciPy (1 file)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
1 file

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;
  • 3 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

Datasets cited

Data availability

ECG data and audio file of the audio of the pre-recorded meditation ‘Ripple’ in English are available at https://doi.org/10.5281/zenodo.17061627.

Code for data-preprocessing, final dataset for statistical analyses (with questionnaires and subjective ratings as well as physiological measures of interest) and VR experience chapters video snapshots are available at https://github.com/vbonnelle/Ripple_Study_Bonnelle2025.

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, issue, pages, dates, 9 authors, 3 keywords, 1 funder, 127 references.

Cite

This paper

Bonnelle, V., Parola, G., Andreu, C., Hardy, J. L., Wall, J., Timmermann, C., Cebolla, A., Wrzesien, M., & Glowacki, D. R. (2026). Autonomic indicators of self-transcendence: insights from the numadelic VR paradigm. Neuroscience of consciousness, 2026(1), niag009. https://doi.org/10.1093/nc/niag009

BibTeX

@article{bonnelle2026autonomic,
author = {Bonnelle, Valerie and Parola, Giulia and Andreu, Catherine and Hardy, Joseph L and Wall, Justin and Timmermann, Christopher and Cebolla, Ausiàs and Wrzesien, Maja and Glowacki, David R},
title = {{Autonomic indicators of self-transcendence: insights from the numadelic VR paradigm}},
journal = {Neuroscience of consciousness},
year = {2026},
month = apr,
volume = {2026},
number = {1},
pages = {niag009},
publisher = {Oxford University Press},
issn = {2057-2107},
doi = {10.1093/nc/niag009},
url = {https://doi.org/10.1093/nc/niag009},
pmid = {42028122},
pmcid = {PMC13099402}
}

RIS

TY - JOUR
AU - Bonnelle, Valerie
AU - Parola, Giulia
AU - Andreu, Catherine
AU - Hardy, Joseph L
AU - Wall, Justin
AU - Timmermann, Christopher
AU - Cebolla, Ausiàs
AU - Wrzesien, Maja
AU - Glowacki, David R
TI - Autonomic indicators of self-transcendence: insights from the numadelic VR paradigm
T2 - Neuroscience of consciousness
J2 - Neurosci Conscious
PY - 2026
DA - 2026/04/22
VL - 2026
IS - 1
SP - niag009
SN - 2057-2107
PB - Oxford University Press
DO - 10.1093/nc/niag009
UR - https://doi.org/10.1093/nc/niag009
LA - en
ER -

CSL-JSON

{
"id": "10.1093/nc/niag009",
"type": "article-journal",
"title": "Autonomic indicators of self-transcendence: insights from the numadelic VR paradigm",
"container-title": "Neuroscience of consciousness",
"author": [
{
"family": "Bonnelle",
"given": "Valerie"
},
{
"family": "Parola",
"given": "Giulia"
},
{
"family": "Andreu",
"given": "Catherine"
},
{
"family": "Hardy",
"given": "Joseph L"
},
{
"family": "Wall",
"given": "Justin"
},
{
"family": "Timmermann",
"given": "Christopher"
},
{
"family": "Cebolla",
"given": "Ausiàs"
},
{
"family": "Wrzesien",
"given": "Maja"
},
{
"family": "Glowacki",
"given": "David R"
}
],
"container-title-short": "Neurosci Conscious",
"volume": "2026",
"issue": "1",
"page": "niag009",
"DOI": "10.1093/nc/niag009",
"PMID": "42028122",
"PMCID": "PMC13099402",
"ISSN": "2057-2107",
"publisher": "Oxford University Press",
"URL": "https://doi.org/10.1093/nc/niag009",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
22
]
]
}
}

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.1038/s42003-026-10156-5 [code]
Brain-heart interactions in late-onset major depressive disorder revealed by multimodal HRV-driven fMRI.
Journal: Communications biology
In common: 10 references
[2] doi:10.1093/nc/niag044 [code]
Aquahenosis: a non-pharmacological altered state of consciousness induced by floatation-REST in individuals with anxiety and depression.
Journal: Neuroscience of consciousness
In common: cognitive, 6 references
[3] doi:10.1038/s41586-026-10910-z [code]
Psychedelics align brain activity with context.
Journal: Nature
In common: SciPy, NumPy, cognitive, 4 references
[4] doi:10.3389/fnsys.2026.1880737
Autonomic-salience stability as a candidate Gate for awake low-dose ketamine: a systems neuroscience framework with a clinical anchor.
Journal: Frontiers in systems neuroscience
In common: 6 references
[5] doi:10.1038/s41467-026-71604-8 [code]
Respiration as a dynamic modulator of sensory sampling.
Journal: Nature communications
In common: cognitive, 5 references
[6] doi:10.1093/braincomms/fcag120 [code]
The heartbeat evoked potential and the prediction of functional seizure semiology.
Journal: Brain communications
In common: NeuroKit2, pandas, SciPy, 2 other tools, 1 reference
[7] doi:10.1016/j.isci.2026.116151 [code]
Impaired sleep resilience underlies transient neural instability in insomnia disorder.
Journal: iScience
In common: NeuroKit2, pandas, SciPy, 2 other tools, 1 reference
[8] doi:10.1038/s41398-026-04333-7
Cardiac vagal activity during in-vivo threat exposure is associated with within-session inhibition of fear and avoidance.
Journal: Translational psychiatry
In common: 5 references
[9] doi:10.3389/fnhum.2026.1874178
Longitudinal neural and psychological changes following psilocybin under compassion imagery priming.
Journal: Frontiers in human neuroscience
In common: cognitive, 4 references
[10] doi:10.1111/psyp.70385 [code]
Exploring EEG and ECG in Music Listening: A Scoping Review.
Journal: Psychophysiology
In common: cognitive, 4 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.