An Intraoperative EEG Biomarker for Postoperative Delirium Predicting Based on Interpretable Deep Learning Framework.
The 8 matches
- [1] § Results › Best Temporal Filter Captures an EEG Signature of POD Individuals ↔ plot/Figure4.ipynb, lines 254–318 · score 0.69 · gradient boosting machine, multilayer perceptron, random forest, 10 %, training, accuracies
- [2] § Results › Best Temporal Filter Captures an EEG Signature of POD Individuals ↔ plot/figure_3.ipynb, lines 282–359 · score 0.69 · gradient boosting machine, multilayer perceptron, random forest, 10 %, training, accuracies
- [3] § Materials and Methods › Filter Analysis ↔ plot/Figure4.ipynb, lines 254–318 · score 0.68 · multilayer perceptron, gradient boosting, random forest, training, model
- [4] § Materials and Methods › Filter Analysis ↔ plot/figure_3.ipynb, lines 282–359 · score 0.68 · multilayer perceptron, gradient boosting, random forest, training, model
- [5] § Results › A Novel EEG Waveform as a Potential Biomarker for POD Early Warning ↔ plot/Figure5.ipynb, lines 187–248 · score 0.63 · power spectral density, confidence interval, target wave, 12 Hz
- [6] § Results › A Novel EEG Waveform as a Potential Biomarker for POD Early Warning ↔ plot/Figure5.ipynb, lines 561–662 · score 0.58 · biomarker events, confidence intervals, brain regions, bootstrap, min, POD
- [7] § Results › A Novel EEG Waveform as a Potential Biomarker for POD Early Warning ↔ plot/Figure5.ipynb, lines 561–662 · score 0.53 · confidence interval, brain regions, event, bootstrap, min, biomarkers
- [8] § Results › A Novel EEG Waveform as a Potential Biomarker for POD Early Warning ↔ supp_plot/supp_figure_4.ipynb, lines 74–140 · score 0.52 · power spectral density, target wave, PSD, POD, 12 Hz
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 · 662 lines · 25 KB · no license · 3 matches
- # %% [markdown]
- # # Figure 5
- # %%
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import seaborn as sns
- import torch
- import torch.nn.functional as F
- from scipy import stats
- import os
- import pickle
- from sklearn.preprocessing import StandardScaler
- import mne
- from matplotlib.colors import Normalize
- from matplotlib.colors import TwoSlopeNorm
- from scipy.stats import bootstrap
- import matplotlib as mpl
- # %% [markdown]
- # # Figure 5A
- # %%
- import os
- import pickle
- import numpy as np
- import matplotlib.pyplot as plt
- import matplotlib.patches as patches
- def highlight_interval_visualization(load_path, signal_data_path, subject_ids, save_root, sampling_rate=125):
- """
- Visualizes highlighted intervals and signal data for different channel positions.
- A plot is generated for each minute of data.
- Arguments:
- - load_path: str, path to the highlighted interval data (pickle format).
- - signal_data_path: str, path to processed signal data (pickle format, organized by channel).
- - subject_ids: list, list of subject IDs to visualize.
- - save_root: str, directory to save the generated images.
- - sampling_rate: int, the EEG data sampling rate (default is 125Hz).
- Returns:
- - None: This function saves visualization results as image files in the specified folder.
- """
- # Load the highlight regions and signal data from the provided paths
- with open(load_path, 'rb') as file:
- highlight_regions_per_channel = pickle.load(file)
- with open(signal_data_path, 'rb') as file:
- signal_data = pickle.load(file)
- # Configure plot appearance
- plt.rcParams.update({
- 'font.family': 'Times New Roman', # Recommended journal font
- 'font.size': 12,
- 'axes.labelsize': 14,
- 'axes.linewidth': 1.5,
- 'legend.fontsize': 10,
- 'xtick.labelsize': 12,
- 'ytick.labelsize': 12,
- 'pdf.fonttype': 42,
- 'ps.fonttype': 42,
- 'figure.dpi': 300
- })
- # Process each subject
- for subject_id in subject_ids:
- # Extract highlight intervals for the current subject
- subject_highlight = {}
- for channel, highlights in highlight_regions_per_channel.items():
- highlight_data = next((item for item in highlights if item['subject_id'] == subject_id), None)
- if highlight_data:
- subject_highlight[channel] = highlight_data['highlight_intervals']
- # Extract signal data for the current subject
- subject_signal = {}
- for channel, signals in signal_data.items():
- signal_data_item = next((item for item in signals if item['subject_id'] == subject_id), None)
- if signal_data_item:
- subject_signal[channel] = signal_data_item['processed_data']
- # Calculate the number of minutes of signal data
- num_samples_per_minute = sampling_rate * 10
- total_samples = len(next(iter(subject_signal.values()))) # Get the number of samples from one channel
- num_minutes = total_samples // num_samples_per_minute
- # Generate one plot for each minute
- for minute in range(num_minutes):
- fig, ax = plt.subplots(figsize=(6.5, 8)) # Standard size for Nature journal (inches)
- plt.subplots_adjust(left=0.15, right=0.95, top=0.93, bottom=0.08) # Adjust layout margins
- num_channels = len(subject_signal)
- bar_height = 0.8
- # Plot the signal data for each channel
- for channel_id, signal in subject_signal.items():
- signal_start = minute * num_samples_per_minute
- signal_end = (minute + 1) * num_samples_per_minute
- signal_segment = signal[signal_start:signal_end]
- signal_segment = normalize_signal(signal_segment)
- time_segment = np.linspace(signal_start / sampling_rate, signal_end / sampling_rate, len(signal_segment))
- # Plot the signal waveform for the current channel
- ax.plot(time_segment, signal_segment + channel_id - 0.5,
- color="#2f2f2f", alpha=0.95, linewidth=1.2, solid_capstyle='round')
- # Plot the highlighted intervals for the current channel
- if channel_id not in subject_highlight:
- continue
- channel_highlights = subject_highlight[channel_id]
- for highlight in channel_highlights:
- start, end = highlight
- start_time = start / sampling_rate
- end_time = end / sampling_rate
- # Check if the highlight interval falls within the current minute's time window
- if start_time >= signal_start / sampling_rate and end_time <= signal_end / sampling_rate:
- ax.add_patch(patches.Rectangle(
- (start_time, channel_id - 0.5),
- end_time - start_time,
- bar_height,
- color='orange',
- alpha=0.45,
- edgecolor="orange",
- linewidth=0.7,
- linestyle="-"
- ))
- # Customize the plot appearance
- channel_labels = ["Fp1", "Fp2", "Fz", "F3", "F4", "F7", "F8", "FCz", "FC3", "FC4", "FT7", "FT8", "Cz",
- "C3", "C4", "T3", "T4", "CP3", "CP4", "TP7", "TP8", "Pz", "P3", "P4", "T5", "T6", "Oz", "O1", "O2"]
- ax.set_xlim(minute * 10, (minute + 1) * 10)
- ax.set_ylim(-0.5, num_channels - 0.5)
- ax.set_yticks(range(num_channels))
- ax.set_yticklabels(channel_labels, fontstyle='italic', color="black")
- ax.yaxis.set_tick_params(width=0.5)
- ax.xaxis.set_tick_params(width=0.5)
- ax.set_xlabel("Time (s)", labelpad=3)
- ax.set_ylabel("EEG Channels", labelpad=3)
- ax.spines['right'].set_visible(False)
- ax.spines['top'].set_visible(False)
- ax.spines['left'].set_linewidth(0.5)
- ax.spines['bottom'].set_linewidth(0.5)
- ax.invert_yaxis()
- # Title the plot
- ax.set_title(f"Subject {subject_id} | Time window: {minute + 1}", fontweight='semibold', pad=12)
- # Save the plot as an image
- save_path = os.path.join(save_root, f"{subject_id}_second_{minute + 1}.png")
- plt.savefig(save_path, dpi=600, bbox_inches='tight', pil_kwargs={'compression': 'tiff_lzw'})
- plt.close()
- def normalize_signal(signal):
- """Normalize the signal data to be between -1 and 1."""
- return (signal - np.min(signal)) / (np.max(signal) - np.min(signal)) * 2 - 1
- # If necessary, please contact the corresponding author to obtain the data.
- # %% [markdown]
- # # Figure 5B
- # %%
- # 读取 .npz 文件
- data = np.load('../dataset/figure5_B_plot_variables.npz')
- # 获取文件中的变量
- freqs_tar = data['freqs_tar']
- psd_tar = data['psd_tar']
- ci_lower_tar = data['ci_lower_tar']
- ci_upper_tar = data['ci_upper_tar']
- freqs_ori = data['freqs_ori']
- psd_ori = data['psd_ori']
- ci_lower_ori = data['ci_lower_ori']
- ci_upper_ori = data['ci_upper_ori']
- mean_psd_tar = data['mean_psd_tar']
- mean_psd_ori = data['mean_psd_ori']
- freqs_show = data['freqs_show']
- sig_freqs = data['sig_freqs']
- pvals_corrected = data['pvals_corrected']
- ci_lower_diff = data['ci_lower_diff']
- ci_upper_diff = data['ci_upper_diff']
- # %%
- import matplotlib.pyplot as plt
- import numpy as np
- # Define frequency limit and mask for frequency range
- freq_limit = 30
- freq_mask = freqs_tar <= freq_limit
- freqs_show = freqs_tar[freq_mask]
- # Update plotting parameters for a professional look
- plt.rcParams.update({
- 'font.family': 'DejaVu Sans',
- 'font.size': 12, # Base font size
- 'axes.labelsize': 14, # Axis label font size
- 'axes.linewidth': 1.5, # Axis line width
- 'legend.fontsize': 10, # Legend font size
- 'xtick.labelsize': 12, # X-axis tick label font size
- 'ytick.labelsize': 12, # Y-axis tick label font size
- 'pdf.fonttype': 42, # Ensure the output text is editable
- 'ps.fonttype': 42,
- 'figure.dpi': 300 # High resolution for the figure
- })
- # Create the plot with specified figure size
- fig, ax = plt.subplots(figsize=(4.3*1.5, 3*1.5))
- # Plot the target wave (mean and confidence intervals)
- ax.plot(freqs_tar[freq_mask], mean_psd_tar, color='orange', alpha=0.75, label='Target wave', zorder=1)
- ax.fill_between(freqs_tar[freq_mask], ci_lower_tar, ci_upper_tar, color='orange', alpha=0.25)
- # Plot the original signal (mean and confidence intervals)
- ax.plot(freqs_tar[freq_mask], mean_psd_ori, color='gray', alpha=0.75, label='Original signal', zorder=2)
- ax.fill_between(freqs_tar[freq_mask], ci_lower_ori, ci_upper_ori, color='gray', alpha=0.25)
- # Plot the confidence interval of the difference between target and original signals
- ax.fill_between(freqs_tar[freq_mask], ci_lower_diff, ci_upper_diff, color='red', alpha=0.25, label='95% CI of difference', zorder=3)
- # Highlight the significant frequencies with stars
- sig_freqs = freqs_show[pvals_corrected < 0.05] # Frequencies with p-values < 0.05
- sig_freqs = sig_freqs[sig_freqs < 16] # Limit to frequencies below 16 Hz
- if len(sig_freqs) > 0:
- ax.plot(sig_freqs, [np.max([mean_psd_tar, mean_psd_ori]) * 1.1] * len(sig_freqs),
- lw=0, marker='*', color='k', markersize=8, label='p < 0.05')
- # Adjust plot formatting
- ax.set_xlim(-0.5, freq_limit + 0.5)
- ax.set_xlabel('Frequency (Hz)') # X-axis label
- ax.set_ylabel('Power (a.u.)') # Y-axis label with units
- # Customize grid if needed (commented out for clarity)
- # ax.grid(True, linestyle=':', alpha=0.6) # Optional grid lines for better visualization
- # Add legend
- legend = ax.legend(frameon=True, loc='upper right')
- legend.get_frame().set_linewidth(1.2) # Set legend border width
- legend.get_frame().set_edgecolor('whitesmoke') # Set legend border color
- # Adjust layout to fit the plot nicely
- plt.tight_layout()
- # Show the plot (if running interactively)
- # plt.show()
- # %%
- from scipy.signal import butter, filtfilt, welch
- from scipy.stats import t
- # Define the mapping of regions to channels in a dictionary
- net_dict = {
- 'Forehead': list(range(0, 2)),
- 'Frontal': list(range(2, 12)),
- 'Center': [12, 13, 14, 17, 18],
- 'Temporal': [15, 16, 19, 20, 24, 25],
- 'Parietal': [21, 22, 23],
- 'Occipital': [26, 27, 28],
- 'All': list(range(0, 29)) # Mapping includes all channels
- }
- fs = 125 # Sampling frequency
- def calculate_power_per_subject(detection_datas, region_dict):
- """
- Calculate the total power of EEG signals in different frequency bands for each subject,
- averaged over specified highlight intervals for each region.
- Arguments:
- - detection_datas: Data for the EEG signals with highlight intervals.
- - region_dict: Mapping of regions to channel indices.
- Returns:
- - subject_region_mean_intervals: List of dictionaries with average power for each region per subject.
- """
- subject_region_mean_intervals = []
- # Iterate over each subject's data
- for j, subject_data in enumerate(detection_datas[0]): # Assuming the subject data is in the first element
- subject_id = subject_data['subject_id']
- # Store the region-wise mean power for each subject
- region_mean_per_subject = {}
- # Iterate through the regions and their channels
- for region, channels in region_dict.items():
- highlight_intervals_per_region = []
- # Iterate through each channel in the region
- for channel in channels:
- highlight_intervals = detection_datas[channel][j]['highlight_intervals']
- # Process each highlight interval
- for highlight_interval in highlight_intervals:
- # Normalize the highlight interval
- segment_normalized = highlight_interval / np.max(np.abs(highlight_interval))
- # Calculate the power spectrum using Welch's method
- frequencies, psd = welch(segment_normalized, fs=fs, nperseg=100)
- # Calculate the total power of the signal
- total_power = np.trapz(psd, frequencies) / len(highlight_interval) * fs
- highlight_intervals_per_region.append(total_power)
- # Compute the average power for the region across channels
- region_mean_per_subject[region] = np.mean(highlight_intervals_per_region)
- # Store the region-wise power results for the subject
- subject_region_mean_intervals.append(region_mean_per_subject)
- return subject_region_mean_intervals
- def calculate_center_frequency_per_subject(detection_datas, region_dict):
- """
- Calculate the center frequency of EEG signals for each subject, averaged over highlight intervals
- for each region.
- Arguments:
- - detection_datas: Data for the EEG signals with highlight intervals.
- - region_dict: Mapping of regions to channel indices.
- Returns:
- - subject_region_mean_intervals: List of dictionaries with average center frequency for each region per subject.
- """
- subject_region_mean_intervals = []
- # Iterate over each subject's data
- for j, subject_data in enumerate(detection_datas[0]):
- subject_id = subject_data['subject_id']
- # Store the region-wise center frequency for each subject
- region_mean_per_subject = {}
- # Iterate through the regions and their channels
- for region, channels in region_dict.items():
- highlight_intervals_per_region = []
- # Iterate through each channel in the region
- for channel in channels:
- highlight_intervals = detection_datas[channel][j]['highlight_intervals']
- # Process each highlight interval
- for highlight_interval in highlight_intervals:
- # Calculate the power spectral density using Welch's method
- frequencies, psd = welch(highlight_interval, fs=fs, nperseg=100)
- # Calculate the center frequency as the weighted average of the frequencies
- center_frequency = np.sum(frequencies * psd) / np.sum(psd)
- highlight_intervals_per_region.append(center_frequency)
- # Compute the average center frequency for the region across channels
- region_mean_per_subject[region] = np.mean(highlight_intervals_per_region)
- # Store the region-wise center frequency results for the subject
- subject_region_mean_intervals.append(region_mean_per_subject)
- return subject_region_mean_intervals
- def calculate_highlight_intervals_per_subject(detection_datas, region_dict):
- """
- Calculate the number of highlight intervals for each region in the EEG data for each subject.
- Arguments:
- - detection_datas: Data for the EEG signals with highlight intervals.
- - region_dict: Mapping of regions to channel indices.
- Returns:
- - subject_region_mean_intervals: List of dictionaries with the number of highlight intervals per region per subject.
- """
- subject_region_mean_intervals = []
- # Iterate over each subject's data
- for j, subject_data in enumerate(detection_datas[0]):
- subject_id = subject_data['subject_id']
- # Store the region-wise highlight interval count for each subject
- region_mean_per_subject = {}
- # Iterate through the regions and their channels
- for region, channels in region_dict.items():
- highlight_intervals_per_region = []
- # Iterate through each channel in the region
- for channel in channels:
- highlight_intervals = detection_datas[channel][j]['highlight_intervals']
- # Count the number of highlight intervals for the channel
- highlight_intervals_per_region.append(len(highlight_intervals))
- # Compute the average number of highlight intervals for the region
- region_mean_per_subject[region] = np.mean(highlight_intervals_per_region)
- # Store the region-wise highlight interval counts for the subject
- subject_region_mean_intervals.append(region_mean_per_subject)
- return subject_region_mean_intervals
- def extract_region_means(subject_region_means, region_name):
- """
- Extract the region-specific means for all subjects.
- Arguments:
- - subject_region_means: List of dictionaries containing region-wise means for each subject.
- - region_name: The region to extract means for.
- Returns:
- - np.array: Array of region-specific means across subjects.
- """
- return np.array([subject[region_name] for subject in subject_region_means])
- def mean_difference(data, n_group_A):
- """
- Calculate the difference in means between two groups.
- Arguments:
- - data: Combined data from both groups.
- - n_group_A: The number of samples in group A.
- Returns:
- - The difference in means between the two groups.
- """
- group_A = data[:n_group_A]
- group_B = data[n_group_A:]
- return np.mean(group_A) - np.mean(group_B)
- def bootstrap_region_comparison(subject_region_mean_A, subject_region_mean_B, region_dict, confidence_level=0.95, n_resamples=10000):
- """
- Perform a bootstrap test to compare the means of two groups for each region.
- Arguments:
- - subject_region_mean_A: Region means for group A.
- - subject_region_mean_B: Region means for group B.
- - region_dict: Mapping of regions to channel indices.
- - confidence_level: Confidence level for the bootstrap test (default is 0.95).
- - n_resamples: Number of bootstrap resamples (default is 10,000).
- Returns:
- - results: Dictionary with mean differences, confidence intervals, and p-values for each region.
- """
- results = {}
- # Iterate over each region in the dictionary
- for region in region_dict.keys():
- # Extract region means for both groups
- group_A = extract_region_means(subject_region_mean_A, region)
- group_B = extract_region_means(subject_region_mean_B, region)
- # Record the sample size of group A
- n_group_A = len(group_A)
- # Combine data from both groups
- data = np.concatenate([group_A, group_B])
- # Perform the bootstrap test
- res = bootstrap(
- (data,),
- lambda x: mean_difference(x, n_group_A),
- confidence_level=confidence_level,
- n_resamples=n_resamples,
- method='BCa',
- paired=False,
- alternative='two-sided'
- )
- # Save the results for this region
- results[region] = {
- "mean_difference": mean_difference(data, n_group_A),
- "confidence_interval": res.confidence_interval,
- "p_value": (res.confidence_interval.low > 0) or (res.confidence_interval.high < 0)
- }
- return results
- # %% [markdown]
- # # Figure 5C
- # %%
- results = np.load('../dataset/figure5_C_results.npz', allow_pickle=True)
- for region, result in results.items():
- result = result.item()
- print(f" Region: {region}")
- print(f" Mean Difference: {result['mean_difference']}")
- print(f" Confidence Interval: {result['confidence_interval']}")
- print(f" Significant: {result['p_value']}\n")
- violin_data = np.load('../dataset/figure5_C_violin_data.npy', allow_pickle=True)
- df_violin = pd.DataFrame(violin_data, columns=["Region", "Group", "Center Frequecy(Hz)"])
- # 设置期刊级绘图参数
- plt.rcParams.update({
- 'font.family': 'DejaVu Sans',
- 'font.size': 10,
- 'axes.titlesize': 12,
- 'axes.labelsize': 11,
- 'xtick.labelsize': 10,
- 'ytick.labelsize': 10,
- 'legend.fontsize': 10,
- 'figure.dpi': 300,
- 'axes.linewidth': 0.8 # 坐标轴线宽
- })
- fig, ax = plt.subplots(figsize=(13, 4), constrained_layout=True)
- clinical_palette = {"POD": "#E64B35", "non-POD": "#3C5488"}
- # 增强型箱线图设计
- box = sns.boxplot(
- x='Region',
- y='Center Frequecy(Hz)',
- hue='Group',
- data=df_violin,
- palette=clinical_palette,
- width=0.6,
- linewidth=1.2,
- flierprops={
- 'marker': 'o',
- 'markersize': 4,
- 'markerfacecolor': 'none',
- 'markeredgecolor': 'gray',
- 'markeredgewidth': 0.5
- },
- # boxprops={'facecolor': 'none', 'edgecolor': 'black'},
- whiskerprops={'linewidth': 1.2},
- medianprops={'color': 'black', 'linewidth': 1.5},
- ax=ax
- )
- # 坐标轴优化
- ax.set_ylim(3, 9.5)
- ax.set_ylabel("Center Frequecy(Hz)", labelpad=10)
- ax.set_xlabel("Brain Regions", labelpad=10)
- ax.yaxis.grid(True, linestyle='--', alpha=0.6)
- # 高级图例设计
- handles, labels = ax.get_legend_handles_labels()
- legend = ax.legend(
- handles[:2],
- ['POD', 'Non-POD'],
- frameon=True,
- loc='upper right',
- bbox_to_anchor=(1.18, 1), # 图例外置防止重叠
- ncol=1,
- # title='Experimental Group',
- title_fontproperties={'weight': 'bold'},
- edgecolor='none',
- borderpad=0.8
- )
- # 优化视觉层次
- sns.despine(offset=5, trim=True) # 移除顶部和右侧轴线
- plt.savefig('/mnt/dataset1/UnonoU/POD-biomarker/plot/figure_5_C', dpi=300, bbox_inches='tight')
- plt.show()
- # %% [markdown]
- # # Figure 5E
- # %%
- # subject_region_mean_A = calculate_highlight_intervals_per_subject(detection_datas_A, net_dict)
- # subject_region_mean_B = calculate_highlight_intervals_per_subject(detection_datas_B, net_dict)
- # results = bootstrap_region_comparison(subject_region_mean_A, subject_region_mean_B, net_dict, confidence_level=0.999, n_resamples=1000)
- # violin_data = []
- # for region in regions:
- # data_A = [subject[region] for subject in subject_region_mean_A]
- # data_B = [subject[region] for subject in subject_region_mean_B]
- # for value in data_A:
- # violin_data.append((region, 'POD', value/10))
- # for value in data_B:
- # violin_data.append((region, 'non-POD', value/10))
- # df_violin = pd.DataFrame(violin_data, columns=["Region", "Group", "Biomarker Event Frequency (event/min)"])
- results = np.load('../dataset/figure5_E_results_99.9.npz', allow_pickle=True)
- print('P<0.001')
- for region, result in results.items():
- result = result.item()
- print(f" Region: {region}")
- print(f" Mean Difference: {result['mean_difference']}")
- print(f" Confidence Interval: {result['confidence_interval']}")
- print(f" Significant: {result['p_value']}\n")
- results = np.load('../dataset/figure5_E_results_99.npz', allow_pickle=True)
- print('P<0.01')
- for region, result in results.items():
- result = result.item()
- print(f" Region: {region}")
- print(f" Mean Difference: {result['mean_difference']}")
- print(f" Confidence Interval: {result['confidence_interval']}")
- print(f" Significant: {result['p_value']}\n")
- violin_data = np.load('../dataset/figure5_E_violin_data.npy', allow_pickle=True)
- df_violin = pd.DataFrame(violin_data, columns=["Region", "Group", "Biomarker Event Frequency (event/min)"])
- # 设置期刊级绘图参数
- plt.rcParams.update({
- 'font.family': 'DejaVu Sans',
- 'font.size': 10,
- 'axes.titlesize': 12,
- 'axes.labelsize': 11,
- 'xtick.labelsize': 10,
- 'ytick.labelsize': 10,
- 'legend.fontsize': 10,
- 'figure.dpi': 300,
- 'axes.linewidth': 0.8 # 坐标轴线宽
- })
- fig, ax = plt.subplots(figsize=(12, 4), constrained_layout=True)
- clinical_palette = {"POD": "#E64B35", "non-POD": "#3C5488"}
- # 增强型箱线图设计
- box = sns.boxplot(
- x='Region',
- y='Biomarker Event Frequency (event/min)',
- hue='Group',
- data=df_violin,
- palette=clinical_palette,
- width=0.6,
- linewidth=1.2,
- flierprops={
- 'marker': 'o',
- 'markersize': 4,
- 'markerfacecolor': 'none',
- 'markeredgecolor': 'gray',
- 'markeredgewidth': 0.5
- },
- # boxprops={'facecolor': 'none', 'edgecolor': 'black'},
- whiskerprops={'linewidth': 1.2},
- medianprops={'color': 'black', 'linewidth': 1.5},
- ax=ax
- )
- # 坐标轴优化
- # ax.set_ylim(3, 9.5)
- ax.set_ylabel("Biomarker Event Frequency (event/min)", labelpad=10)
- ax.set_xlabel("Brain Regions", labelpad=10)
- ax.yaxis.grid(True, linestyle='--', alpha=0.6)
- # 高级图例设计
- handles, labels = ax.get_legend_handles_labels()
- legend = ax.legend(
- handles[:2],
- ['POD', 'Non-POD'],
- frameon=True,
- loc='upper right',
- bbox_to_anchor=(1.18, 1), # 图例外置防止重叠
- ncol=1,
- # title='Experimental Group',
- title_fontproperties={'weight': 'bold'},
- edgecolor='none',
- borderpad=0.8
- )
- # 优化视觉层次
- sns.despine(offset=5, trim=True) # 移除顶部和右侧轴线
- plt.savefig('/mnt/dataset1/UnonoU/POD-biomarker/plot/figure_5_E', dpi=300, bbox_inches='tight')
- plt.show()
Figure5.ipynb at commit 0f9c56d, no license · at the source
Overview
- Department of Biomedical Engineering, Southern University of Science and Technology, Shenzhen, Guangdong, China
- Department of Anesthesiology and Perioperative Medicine, Xijing Hospital, The Fourth Military Medical University, Xi'an, China
- Key Laboratory of Anesthesiology (The Fourth Military Medical University), Ministry of Education, Xi'an, China
- Shaanxi Provincial Clinical Research Center for Anesthesiology Medicine, Xi'an, China
- Department of Anesthesiology, The Third People's Hospital of Chengdu, Chengdu, Sichuan, China
- Center for Neurocognition and Social Behavior, Artificial Intelligence Research Institute, Shenzhen University of Advanced Technology, Shenzhen, Guangdong, China
Abstract
Postoperative delirium (POD) is a common complication in older surgical patients and substantially worsens clinical outcomes, yet existing intraoperative electroencephalography (EEG) monitoring tools lack spatial and temporal specificity, creating a need for interpretable biomarkers. We prospectively analyzed 32‐channel intraoperative EEG from 71 patients aged ≥ 60 undergoing noncardiac surgery, trained an interpretable spatiotemporal convolutional network (ST‐CN), derived a best temporal filter (BTF), and evaluated model performance with region‐specific tests and independent external validation. The ST‐CN classified POD with 97.52% accuracy and an ROC of 0.996. The BTF alone discriminated POD with an AUC of 0.911 and achieved 85.12% accuracy using frontal EEG alone. It captured a distinct 2–12 Hz (δ–θ–α) oscillation in a spindle‐like envelope, which occurred at a significantly higher rate in POD patients (4.62 ± 0.15 vs. 3.88 ± 0.13 waves/
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 8 matches between paragraphs and lines of code.
ncclab-sustech/POD-biomarker
0f9c56db9a968774970f1494373a6581407b2c88, 13 September 2025Availability: 1 check, the latest on 26 September 2026: the link answers
- 26 September 2026: the link answers
14 files
- plot/
Figure4.ipynb , Jupyter, 402 lines, 2 matches - plot/
Figure5.ipynb , Jupyter, 662 lines, 3 matches - plot/
figure_3.ipynb , Jupyter, 953 lines, 2 matches - supp_plot/
supp_figure_1.ipynb , Jupyter, 138 lines - supp_plot/
supp_figure_2.ipynb , Jupyter, 257 lines - supp_plot/
supp_figure_3.ipynb , Jupyter, 288 lines - supp_plot/
supp_figure_4.ipynb , Jupyter, 208 lines, 1 match - train/
SubjectDataset.py , Python, 206 lines - train/
compute contribution.py , Python, 395 lines - train/
model.py , Python, 366 lines - train/
spindle_detection.ipynb , Jupyter, 204 lines - train/
train_main.py , Python, 291 lines - train/
train_utils.py , Python, 765 lines - README.md, Text, 1 line
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;
- 13 scripts, each with its path and the digest of its content;
- 8 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 Statement
The analysis code has been uploaded to GitHub, the code is available at https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 3, 28 September 2026
- Funding: added National Natural Science Foundation of China: 2021ZD0200500, 82221001, 82271211, 82293643, 62472206, 82430040, 3254100307; Southern University of Science and Technology; Shenzhen Municipal Science and Technology Innovation Council: KJZD20230923115221044, RCBS20231211090748082; National Science and Technology Major Project: 2021ZD0200500, 2025ZD0218300; Basic and Applied Basic Research Foundation of Guangdong Province: 2025A1515011645, 2026A1515010121, 2026B1515020099
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 12 authors, 4 keywords, 36 references.
Cite
This paper
Zhang, Y., Zhu, Y., Zhang, X., Shen, X., Tang, X., Lu, Z., Lei, C., Li, M., Dong, H., Liang, Z., Liu, Q., & Zhao, G. (2026). An Intraoperative EEG Biomarker for Postoperative Delirium Predicting Based on Interpretable Deep Learning Framework. MedComm, 7(9), e70980. https://
BibTeX
@article{zhang2026intrao
author = {Zhang, Yinuo and Zhu, Yan and Zhang, Xinxin and Shen, Xinke and Tang, Xuemiao and Lu, Zhihong and Lei, Chong and Li, Mengyu and Dong, Hailong and Liang, Zhichao and Liu, Quanying and Zhao, Guangchao},
title = {{An Intraoperative EEG Biomarker for Postoperative Delirium Predicting Based on Interpretable Deep Learning Framework}},
journal = {MedComm},
year = {2026},
month = sep,
volume = {7},
number = {9},
pages = {e70980},
publisher = {Wiley},
issn = {2688-2663},
doi = {10.1002/
url = {https://
pmid = {42707129},
pmcid = {PMC13547085}
}
RIS
TY - JOUR
AU - Zhang, Yinuo
AU - Zhu, Yan
AU - Zhang, Xinxin
AU - Shen, Xinke
AU - Tang, Xuemiao
AU - Lu, Zhihong
AU - Lei, Chong
AU - Li, Mengyu
AU - Dong, Hailong
AU - Liang, Zhichao
AU - Liu, Quanying
AU - Zhao, Guangchao
TI - An Intraoperative EEG Biomarker for Postoperative Delirium Predicting Based on Interpretable Deep Learning Framework
T2 - MedComm
J2 - MedComm (2020)
PY - 2026
DA - 2026/
VL - 7
IS - 9
SP - e70980
SN - 2688-2663
PB - Wiley
DO - 10.1002/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1002/
"type": "article-journal",
"title": "An Intraoperative EEG Biomarker for Postoperative Delirium Predicting Based on Interpretable Deep Learning Framework",
"container-title": "MedComm",
"author": [
{
"family": "Zhang",
"given": "Yinuo"
},
{
"family": "Zhu",
"given": "Yan"
},
{
"family": "Zhang",
"given": "Xinxin"
},
{
"family": "Shen",
"given": "Xinke"
},
{
"family": "Tang",
"given": "Xuemiao"
},
{
"family": "Lu",
"given": "Zhihong"
},
{
"family": "Lei",
"given": "Chong"
},
{
"family": "Li",
"given": "Mengyu"
},
{
"family": "Dong",
"given": "Hailong"
},
{
"family": "Liang",
"given": "Zhichao"
},
{
"family": "Liu",
"given": "Quanying"
},
{
"family": "Zhao",
"given": "Guangchao"
}
],
"container-title-short":
"volume": "7",
"issue": "9",
"page": "e70980",
"DOI": "10.1002/
"PMID": "42707129",
"PMCID": "PMC13547085",
"ISSN": "2688-2663",
"publisher": "Wiley",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
6
]
]
}
}
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.3389/fmed.2026.1794626 [code]
- Divergent periodic and aperiodic EEG signatures of Propofol versus sevoflurane anesthesia: a comparative neurophysiological study.Journal: Frontiers in medicineIn common: seaborn, scikit-learn, pandas, 3 other tools, EEG, clinical / translational, 2 references
- [2] doi:10.3389/fncom.2026.1786996 [code]
- Schumann-anchored golden ratio organization of human neural oscillations.Journal: Frontiers in computational neuroscienceIn common: MNE-Python, seaborn, scikit-learn, 4 other tools, EEG, 1 reference
- [3] doi:10.3390/diagnostics16081186 [code]
- The Perioperative Neurocognitive Disorder Prediction Based on AI-Assisted EEG Dynamic Features in Anesthetized Mice.Journal: Diagnostics (Basel, Switzerland)In common: EEG, clinical / translational, 1 reference, author Guangchao Zhao
- [4] doi:10.1371/journal.pcbi.1014302 [code]
- Trial-level sequence modeling reveals hidden dynamics of dual-task interference.Journal: PLoS computational biologyIn common: MNE-Python, PyTorch, seaborn, 5 other tools, EEG
- [5] doi:10.1162/imag.a.1207 [code]
- Investigating the temporal dynamics and modeling of mid-level feature representations in humans.Journal: Imaging neuroscience (Cambridge, Mass.)In common: MNE-Python, PyTorch, seaborn, 5 other tools, EEG
- [6] doi:10.1371/journal.pone.0347671 [code]
- RMETNet: A cross-subject motor imagery EEG signal classification model based on TSLANet and riemannian geometry features.Journal: PloS oneIn common: MNE-Python, PyTorch, seaborn, 5 other tools, EEG
- [7] doi:10.1002/hbm.70528 [code]
- Explainable AI Insights Into EEG Classification and Its Alignment to Neural Correlates.Journal: Human brain mappingIn common: MNE-Python, PyTorch, seaborn, 5 other tools, EEG
- [8] doi:10.1038/s41598-026-41532-0 [code]
- Prediction, syntax and semantic grounding in the brain and large language models.Journal: Scientific reportsIn common: MNE-Python, PyTorch, seaborn, 5 other tools, EEG
- [9] doi:10.1038/s41597-025-05174-7 [code]
- A large-scale MEG and EEG dataset for object recognition in naturalistic scenesJournal: n/aIn common: MNE-Python, PyTorch, seaborn, 5 other tools, EEG
- [10] doi:10.34133/csbj.0042 [code]
- Using Steady-State Visual Evoked Potentials to Characterize Wide-Ranging Retinopathy Linked to &
lt;i& gt;CRB1& lt;/ i& gt;: Implications for Clinical Trials. Journal: Computational and structural biotechnology journalIn common: MNE-Python, seaborn, scikit-learn, 4 other tools, EEG, clinical / translational, other condition
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 13 scripts, and 8 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:3112b5e1709070a2…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
