Arquivos
dreamento/OfflineDreamento.py
MahdadJafarzadeh a744749b5d minor bugs fixed
2024-03-18 17:49:24 +01:00

6948 linhas
378 KiB
Python

# -*- coding: utf-8 -*-
"""
Copyright (c) 2021-2023 Mahdad Jafarzadeh Esfahani
OfflineDreamento: The post-processing Dreamento!
This file is beta version, thus including way more features than offlineDreamento.py
But, if you look for a stable version, we recommend you use offlineDreamento.py ...
"""
import tkinter as tk
from tkinter import LabelFrame, Label, Button, filedialog, messagebox,OptionMenu, StringVar, DoubleVar, PhotoImage, Entry
from tkinter import *
import mne
import numpy as np
from numpy import loadtxt
import time
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from lspopt import spectrogram_lspopt
from matplotlib.colors import Normalize, ListedColormap
from matplotlib import style
from scipy import signal
import mne
import json
import pickle
from scipy.signal import butter, filtfilt
import itertools
import matplotlib
import os
from sklearn.preprocessing import MinMaxScaler
import yasa
import platform
matplotlib.use('TkAgg')
style.use('default')
class OfflineDreamento():
def __init__(self, master):
"""
Initiate the graphics, buttons, and the variables
:param self: access the attributes and methods of the class
:param master: The master window of the Tkinter
"""
self.master = master
master.title("OfflineDreamento: The post-processing Dreamento!")
#%% Import section
#### !!~~~~~~~~~~~~~~~~~ DEFINE INPUT DATAFRAME ~~~~~~~~~~~~~~~~~!!####
self.frame_import = LabelFrame(self.master, text = "Analysis section",
font = 'Calibri 18 bold')
self.frame_import.grid(row = 0 , column = 0, columnspan = 8)
#### ==================== Help pop-up button ======================####
self.popup_button = Button(self.master, text = "Help", command = self.help_pop_up_func,
font = 'Calibri 11 ', fg = 'white', bg = 'black')
self.popup_button.grid(row = 1, column = 7)
#### ==================== Manual scoring pop-up button ======================####
self.manual_scoring_popup_button = Button(self.master, text = "Manual scoring instructions", command = self.manual_scoring_popup_button_func,
font = 'Calibri 11 ', fg = 'white', bg = 'navy')
self.manual_scoring_popup_button.grid(row = 1, column = 6)
###### ================== CopyRight ============================ ######
self.label_CopyRight = Label(self.master, text = "© CopyRight (2021-23): Mahdad Jafarzadeh Esfahani",
font = 'Calibri 10 italic')
self.label_CopyRight.grid(row = 1 , column = 0)#, padx = 10, pady = 10)
#### ==================== Import Hypnodyne data ========================####
# Label: Import EDF
self.label_Hypnodyne = Label(self.frame_import, text = "Import Hypnodyne EEG L.edf:",
font = 'Calibri 11 ')
self.label_Hypnodyne.grid(row = 0 , column = 0)#, padx = 10, pady = 10)
# Button: Import EDF (Browse)
self.button_Hypnodyne_browse = Button(self.frame_import, text = "Browse Hypnodyne",
padx = 40, pady = 10,font = 'Calibri 12 ',
command = self.load_hypnodyne_file_dialog, fg = 'blue',
relief = RIDGE)
self.button_Hypnodyne_browse.grid(row = 1, column = 0)#, padx = 10, pady = 10)
#### ================== Import Dreamento file ====================####
# Show a message about hypnograms
self.label_Dreamento = Label(self.frame_import, text = "Import Dreamento output (.txt):",
font = 'Calibri 11 ')
self.label_Dreamento.grid(row = 0 , column = 1)#, padx = 10, pady = 10)
# Define browse button to import hypnos
self.button_Dreamento_browse = Button(self.frame_import, text = "Browse Dreamento",
padx = 40, pady = 10, font = 'Calibri 12 ',
command = self.load_Dreamento_file_dialog,fg = 'blue',
relief = RIDGE)
self.button_Dreamento_browse.grid(row = 1, column = 1)#, padx = 10, pady = 10)
#### ================== Import markers Json file ====================####
# Show a message about hypnograms
self.label_marker_json = Label(self.frame_import, text = "Import marker file (.json):",
font = 'Calibri 11 ')
self.label_marker_json.grid(row = 0 , column = 2)#, padx = 10, pady = 10)
# Define browse button to import hypnos
self.button_marker_json_browse = Button(self.frame_import, text = "Browse markers",
padx = 40, pady = 10, font = 'Calibri 12 ',
command = self.load_marker_file_dialog,fg = 'blue',
relief = RIDGE)
self.button_marker_json_browse.grid(row = 1, column = 2)#, padx = 10, pady = 10)
#### ================== Import Brainvision EMG Json file ====================####
# Show a message about hypnograms
self.label_EMG = Label(self.frame_import, text = "Import EMG file (.vhdr):",
font = 'Calibri 11 ')
self.label_EMG.grid(row = 0 , column = 3)#, padx = 10, pady = 10)
# Define browse button to import hypnos
self.button_EMG_browse = Button(self.frame_import, text = "Browse EMG (.vhdr)",
padx = 40, pady = 10, font = 'Calibri 12 ',
command = self.load_EMG_file_dialog,fg = 'blue',
relief = RIDGE)
self.button_EMG_browse.grid(row = 1, column = 3)#, padx = 10, pady = 10)
# =============================================================================
# #### ============ Push Analyze button to assign markers =========####
# #Label to read data and extract features
# self.label_apply = Label(self.frame_import, text = "Assign markers",
# font = 'Calibri 11 ')
# self.label_apply.grid(row = 2 , column = 1)
# =============================================================================
# Apply button
self.button_apply = Button(self.frame_import, text = "Analyze!",#, padx = 40, pady=8,
font = 'Calibri 11 bold', relief = RIDGE, fg = 'blue',
command = self.Apply_button, state = tk.DISABLED)
self.button_apply.grid(row = 3 , column =2)#, padx = 10, pady = 10)
#%% =========================== Options for analysis =================== #%%
#Label to read data and extract features
self.label_analysis = Label(self.frame_import, text = "Analysis options:",
font = 'Calibri 11 ')
self.label_analysis.grid(row = 0 , column = 4, sticky="w")
#%% label import human scoring / already scored hypnogram
self.deactivate_markers = IntVar(value = 0)
self.checkbox_deactivate_markers = Checkbutton(self.frame_import, text = "Deactivate Markers for scoring",
font = 'Calibri 11 ', variable = self.deactivate_markers)
self.checkbox_deactivate_markers.grid(row = 1, column = 4, sticky="w")
#%% Enable manual scoring
self.import_scoring = IntVar(value = 0)
self.checkbox_import_scoring = Checkbutton(self.frame_import, text = "Import sleep scoring",
font = 'Calibri 11 ', variable = self.import_scoring,
command=self.ImportManualScoring)
self.checkbox_import_scoring.grid(row = 2, column = 4, sticky="w")
#%% Bulk autoscoring
self.bulk_autoscoring_val = IntVar(value = 0)
self.checkbox_bulk_autoscoring = Checkbutton(self.frame_import, text = "Bulk autoscoring",
font = 'Calibri 11 ', variable = self.bulk_autoscoring_val,
command = self.bulk_autoscoring_popup)
self.checkbox_bulk_autoscoring.grid(row = 3, column = 4, sticky="w")
#%% Checkbox for autoscoring
self.is_autoscoring = IntVar(value = 0)
self.checkbox_is_autoscoring = Checkbutton(self.frame_import, text = "Single-file autoscoring",
font = 'Calibri 11 ', variable = self.is_autoscoring)#, command = self.scoring_caution)
self.checkbox_is_autoscoring.grid(row = 4, column = 4, sticky="w")
#%% Checkbox for filtering
self.is_filtering = IntVar(value = 1)
self.checkbox_is_filtering = Checkbutton(self.frame_import, text = "Band-pass filtering (.3-30 Hz)",
font = 'Calibri 11 ', variable = self.is_filtering)
self.checkbox_is_filtering.grid(row = 5, column = 4, sticky="w")
#%% Checkbox for plotting syncing process
self.plot_sync_output = IntVar()
self.checkbox_plot_sync_output = Checkbutton(self.frame_import, text = "Plot EEG alignment process",
font = 'Calibri 11 ', variable = self.plot_sync_output)
self.checkbox_plot_sync_output.grid(row = 6, column = 4, sticky="w")
#%% Checkbox for plotting EMG quality TFR
self.plot_EMG_quality_evaluation = IntVar(value=1)
self.checkbox_plot_EMG_quality_evaluation = Checkbutton(self.frame_import, text = "EMG quality evaluation",
font = 'Calibri 11 ', variable = self.plot_EMG_quality_evaluation)
self.checkbox_plot_EMG_quality_evaluation.grid(row = 7, column = 4, sticky="w")
#%% Checkbox for automatic eye movement detector
self.automatic_REM_event_deetction = IntVar(value=0)
self.checkbox_automatic_REM_event_deetction = Checkbutton(self.frame_import, text = "Automatic eye movement event detection",
font = 'Calibri 11 ', variable = self.automatic_REM_event_deetction,
command = self.automatic_REM_event_detection_popup)
self.checkbox_automatic_REM_event_deetction.grid(row = 8, column = 4, sticky="w")
#%% Checkbox for automatic spindle detection
self.automatic_spd_event_deetction = IntVar(value=0)
self.checkbox_automatic_spd_event_deetction = Checkbutton(self.frame_import, text = "Automatic SO + spindle detection",
font = 'Calibri 11 ', variable = self.automatic_spd_event_deetction,
command = self.automatic_spd_event_detection_popup)
self.checkbox_automatic_spd_event_deetction.grid(row = 9, column = 4, sticky="w")
#%% Checkbox for plotting periodogram
self.plot_psd = IntVar(value = 0)
self.checkbox_plot_psd = Checkbutton(self.frame_import, text = "Plot peridogram",
font = 'Calibri 11 ', variable = self.plot_psd)
self.checkbox_plot_psd.grid(row = 10, column = 4, sticky="w")#, pady = 10)
#%% checkbox DreamentoConverter
self.DreamentoConverter_val = IntVar(value = 0)
self.checkbox_DreamentoConverter = Checkbutton(self.frame_import, text = "Dreamento Converter",
font = 'Calibri 11 ', variable = self.DreamentoConverter_val,\
command=self.DreamentoConverter)
self.checkbox_DreamentoConverter.grid(row = 11, column = 4, sticky="w")#, pady = 10)
#%% Label to select the desired analysis
#Label to read data and extract features
self.label_analysis_data = Label(self.frame_import, text = "Select the data to analyze:",
font = 'Calibri 11 ')
self.label_analysis_data.grid(row = 12 , column = 4, sticky="w")
#%% Checkbox for plotting Dreamento + HDRecorder + EMG
self.analysis_signal_options = IntVar(value = 1)
self.checkbox_plot_additional_EMG = Radiobutton(self.frame_import, text = "Dreamento + HDRecorder + EMG",
font = 'Calibri 11 ', variable = self.analysis_signal_options,\
value = 1, command=self.analysis_signal_options_button_activator)
self.checkbox_plot_additional_EMG.grid(row = 13, column = 4, sticky="w")#, pady = 10)
#%% Checkbox for analyzing ZMax Hypndoyne + Dreamento
self.ZMax_Hypno_Dreamento = IntVar(value = 0)
self.checkbox_ZMax_Hypno_Dreamento = Radiobutton(self.frame_import, text = "Dreamento + HDRecorder",
font = 'Calibri 11 ', variable = self.analysis_signal_options, value = 2,\
command=self.analysis_signal_options_button_activator)
self.checkbox_ZMax_Hypno_Dreamento.grid(row = 14, column = 4, sticky="w")#, pady = 10)
#%% Checkbox for analyzing ZMax Hypndoyne only
self.ZMax_Hypno_only = IntVar(value = 0)
self.checkbox_ZMax_Hypno_only = Radiobutton(self.frame_import, text = "HDRecorder",
font = 'Calibri 11 ', variable = self.analysis_signal_options, value = 3,\
command=self.analysis_signal_options_button_activator)
self.checkbox_ZMax_Hypno_only.grid(row = 15, column = 4, sticky="w")#, pady = 10)
#%% Checkbox for analyzing ZMax Hypndoyne only
self.BrainVision_analysis = IntVar(value = 0)
self.checkbox_BrainVision_analysis = Radiobutton(self.frame_import, text = "BrainAmpProducts",
font = 'Calibri 11 ', variable = self.analysis_signal_options, value = 4,\
command=self.analysis_signal_options_button_activator)
self.checkbox_BrainVision_analysis.grid(row = 16, column = 4, sticky="w")#, pady = 10)
#%% EMG Y SCALE
#Label to read data and extract features
self.label_EMG_scale = Label(self.frame_import, text = "EMG amplitude (uV):",
font = 'Calibri 11 ')
self.label_EMG_scale.grid(row = 2 , column = 0)
self.EMG_scale_options = ['Set desired EMG amplitude ...','100', '50', '20', '10']
self.EMG_scale_options_val = StringVar()
self.EMG_scale_options_val.set(self.EMG_scale_options[0])
self.EMG_scale_option_menu = OptionMenu(self.frame_import, self.EMG_scale_options_val, *self.EMG_scale_options)
self.EMG_scale_option_menu.config(fg = 'blue')
self.EMG_scale_option_menu.grid(row = 3, column = 0)
#%% Initial values for REM and spd detection, default: 0 (no detection)
# =============================================================================
# self.checkbox_save_REM_detection_results_val = IntVar(value = 0)
# self.checkbox_save_REM_detection_results_val = IntVar(value = 0)
# =============================================================================
#%% EMG sync option
#Label to read data and extract features
self.button_sync_EMG = Button(self.frame_import, text = "Analyze! (+EMG)",#, padx = 40, pady=8,
font = 'Calibri 11 bold', relief = RIDGE, fg = 'blue',
command = self.EMG_sync_method_activator)
self.button_sync_EMG.grid(row = 3 , column =1)#, padx = 15, pady = 10)
# =============================================================================
# self.label_sync_EMG_option = Label(self.frame_import, text = "EMG sync method?",
# font = 'Calibri 11 ')
# self.label_sync_EMG_option.grid(row = 5 , column = 0)
# self.sync_EMG_option = ['no sync', 'manual']
# self.sync_EMG_option_val = StringVar()
# self.sync_EMG_option_val.set(self.sync_EMG_option[0])
# self.sync_EMG_option_menu = OptionMenu(self.frame_import, self.sync_EMG_option_val, *self.sync_EMG_option, command = self.EMG_sync_method_activator)
# self.sync_EMG_option_menu.grid(row = 6, column = 0)
# =============================================================================
#%% WarningNotEnoughDataMessage
self.WarningNotEnoughDataMessage = "Dear user! \nAll required data are not uploaded! \n Please upload them all and try again."
#%% Activation/inactivation of EMG button depending on the checkbox
def analysis_signal_options_button_activator(self):
int_val = int(self.analysis_signal_options.get())
if int_val == 1:
# EMG load button
self.button_EMG_browse['state'] = tk.NORMAL
# EMG option menu
self.EMG_scale_option_menu['state'] = tk.NORMAL
# Sync button
self.button_sync_EMG['state'] = tk.NORMAL
# Sync button
self.button_apply['state'] = tk.DISABLED
# EMG Evaluation
self.checkbox_plot_EMG_quality_evaluation['state'] = tk.NORMAL
self.button_Hypnodyne_browse['state'] = tk.NORMAL
self.button_Dreamento_browse['state'] = tk.NORMAL
self.button_marker_json_browse['state'] = tk.NORMAL
elif int_val == 2:
# EMG load button
self.button_EMG_browse['state'] = tk.DISABLED
# EMG option menu
self.EMG_scale_option_menu['state'] = tk.DISABLED
# Sync button
self.button_sync_EMG['state'] = tk.DISABLED
# Sync button
self.button_apply['state'] = tk.NORMAL
# EMG Evaluation
self.checkbox_plot_EMG_quality_evaluation['state'] = tk.DISABLED
self.button_Hypnodyne_browse['state'] = tk.NORMAL
self.button_Dreamento_browse['state'] = tk.NORMAL
elif int_val == 3:
# EMG load button
self.button_EMG_browse['state'] = tk.DISABLED
# EMG option menu
self.EMG_scale_option_menu['state'] = tk.DISABLED
# Sync button
self.button_sync_EMG['state'] = tk.DISABLED
# Sync button
self.button_apply['state'] = tk.NORMAL
# Browse Dreamento button
self.button_Dreamento_browse['state'] = tk.DISABLED
# Browse Markers browse button
self.button_marker_json_browse['state'] = tk.DISABLED
# EMG Evaluation
self.checkbox_plot_EMG_quality_evaluation['state'] = tk.DISABLED
self.button_Hypnodyne_browse['state'] = tk.NORMAL
elif int_val == 4:
# EMG load button
self.button_EMG_browse['state'] = tk.NORMAL
# EMG option menu
self.EMG_scale_option_menu['state'] = tk.DISABLED
# Sync button
self.button_sync_EMG['state'] = tk.NORMAL
# Sync button
self.button_apply['state'] = tk.DISABLED
# Browse Dreamento button
self.button_Dreamento_browse['state'] = tk.DISABLED
# Browse Markers browse button
self.button_marker_json_browse['state'] = tk.DISABLED
# EMG Evaluation
self.checkbox_plot_EMG_quality_evaluation['state'] = tk.DISABLED
# Load EEG L button
self.button_Hypnodyne_browse['state'] = tk.DISABLED
# update label for loading data
self.label_EMG.config(text = 'BrainProducts (.vhdr)')
#%% Moving average filter
def MA(self,x, N):
cumsum = np.cumsum(np.insert(x, 0, 0))
return (cumsum[N:] - cumsum[:-N]) / float(N)
#%% Def progress bar
def progress_bar(self):
from tkinter import ttk
from time import sleep
#start progress bar
popup = tk.Toplevel()
tk.Label(popup, text="Analysing in progress").grid(row=0,column=0)
teams = range(30)
progress = 0
progress_var = tk.DoubleVar()
progress_bar = ttk.Progressbar(popup, variable=progress_var, maximum=100)
progress_bar.grid(row=1, column=0)#.pack(fill=tk.X, expand=1, side=tk.BOTTOM)
popup.pack_slaves()
progress_step = float(100.0/len(teams))
for team in teams:
popup.update()
sleep(5) # lauch task
progress += progress_step
progress_var.set(progress)
return 0
#%% Enable manual scoring impotr
def ImportManualScoring(self):
messagebox.showinfo("Information",f"You can import up to 3 scorings for each data (e.g., to get the agreement between scorers)\n\n" +\
"Dreamento excpects the scorings to be of 2 columns:\n\n"+\
"column1:\n sleep scorings, where 0,1,2,3,4 stand for W,N1,N2,N3,REM, respectively.\n\n" +\
"column2:\n events indicated by 0,1,2,5757 for no event, arousal, major body movement, and LRLR eye movement, respectively.")
self.popupWin_ImportManualScoring = Toplevel(root)
self.path_to_manual_scorings_label = Label(self.popupWin_ImportManualScoring, text='Path to scorings:')
self.path_to_manual_scorings_label.grid(row = 1 , column =1)
# Define browse button to import hypnos
self.button_import_manual_scorings_browse = Button(self.popupWin_ImportManualScoring, text = "Browse scorings",
command = self.import_manual_scorings,fg = 'blue',
relief = RIDGE)
self.button_import_manual_scorings_browse.grid(row = 1 , column =2, padx = 10, pady = 10)
# Define browse button to import hypnos
self.button_import_manual_scorings_ok = Button(self.popupWin_ImportManualScoring, text = "OK!",
command = self.receive_scorings,fg = 'blue',
relief = RIDGE, padx = 10, pady = 10)
self.button_import_manual_scorings_ok.grid(row = 3 , column =1)
self.combine_W_N1_for_assessment = IntVar(value = 0)
self.checkbox_combine_W_N1_for_assessment = Checkbutton(self.popupWin_ImportManualScoring, text = "Combine W and N1 epochs for inter-rater agreement",
font = 'Calibri 11 ', variable = self.combine_W_N1_for_assessment)
self.checkbox_combine_W_N1_for_assessment.grid(row = 1, column = 3, sticky="w")
#%% receive scoring results
def receive_scorings(self):
# =============================================================================
# self.x_stages_imported = dict()
# self.y_stages_imported = dict()
# self.stages_imported = dict()
# self.scorings_imported = dict()
# print(f'number of imported scorings: {len(self.filename_scoring_paths)}')
#
# for scoring_file in np.arange(len(self.filename_scoring_paths)):
# print(f'reading {self.filename_scoring_paths[scoring_file]}')
# self.stages_imported[self.filename_scoring_paths[scoring_file]] = np.loadtxt(self.filename_scoring_paths[scoring_file], dtype = int)
# print(f'reading shape of {np.shape(self.stages_imported[self.filename_scoring_paths[scoring_file]])}')
# =============================================================================
self.stages_imported = dict()
self.events_imported = dict()
self.popupWin_ImportManualScoring.destroy()
#%% Activation/inactivation of EMG button depending on the checkbox
def EMG_sync_method_activator(self):
if int(self.analysis_signal_options.get()) == 1:
global sample_sync_EMG_Hypnodyne
# Sanitary checks
if 'hypnodyne_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The EEG L.edf file is not selected!")
elif 'Dreamento_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The .txt file recorded by Dreamento is not selected!")
elif 'marker_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The .json file of markers is not selected!")
elif 'EMG_files_list' not in globals() and int(self.analysis_signal_options.get()) == 1:
messagebox.showerror("Dreamento", "Sorry, but no EMG files is loaded, though the plot EMG check box is activated! Change either of these and try again.")
elif str(self.EMG_scale_options_val.get()) == 'Set desired EMG amplitude ...' and int(self.analysis_signal_options.get()) == 1:
messagebox.showerror("Dreamento", "Sorry, but a parameter is missing ...\nThe EMG amplitude is not set!")
else:
Dreamento_files_list, hypnodyne_files_list, marker_files_list
self.ZmaxDondersRecording = Dreamento_files_list[0]
self.HDRecorderRecording = hypnodyne_files_list[0]
self.path_to_json_markers = marker_files_list[0]
self.path_to_EMG = EMG_files_list[0]
# Opening JSON file
f = open(self.path_to_json_markers,)
print('reading annotation file ...')
# returns JSON object as a dictionary
markers = json.load(f)
markers_details = list(markers.values())
marker_keys = list(markers.keys())
clench_event = []
counter_sync = []
time_sync_event = []
sync_event_is_selected = 0
self.all_markers = []
self.all_timestamp_markers = []
self.all_markers_with_timestamps = []
for counter, marker in enumerate(markers.values()):
self.all_markers.append(marker)
for counter, marker in enumerate(markers.keys()):
self.all_timestamp_markers.append(marker)
for i in np.arange(len(self.all_markers)):
self.all_markers_with_timestamps.append(self.all_markers[i]+ '__'+ self.all_timestamp_markers[i])
self.select_marker_for_sync()
self.selected_marker = self.markers_sync_event.get()
messagebox.showinfo('syncing in process', f'The syncing event has been selected. Please wait for sync window to pop up.')
print(f'lets split the marker into ... {self.selected_marker.split()}')
time_sync_event.append(int(self.selected_marker.split()[-1]))
# =============================================================================
# for counter, marker in enumerate(markers.values()):
# try:
# if marker.split()[0] == 'clench' or marker.split()[0] == 'Clench':
# print(marker.split())
# counter_sync.append(counter)
#
# except:
# print(f'an exception occured for marker = {marker}')
# continue
# print(counter_sync)
#
# for counter, marker in enumerate(markers.keys()):
# if counter in counter_sync:
# time_sync_event.append(int(marker.split()[-1]))
#
# =============================================================================
print('Loading EEG file ... Please wait')
path_Txt = self.ZmaxDondersRecording
sigScript = np.loadtxt(path_Txt, delimiter=',')
sigScript_org = sigScript
sigScript_org_R = sigScript[:, 0]
sigScript_org = sigScript_org[:, 1]
# Read EMG
print('Loading EEG file ... Please wait')
if (self.path_to_EMG[-4:] == 'vhdr' or self.path_to_EMG[-4:] == 'VHDR'):
EMG_data = mne.io.read_raw_brainvision(self.path_to_EMG , preload = True)
elif (self.path_to_EMG[-3:] == 'edf' or self.path_to_EMG[-3:] == 'EDF'):
EMG_data = mne.io.read_raw_edf(self.path_to_EMG, preload = True)
print('EEG and EMG imported successfully')
# Read annotations
# Reading sampling frequencies
EMG_sf = int(EMG_data.info['sfreq'])
EEG_sf = 256
print(f'samlping frequency of EMG and EEG are: {EMG_sf} , {EEG_sf} Hz, respectively ... ')
if EEG_sf < EMG_sf:
print(f'resampling EMG to {EEG_sf} Hz ...')
EMG_data.resample(EEG_sf)
EMG_data_get = EMG_data.get_data()
EMG_data_get1 = EMG_data_get[0,:] * 1e6
EMG_data_get2 = EMG_data_get[1,:] * 1e6
EMG_data_get3 = EMG_data_get1 - EMG_data_get2
#Filtering
sigScript_org = self.butter_bandpass_filter(data = sigScript_org, lowcut=10, highcut=100, fs = 256, order = 2)
EMG_data_get1 = self.butter_bandpass_filter(data = EMG_data_get1, lowcut=10, highcut=100, fs = 256, order = 2)
EMG_data_get2 = self.butter_bandpass_filter(data = EMG_data_get2, lowcut=10, highcut=100, fs = 256, order = 2)
EMG_data_get3 = self.butter_bandpass_filter(data = EMG_data_get3, lowcut=10, highcut=100, fs = 256, order = 2)
t_sync = np.arange(time_sync_event[0] - 256*5, time_sync_event[0] + 256*70)
# notch filtering
notch_freq = 50 # set the notch frequency to 50 Hz
# Create the notch filter
q = 30 # quality factor
b, a = signal.iirnotch(notch_freq, q, EEG_sf)
# Apply the notch filter to the data
EMG_data_get1 = signal.filtfilt(b, a, EMG_data_get1, axis=0)
EMG_data_get2 = signal.filtfilt(b, a, EMG_data_get2, axis=0)
EMG_data_get3 = signal.filtfilt(b, a, EMG_data_get3, axis=0)
# Truncate sync period
try:
EEG_to_sync_period = sigScript_org[t_sync]
EMG_to_sync_period1 = EMG_data_get1[t_sync]
EMG_to_sync_period2 = EMG_data_get2[t_sync]
EMG_to_sync_period3 = EMG_data_get3[t_sync]
except IndexError:
messagebox.showerror("Dreamento", "IndexError: t_sync not long enough for sync ... Either select another event or adjust t_sync ")
# Rectified signal
EEG = EEG_to_sync_period
EMG1 = EMG_to_sync_period1
EMG2 = EMG_to_sync_period2
EMG3 = EMG_to_sync_period3
# Normalizing signals:
scaler = MinMaxScaler()
normalized_EEG = scaler.fit_transform([[i] for i in EEG])
scaler = MinMaxScaler()
normalized_EMG1 = scaler.fit_transform([[i] for i in EMG1])
scaler = MinMaxScaler()
normalized_EMG2 = scaler.fit_transform([[i] for i in EMG2])
scaler = MinMaxScaler()
normalized_EMG3 = scaler.fit_transform([[i] for i in EMG3])
EEG = normalized_EEG
EMG1 = normalized_EMG1
EMG2 = normalized_EMG2
EMG3 = normalized_EMG3
EEG_Abs = abs(normalized_EEG)
EMG_Abs1 = abs(normalized_EMG1)
EMG_Abs2 = abs(normalized_EMG2)
EMG_Abs3 = abs(normalized_EMG3)
MA_EEG = self.MA(EEG_Abs, 512)
MA_EMG1 = self.MA(EMG_Abs1, 512)
MA_EMG2 = self.MA(EMG_Abs2, 512)
MA_EMG3 = self.MA(EMG_Abs3, 512)
fig, axs = plt.subplots(5, figsize = (10,10))
axs[0].set_title('EEG during sync event')
axs[0].plot(normalized_EEG, color = 'powderblue')
axs[1].set_title('EMG 1 during sync event')
axs[1].plot(normalized_EMG1, color = 'plum')
axs[2].set_title('EMG 2 during sync event')
axs[2].plot(normalized_EMG2, color = 'orchid')
axs[3].set_title('EMG 1 - EMG 2 during sync event')
axs[3].plot(normalized_EMG3, color = 'thistle')
axs[4].set_title('EMG vs EEG (normalized signals)')
axs[4].plot(normalized_EEG, color = 'powderblue')
axs[4].plot(normalized_EMG1, color = 'plum')
axs[4].plot(normalized_EMG2, color = 'orchid')
axs[4].plot(normalized_EMG3, color = 'thistle')
plt.tight_layout()
MsgBox = tk.messagebox.askquestion ('EEG vs EMG synchronization','Look at the data during sync period. Does the data require further synchronization (recommended to sync further)?',icon = 'warning')
plt.show()
if MsgBox == 'no':
messagebox.showinfo("Information",f"OK! Now we proceed with the main analysis ... Please click on OK and wait ...")
self.flag_sync_EEG_EMG = False
if MsgBox == 'yes':
self.flag_sync_EEG_EMG = True
print('Proceeding to synchronization process ...')
while True:
MsgBox = tk.messagebox.askquestion ('Synchronization?','Proceed to automatic synchronization? For manual sync, press No.',icon = 'warning')
if MsgBox == 'yes':
# close previous plot
plt.close()
fig, axs = plt.subplots(6, figsize = (15,10))
axs[0].plot(EEG_Abs, color = 'powderblue')
axs[0].plot(MA_EEG, color = 'olive', linewidth=3)
axs[0].set_title('EEG', fontsize = 10)
axs[1].plot(EMG_Abs1, color = 'plum')
axs[1].plot(MA_EMG1, color = 'purple', linewidth=3)
axs[1].set_title('EMG1' , fontsize = 10)
axs[2].plot(EMG_Abs2, color = 'orchid')
axs[2].plot(MA_EMG2, color = 'slateblue', linewidth=3)
axs[2].set_title('EMG2', fontsize = 10)
axs[3].plot(EMG_Abs3, color = 'thistle')
axs[3].plot(MA_EMG3, color = 'blueviolet', linewidth=3)
axs[3].set_title('EMG1 - EMG2', fontsize = 10)
# =============================================================================
# x = EEG_Abs
# y = EMG_Abs
# =============================================================================
x = MA_EEG
y = MA_EMG3
N = max(len(x), len(y))
n = min(len(x), len(y))
if N == len(y):
lags = np.arange(-N + 1, n)
else:
lags = np.arange(-n + 1, N)
c = signal.correlate(x / np.std(x), y / np.std(y), 'full')
axs[4].plot(lags, c / n, color='k', label="Cross-correlation")
axs[4].set_title('Cross-correlation')
corr2 = np.correlate(x, y, "full")
lag2 = np.argmax(corr2)
samples_before_begin = lag2 + 1 - len(y)
samples_after_end = len(x) - samples_before_begin - len(y)
index_start_script = samples_before_begin
index_end_script = len(x) - samples_after_end
print(f"samples_before_begin {samples_before_begin}")
print(f"samples_after_end {samples_after_end}")
print(f"index_start_script {index_start_script}")
print(f"index_end_script {index_end_script}")
if samples_before_begin < 0:
axs[5].plot(EEG, color ='powderblue')
axs[5].plot(MA_EEG, color = 'olive', linewidth=3)
axs[5].plot(EMG1[-samples_before_begin:], color = 'plum')
axs[5].plot(MA_EMG1[-samples_before_begin:], color = 'purple', linewidth=3)
axs[5].plot(EMG2[-samples_before_begin:], color = 'orchid')
axs[5].plot(MA_EMG2[-samples_before_begin:], color = 'slateblue', linewidth=3)
axs[5].plot(EMG3[-samples_before_begin:], color = 'thistle')
axs[5].plot(MA_EMG3[-samples_before_begin:], color = 'blueviolet', linewidth=3)
axs[5].set_title('EEG and EMG after sync', fontsize = 10)
self.samples_before_begin_EMG_Dreamento = -samples_before_begin
self.flag_sign_samples_before_begin_EMG_Dreamento = 'eeg_event_earlier'
else:
tmp = np.zeros(samples_before_begin)
synced_EMG1 = np.append(tmp, EMG1)
synced_EMG_MA1 = np.append(tmp, MA_EMG1)
synced_EMG2 = np.append(tmp, EMG2)
synced_EMG_MA2 = np.append(tmp, MA_EMG2)
synced_EMG3 = np.append(tmp, EMG3)
synced_EMG_MA3 = np.append(tmp, MA_EMG3)
axs[5].plot(synced_EMG1, color = 'plum')
axs[5].plot(synced_EMG_MA1, color = 'purple', linewidth=3)
axs[5].plot(synced_EMG2, color = 'orchid')
axs[5].plot(synced_EMG_MA2, color = 'slateblue', linewidth=3)
axs[5].plot(synced_EMG3, color = 'thistle')
axs[5].plot(synced_EMG_MA3, color = 'blueviolet', linewidth=3)
axs[5].plot(EEG, color ='powderblue')
axs[5].plot(MA_EEG, color = 'olive', linewidth=3)
axs[5].set_title('EEG and EMG after sync', fontsize = 10)
self.samples_before_begin_EMG_Dreamento = tmp
self.flag_sign_samples_before_begin_EMG_Dreamento = 'emg_earlier'
plt.tight_layout()
MsgBox = tk.messagebox.askquestion ('Satisfying results?','Are the results satisfying? If not click on No to try again with the other method.',icon = 'warning')
if MsgBox == 'yes':
messagebox.showinfo("Information",f"Perfect! Now we proceed with the main analysis ... Click on OK and wait ...")
plt.show()
break
else:
MsgBox = tk.messagebox.askquestion ('Synchronization?','Do you want to manually synchronize data?',icon = 'warning')
if MsgBox == 'yes':
# close previous plot
plt.close()
# Truncate sync period
self.EEG = EEG
self.EMG1 = EMG1
self.EMG2 = EMG2
self.EMG3 = EMG3
# Rectified signal
self.EEG_Abs = EEG_Abs
self.EMG_Abs1 = EMG_Abs1
self.EMG_Abs2 = EMG_Abs2
self.EMG_Abs3 = EMG_Abs3
# defining points to be selected by cursor for sync
self.points = []
self.n = 2
self.fig, self.axs = plt.subplots(5 ,figsize=(15, 10))
line = self.axs[0].plot(self.EEG, picker=2, color = 'powderblue')
self.axs[0].set_title('Manual drift estimation ... \nPlease click on two points to create the estimate line ...')
self.MA_EEG = MA_EEG
self.MA_EMG1 = MA_EMG1
self.MA_EMG2 = MA_EMG2
self.MA_EMG3 = MA_EMG3
self.axs[0].set_xlim([0,len(self.EMG1)])
self.axs[1].set_xlim([0,len(self.EMG1)])
self.axs[2].set_xlim([0,len(self.EMG1)])
self.axs[3].set_xlim([0,len(self.EMG1)])
self.axs[4].set_xlim([0,len(self.EMG1)])
self.axs[0].set_ylabel('Lag (samples)')
line = self.axs[1].plot(self.EMG_Abs1, picker=2, color = 'plum')
line = self.axs[2].plot(self.EMG_Abs2, picker=2, color = 'orchid')
line = self.axs[3].plot(self.EMG_Abs3, picker=2, color = 'thistle')
plt.tight_layout()
plt.show()
self.fig.canvas.mpl_connect('pick_event', self.onpick)
MsgBox = tk.messagebox.askquestion ('Satisfying results?','Are the results satisfying? If not click on No to try again with the other method.',icon = 'warning')
if MsgBox == 'yes':
messagebox.showinfo("Information",f"Perfect! Now we proceed with the main analysis ... Please click on OK and wait ...")
plt.show()
break
self.ppg_path = hypnodyne_files_list[0].split('EEG')[0] + 'OXY_IR_AC.edf'
self.ppg_obj = mne.io.read_raw_edf(self.ppg_path)
self.ppg_data = self.ppg_obj.get_data()[0]
self.ppg_data = self.butter_bandpass_filter(data = self.ppg_data, lowcut=.3, highcut=100, fs = 256, order = 2)
# Loading all available data
self.noise_path = hypnodyne_files_list[0].split('EEG')[0] + 'NOISE.edf'
self.noise_obj = mne.io.read_raw_edf(self.noise_path)
self.noise_data = self.noise_obj.get_data()[0]
# Acc
self.acc_x_path = hypnodyne_files_list[0].split('EEG')[0] + 'dX.edf'
self.acc_y_path = hypnodyne_files_list[0].split('EEG')[0] + 'dY.edf'
self.acc_z_path = hypnodyne_files_list[0].split('EEG')[0] + 'dZ.edf'
self.acc_x_obj = mne.io.read_raw_edf(self.acc_x_path)
self.acc_y_obj = mne.io.read_raw_edf(self.acc_y_path)
self.acc_z_obj = mne.io.read_raw_edf(self.acc_z_path)
self.acc_x = self.acc_x_obj.get_data()[0]
self.acc_y = self.acc_y_obj.get_data()[0]
self.acc_z = self.acc_z_obj.get_data()[0]
data = mne.io.read_raw_edf(self.HDRecorderRecording)
raw_data = data.get_data()
self.sigHDRecorder = np.ravel(raw_data)
data_r = mne.io.read_raw_edf(self.HDRecorderRecording.split('EEG L.edf')[0] + 'EEG R.edf')
raw_data_r = data_r.get_data()
self.sigHDRecorder_r = np.ravel(raw_data_r)
print('Acceleration and noise data imported successfully ...')
self.samples_before_begin, self.sigHDRecorder_org_synced, self.sigScript_org, self.sigScript_org_R = self.calculate_lag(
plot=(int(self.plot_sync_output.get()) ==1) , path_EDF=self.HDRecorderRecording,\
path_Txt=self.ZmaxDondersRecording,\
T = 30,\
t_start_sync = 100,\
t_end_sync = 200)
print('The lag between Dreamento and Hypndoyne EEG computed ...')
# Filter?
if int(self.is_filtering.get()) == 1:
print('Bandpass filtering (.3-30 Hz) started')
self.sigScript_org = self.butter_bandpass_filter(data = self.sigScript_org, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigScript_org_R = self.butter_bandpass_filter(data = self.sigScript_org_R, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigHDRecorder = self.butter_bandpass_filter(data = self.sigHDRecorder, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigHDRecorder_r = self.butter_bandpass_filter(data = self.sigHDRecorder_r, lowcut=.3, highcut=30, fs = 256, order = 2)
print('Band-pass filter applied to data ...')
else:
print('No filtering applied ...')
# Plot psd?
if int(self.plot_psd.get()) == 1:
print('plotting peridogram ...')
self.plot_welch_periodogram(data = self.sigScript_org, sf = 256, win_size = 5)
print('PSD plotted ...')
# Plot EMG as well?
print('Loading the main window of Dreamento ... Please wait')
if int(self.analysis_signal_options.get()) == 1:
fig, markers_details = self.AssignMarkersToRecordedData_EEG_TFR(data = self.sigScript_org, data_R = self.sigScript_org_R, sf = 256,\
path_to_json_markers=self.path_to_json_markers,EMG_path=self.path_to_EMG,\
markers_to_show = ['light', 'manual', 'sound'],\
win_sec=30, fmin=0.5, fmax=25,\
trimperc=5, cmap='RdBu_r', add_colorbar = False)
else:
fig, markers_details = self.AssignMarkersToRecordedData_EEG_TFR_noEMG(data = self.sigScript_org, data_R = self.sigScript_org_R, sf = 256,\
path_to_json_markers=self.path_to_json_markers,\
markers_to_show = ['light', 'manual', 'sound'],\
win_sec=30, fmin=0.5, fmax=25,\
trimperc=5, cmap='RdBu_r', add_colorbar = False)
# Activate save section
if int(self.is_autoscoring.get()) == 1:
self.create_save_options_autoscoring()
# Create export option
self.create_save_options()
#### IF brainproducts analysis:
elif int(self.analysis_signal_options.get()) == 4:
print(f'selected option is {self.analysis_signal_options.get()}')
# Sanitary checks
if 'EMG_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but the .vhdr file has not been loaded!")
else:
EMG_files_list
print(f'Analyzing BrainProducts data from {EMG_files_list}')
self.path_to_EMG = EMG_files_list[0]
self.raw_BrainProducts = mne.io.read_raw_brainvision(self.path_to_EMG, preload = True)
self.fs_BrainProducts = self.raw_BrainProducts.info['sfreq']
print(f'Sampling frequency is {self.fs_BrainProducts} Hz')
self.ch_names_BrainProducts = self.raw_BrainProducts.info['ch_names']
self.BrainProducts_channel_selector()
################ AUTOSCORING
# Concatenate the new channel with the original raw data
desired_EEG_channels_autoscoring = [str(self.autoscoring_BrainProducts_EEG_main_val.get()), str(self.autoscoring_BrainProducts_EEG_ref_val.get())]
desired_EOG_channels_autoscoring = [str(self.autoscoring_BrainProducts_EOG_main_val.get()), str(self.autoscoring_BrainProducts_EOG_ref_val.get())]
desired_EMG_channels_autoscoring = [str(self.autoscoring_BrainProducts_EMG_main_val.get()), str(self.autoscoring_BrainProducts_EMG_ref_val.get())]
desired_channels_autoscoring = desired_EEG_channels_autoscoring + desired_EOG_channels_autoscoring + desired_EMG_channels_autoscoring
ch_indices_autoscoring = [self.raw_BrainProducts.ch_names.index(ch) for ch in desired_channels_autoscoring]
# Compute the derivation data
derivation_data_eeg_autoscoring = self.raw_BrainProducts.get_data(picks=ch_indices_autoscoring[0]) - self.raw_BrainProducts.get_data(picks=ch_indices_autoscoring[1])
derivation_data_eog_autoscoring = self.raw_BrainProducts.get_data(picks=ch_indices_autoscoring[2]) - self.raw_BrainProducts.get_data(picks=ch_indices_autoscoring[3])
derivation_data_emg_autoscoring = self.raw_BrainProducts.get_data(picks=ch_indices_autoscoring[4]) - self.raw_BrainProducts.get_data(picks=ch_indices_autoscoring[5])
# Create an info object for the derivation channel
derivation_info_EEG_autoscoring = mne.create_info(['EEG ' + desired_channels_autoscoring[0] + '-' + desired_channels_autoscoring[1]], self.raw_BrainProducts.info['sfreq'], ch_types='eeg')
derivation_info_EOG_autoscoring = mne.create_info(['EOG ' + desired_channels_autoscoring[2] + '-' + desired_channels_autoscoring[3]], self.raw_BrainProducts.info['sfreq'], ch_types='eog')
derivation_info_EMG_autoscoring = mne.create_info(['EMG ' + desired_channels_autoscoring[4] + '-' + desired_channels_autoscoring[5]], self.raw_BrainProducts.info['sfreq'], ch_types='emg')
# Create an Evoked object for the derivation data
derivation_evoked_EEG_autoscoring = mne.io.RawArray(data=derivation_data_eeg_autoscoring, info=derivation_info_EEG_autoscoring)
derivation_evoked_EOG_autoscoring = mne.io.RawArray(data=derivation_data_eog_autoscoring, info=derivation_info_EOG_autoscoring)
derivation_evoked_EMG_autoscoring = mne.io.RawArray(data=derivation_data_emg_autoscoring, info=derivation_info_EMG_autoscoring)
# Adding new derivations
self.raw_BrainProducts.add_channels([derivation_evoked_EEG_autoscoring], force_update_info=True)
self.raw_BrainProducts.add_channels([derivation_evoked_EOG_autoscoring], force_update_info=True)
self.raw_BrainProducts.add_channels([derivation_evoked_EMG_autoscoring], force_update_info=True)
################ PLOTTING
desired_EEG_channels_plotting = [str(self.plotting_BrainProducts_EEG_main_val.get()), str(self.plotting_BrainProducts_EEG_ref_val.get())]
desired_EOG_channels_plotting = [str(self.plotting_BrainProducts_EOG_main_val.get()), str(self.plotting_BrainProducts_EOG_ref_val.get())]
desired_EMG_channels_plotting = [str(self.plotting_BrainProducts_EMG_main_val.get()), str(self.plotting_BrainProducts_EMG_ref_val.get())]
desired_channels_plotting = desired_EEG_channels_plotting + desired_EOG_channels_plotting + desired_EMG_channels_plotting
ch_indices_plotting = [self.raw_BrainProducts.ch_names.index(ch) for ch in desired_channels_plotting]
# Compute the derivation data
derivation_data_eeg_plotting = self.raw_BrainProducts.get_data(picks=ch_indices_plotting[0]) - self.raw_BrainProducts.get_data(picks=ch_indices_plotting[1])
derivation_data_eog_plotting = self.raw_BrainProducts.get_data(picks=ch_indices_plotting[2]) - self.raw_BrainProducts.get_data(picks=ch_indices_plotting[3])
derivation_data_emg_plotting = self.raw_BrainProducts.get_data(picks=ch_indices_plotting[4]) - self.raw_BrainProducts.get_data(picks=ch_indices_plotting[5])
# Create an info object for the derivation channel
derivation_info_EEG_plotting = mne.create_info(['EEG ' + desired_channels_plotting[0] + '-' + desired_channels_plotting[1]], self.raw_BrainProducts.info['sfreq'], ch_types='eeg')
derivation_info_EOG_plotting = mne.create_info(['EOG ' + desired_channels_plotting[2] + '-' + desired_channels_plotting[3]], self.raw_BrainProducts.info['sfreq'], ch_types='eog')
derivation_info_EMG_plotting = mne.create_info(['EMG ' + desired_channels_plotting[4] + '-' + desired_channels_plotting[5]], self.raw_BrainProducts.info['sfreq'], ch_types='emg')
# Create an Evoked object for the derivation data
derivation_evoked_EEG_plotting = mne.io.RawArray(data=derivation_data_eeg_plotting, info=derivation_info_EEG_plotting)
derivation_evoked_EOG_plotting = mne.io.RawArray(data=derivation_data_eog_plotting, info=derivation_info_EOG_plotting)
derivation_evoked_EMG_plotting = mne.io.RawArray(data=derivation_data_emg_plotting, info=derivation_info_EMG_plotting)
# Adding new derivations
self.raw_BrainProducts.add_channels([derivation_evoked_EEG_plotting], force_update_info=True)
self.raw_BrainProducts.add_channels([derivation_evoked_EOG_plotting], force_update_info=True)
self.raw_BrainProducts.add_channels([derivation_evoked_EMG_plotting], force_update_info=True)
print('channels have been added')
print('Initiating YASA sleep staging ...')
sls = yasa.SleepStaging(self.raw_BrainProducts, eeg_name=derivation_info_EEG_autoscoring.ch_names[0],\
eog_name = derivation_info_EOG_autoscoring.ch_names[0],\
emg_name = derivation_info_EMG_autoscoring.ch_names[0])
self.hypno_pred = sls.predict() # Predict the sleep stages
self.hypno_pred = yasa.hypno_str_to_int(self.hypno_pred) # Convert "W" to 0, "N1" to 1, etc
print(f'prediction hypnogram of size {np.shape(self.hypno_pred)} has been made!')
# Plotting
print('preparing the TFR ...')
old_fontsize = plt.rcParams['font.size']
plt.rcParams.update({'font.size': 12})
#plt.rcParams["figure.autolayout"] = True
# Initialize lists to store channel indices for EMG and non-EMG channels
emg_channels = []
ecg_channels = []
exg_channels = []
other_channels = []
# Separate EMG and non-EMG channels based on the channel name
for idx, channel_name in enumerate(self.ch_names_BrainProducts):
if channel_name.startswith('EMG'):
emg_channels.append(idx)
print(f'Channel {idx} detected as EMG --> filtering 10 - 100 Hz')
elif channel_name.startswith('ECG'):
ecg_channels.append(idx)
print(f'Channel {idx} detected as ECG --> no filtering applied')
elif channel_name.startswith('x'):
exg_channels.append(idx)
print(f'Channel {idx} detected as ExG --> no filtering applied')
else:
other_channels.append(idx)
print(f'Channel {idx} detected as non-EMG --> filtering .1 - 40 Hz')
# Apply frequency filters directly to the Raw object
self.raw_BrainProducts.filter(l_freq=10, h_freq=100, picks=emg_channels)
self.raw_BrainProducts.filter(l_freq=0.3, h_freq=40, picks=other_channels)
data_TFR = self.raw_BrainProducts.get_data()[ch_indices_plotting[0], :] - self.raw_BrainProducts.get_data()[ch_indices_plotting[1], :]
data_plot1 = data_TFR
data_plot2 = self.raw_BrainProducts.get_data()[ch_indices_plotting[2], :] - self.raw_BrainProducts.get_data()[ch_indices_plotting[3], :]
data_plot3 = self.raw_BrainProducts.get_data()[ch_indices_plotting[4], :] - self.raw_BrainProducts.get_data()[ch_indices_plotting[5], :]
data_autoscoring1 = self.raw_BrainProducts.get_data()[ch_indices_autoscoring[0], :] - self.raw_BrainProducts.get_data()[ch_indices_autoscoring[1], :]
data_autoscoring2 = self.raw_BrainProducts.get_data()[ch_indices_autoscoring[2], :] - self.raw_BrainProducts.get_data()[ch_indices_autoscoring[3], :]
data_autoscoring3 = self.raw_BrainProducts.get_data()[ch_indices_autoscoring[4], :] - self.raw_BrainProducts.get_data()[ch_indices_autoscoring[5], :]
win_sec=30
fmin=0.3
fmax=40
trimperc=5
cmap='RdBu_r'
add_colorbar = False
sf = int(self.fs_BrainProducts)
print(f'sf = {sf}')
assert isinstance(data_TFR, np.ndarray), 'data_TFR must be a 1D NumPy array.'
assert isinstance(sf, (int, float)), 'sf must be int or float.'
assert data_TFR.ndim == 1, 'data_TFR must be a 1D (single-channel) NumPy array.'
assert isinstance(win_sec, (int, float)), 'win_sec must be int or float.'
assert isinstance(fmin, (int, float)), 'fmin must be int or float.'
assert isinstance(fmax, (int, float)), 'fmax must be int or float.'
assert fmin < fmax, 'fmin must be strictly inferior to fmax.'
assert fmax < sf / 2, 'fmax must be less than Nyquist (sf / 2).'
# Calculate multi-taper spectrogram
nperseg = int(10 * sf) #int(win_sec * sf / 8)
assert data_TFR.size > 2 * nperseg, 'data_TFR length must be at least 2 * win_sec.'
f, t, Sxx = spectrogram_lspopt(data_TFR, sf, nperseg=nperseg, noverlap=0)
Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs = np.logical_and(f >= fmin, f <= fmax)
Sxx = Sxx[good_freqs, :]
f = f[good_freqs]
#t *= 256 # Convert t to hours
# Normalization
vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc])
norm = Normalize(vmin=vmin, vmax=vmax)
fig,AX = plt.subplots(nrows=8, figsize=(16, 9), gridspec_kw={'height_ratios': [3,2,2,2,2,2,2,2]})
ax1 = plt.subplot(8,1,1,)
ax_autoscoring = plt.subplot(8,1,2, )
ax2 = plt.subplot(8,1,3, )
ax3 = plt.subplot(8,1,4, sharex = ax2)
ax4 = plt.subplot(8,1,5, sharex = ax2)
ax5 = plt.subplot(8,1,6, sharex = ax2)
ax6 = plt.subplot(8,1,7, sharex = ax2)
ax7 = plt.subplot(8,1,8, sharex = ax2)
ax1.set_title('Dreamento: BrainProducts Post-processing ')
im = ax1.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
self.plot_hypnogram(hypno = self.hypno_pred, axis= ax_autoscoring, sf_hypno=1/30, lw=2)
ax1.set_xlim([0, len(data_TFR)/sf])
ax1.set_ylim((fmin, 35))
ax1.set_ylabel('Frequency [Hz]')
# =============================================================================
# ax2.plot(np.arange(len(data_plot1))/sf, data_plot1, color = 'plum')
# =============================================================================
ax2.set_ylabel(str(desired_channels_plotting[0]) + '-' + str(desired_channels_plotting[1]))
ax3.plot(np.arange(len(data_plot2))/sf, data_plot2, color = 'blue')
ax3.set_ylabel(str(desired_channels_plotting[2]) + '-' + str(desired_channels_plotting[3]))
ax4.plot(np.arange(len(data_plot3))/sf, data_plot3, color = 'red')
ax4.set_ylabel(str(desired_channels_plotting[4]) + '-' + str(desired_channels_plotting[5]))
# =============================================================================
# ax5.plot(np.arange(len(data_autoscoring1))/sf, data_autoscoring1, color = 'green')
# =============================================================================
ax5.set_ylabel(str(desired_channels_autoscoring[0]) + '-' + str(desired_channels_autoscoring[1]))
ax6.plot(np.arange(len(data_autoscoring2))/sf, data_autoscoring2, color = 'black')
ax6.set_ylabel(str(desired_channels_autoscoring[2]) + '-' + str(desired_channels_autoscoring[3]))
ax7.plot(np.arange(len(data_autoscoring3))/sf, data_autoscoring3, color = 'purple')
ax7.set_ylabel(str(desired_channels_autoscoring[4]) + '-' + str(desired_channels_autoscoring[5]))
ax2.set_xlim((0, 30))
# =============================================================================
# ax2.set_ylim((-1e-4, 1e-4))
# ax3.set_ylim((-1e-4, 1e-4))
# ax4.set_ylim((-1e-4, 1e-4))
# ax5.set_ylim((-1e-4, 1e-4))
# ax6.set_ylim((-1e-4, 1e-4))
# =============================================================================
ax1.get_xaxis().set_visible(False)
ax3.get_xaxis().set_visible(False)
ax2.set_ylim((-75e-6, 75e-6))
ax3.set_ylim((-75e-6, 75e-6))
ax4.set_ylim((-75e-6, 75e-6))
ax5.set_ylim((-75e-6, 75e-6))
ax6.set_ylim((-75e-6, 75e-6))
ax7.set_ylim((-75e-6, 75e-6))
fig.canvas.mpl_connect('button_press_event', self.onclick_BrainProdcuts)
fig.canvas.mpl_connect('key_press_event', self.pan_nav_BrainProducts)
self.up_sampled_predicted_hypno = yasa.hypno_upsample_to_data(hypno= self.hypno_pred, sf_hypno = 1/30, data=data_TFR, sf_data=self.fs_BrainProducts, verbose=True)
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = self.raw_BrainProducts.get_data()[ch_indices_autoscoring[2], :] * 1e6
roc = self.raw_BrainProducts.get_data()[ch_indices_autoscoring[3], :] * 1e6
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=self.fs_BrainProducts, hypno=self.up_sampled_predicted_hypno, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.path_to_EMG.split('.vhdr')[0] + '_REM_results.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic SO & spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data_TFR * 1e6, sf=self.fs_BrainProducts, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=remove_outliers_SO_spd, verbose=False)
spindle_events_R = yasa.spindles_detect(data_autoscoring1 * 1e6, sf=self.fs_BrainProducts, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=remove_outliers_SO_spd, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data_TFR * 1e6 * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_autoscoring1 * 1e6* mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data_TFR * 1e6, sf=self.fs_BrainProducts, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_autoscoring1 * 1e6 , sf=self.fs_BrainProducts, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data_TFR * 1e6 * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_autoscoring1 * 1e6 * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.path_to_EMG.split('.vhdr')[0] + '_spd_' + str(desired_channels_plotting[0]) + '-' + str(desired_channels_plotting[1])+ '.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.path_to_EMG.split('.vhdr')[0] + '_spd_' + str(desired_channels_autoscoring[0]) + '-' + str(desired_channels_autoscoring[1])+ '.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.path_to_EMG.split('.vhdr')[0] + '_SO' + str(desired_channels_autoscoring[0]) + '-' + str(desired_channels_autoscoring[1])+ '.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.path_to_EMG.split('.vhdr')[0] + '_SO_' + str(desired_channels_autoscoring[0]) + '-' + str(desired_channels_autoscoring[1])+ '.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax2.plot(np.arange(len(data_TFR))/self.fs_BrainProducts, data_TFR, color = 'slategrey', linewidth = 1)
ax5.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, data_autoscoring1, color = 'black', linewidth = 1)
else:
ax2.plot(np.arange(len(data_TFR))/self.fs_BrainProducts, data_TFR, color = (160/255, 70/255, 160/255), linewidth = 1)
ax5.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, data_autoscoring1, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax6.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, loc_highlight* 1e-6, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax2.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, SO_L_highlight* 1e-6, 'cyan')
ax5.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, SO_R_highlight* 1e-6, 'cyan')
ax2.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, spindles_L_highlight* 1e-6, 'green')
ax5.plot(np.arange(len(data_autoscoring1))/self.fs_BrainProducts, spindles_R_highlight* 1e-6, 'green')
if int(self.checkbox_save_YASA_autoscoring_val.get()) == 1:
np.savetxt(self.path_to_EMG.split('.vhdr')[0] + 'Y_ASA_autoscoring_Dreamento_API.txt', self.hypno_pred, fmt='%d')
print(f'request {int(self.automatic_spd_event_deetction.get())}')
#%% Manual sync function
def onpick(self,event):
point1_x = []
point2_x = []
point1_y = []
point2_y = []
if len(self.points) < self.n:
thisline = event.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = event.ind
point = tuple(zip(xdata[ind], ydata[ind]))
self.points.append(point)
print('onpick point:', point)
print(f'You already chose {len(self.points)} points')
if len(self.points) == self.n :
print('done')
for i in self.points[0]:
point1_x.append(i[0])
point1_y.append(i[1])
for i in self.points[1]:
point2_x.append(i[0])
point2_y.append(i[1])
mean_x1 = np.mean(point1_x)
mean_x2 = np.mean(point2_x)
mean_y1 = np.mean(point1_y)
mean_y2 = np.mean(point2_y)
if mean_x1 > mean_x2:
self.axs[4].plot(self.EEG, color = 'powderblue')
self.axs[4].plot(self.MA_EEG, color = 'olive', linewidth = 2)
tmp_sync = np.zeros(int(mean_x1-mean_x2))
self.synced_EMG1 = np.append(tmp_sync, self.EMG1)
self.synced_MA_EMG1 = np.append(tmp_sync, self.MA_EMG1)
self.synced_EMG2 = np.append(tmp_sync, self.EMG2)
self.synced_MA_EMG2 = np.append(tmp_sync, self.MA_EMG2)
self.synced_EMG3 = np.append(tmp_sync, self.EMG3)
self.synced_MA_EMG3 = np.append(tmp_sync, self.MA_EMG3)
self.axs[4].plot(self.synced_EMG1, color = 'plum')
self.axs[4].plot(self.synced_MA_EMG1, color = 'purple', linewidth = 2)
self.axs[4].plot(self.synced_EMG2, color = 'orchid')
self.axs[4].plot(self.synced_MA_EMG2, color = 'blueviolet', linewidth = 2)
self.axs[4].plot(self.synced_EMG3, color = 'thistle')
self.axs[4].plot(self.synced_MA_EMG3, color = 'slateblue', linewidth = 2)
n_sample_sync = len(tmp_sync)
self.samples_before_begin_EMG_Dreamento = tmp_sync
self.flag_sign_samples_before_begin_EMG_Dreamento = 'emg_event_earlier'
if mean_x1 < mean_x2:
tmp_sync = int(mean_x2-mean_x1)
self.axs[4].plot(self.EEG, color = 'powderblue')
self.axs[4].plot(self.MA_EEG, color = 'olive', linewidth = 2)
self.axs[4].plot(self.EMG1[tmp_sync:], color = 'plum')
self.axs[4].plot(self.MA_EMG1[tmp_sync:], color = 'purple', linewidth = 2)
self.axs[4].plot(self.EMG2[tmp_sync:], color = 'plum')
self.axs[4].plot(self.MA_EMG2[tmp_sync:], color = 'purple', linewidth = 2)
self.axs[4].plot(self.EMG3[tmp_sync:], color = 'plum')
self.axs[4].plot(self.MA_EMG3[tmp_sync:], color = 'purple', linewidth = 2)
self.samples_before_begin_EMG_Dreamento = tmp_sync
self.flag_sign_samples_before_begin_EMG_Dreamento = 'eeg_event_earlier'
#self.axs[1].plot([mean_x1, mean_x2], [mean_y1, mean_y2], linewidth = 3, color = 'plum')
self.fig.canvas.draw()
#return drift_estimate
return self.points
#%% Save section
def create_save_options(self):
"""
Store all the raw and processed variables to be used in Matlab
:param self: access the attributes and methods of the class
:returns: user_defined_name.mat
"""
# Label: Save outcome
self.label_save_path = Label(self.frame_import, text = "Saving path:",
font = 'Calibri 11 ')
self.label_save_path.grid(row = 6 , column = 0)#, padx = 15, pady = 10)
# Define browse button to import hypnos
self.button_save_browse = Button(self.frame_import, text = "Browse ...",
padx = 40, pady = 10, font = 'Calibri 12 ',
command = self.save_path_finder,fg = 'blue',
relief = RIDGE)
self.button_save_browse.grid(row = 7, column = 0)#, padx = 15, pady = 10)
# Label: Save name
self.label_save_filename = Label(self.frame_import, text = "Saving filename:",
font = 'Calibri 11 ')
self.label_save_filename.grid(row = 6 , column = 1)#)#, padx = 15, pady = 10)
# Create entry for user
self.entry_save_name = Entry(self.frame_import,text = " enter filename.mat ")#, borderwidth = 2, width = 10)
self.entry_save_name.grid(row = 7, column = 1)#)#, padx = 15, pady = 10)
self.button_save_mat = Button(self.frame_import, text = "Save",#, padx = 40, pady=8,
font = 'Calibri 11 bold', relief = RIDGE, fg = 'blue',
command = self.save_results_button)
self.button_save_mat.grid(row = 7 , column =2)#, padx = 15, pady = 10)
#%% create save options for autoscoring
def create_save_options_autoscoring(self):
"""
Store the scoring results and the sleep metrics as a txt file
:param self: access the attributes and methods of the class
:returns: user_defined_name.mat
"""
# Label: Save outcome
self.label_save_path_autoscoring = Label(self.frame_import, text = "Path to save autoscoring results:",
font = 'Calibri 11 ')
self.label_save_path_autoscoring.grid(row = 4 , column = 0)#, padx = 15, pady = 10)
# Define browse button to import hypnos
self.button_save_browse_autoscoring = Button(self.frame_import, text = "Browse ...",
padx = 40, pady = 10, font = 'Calibri 12 ',
command = self.save_path_finder,fg = 'blue',
relief = RIDGE)
self.button_save_browse_autoscoring.grid(row = 5, column = 0)#, padx = 15, pady = 10)
# Label: Save name
self.label_save_filename_autoscoring = Label(self.frame_import, text = "Saving filename:",
font = 'Calibri 11 ')
self.label_save_filename_autoscoring.grid(row = 4 , column = 1)#)#, padx = 15, pady = 10)
# Create entry for user
self.entry_save_name_autoscoring = Entry(self.frame_import,text = "Subject#_night#.txt ")#, borderwidth = 2, width = 10)
self.entry_save_name_autoscoring.grid(row = 5, column = 1)#)#, padx = 15, pady = 10)
self.button_save_mat_autoscoring = Button(self.frame_import, text = "Save",#, padx = 40, pady=8,
font = 'Calibri 11 bold', relief = RIDGE, fg = 'blue',
command = self.save_autoscoring_button)
self.button_save_mat_autoscoring.grid(row = 5 , column =2)#, padx = 15, pady = 10)
#%% ################### DEFINE FUNCTIONS OF BUTTON(S) #######################
#%% Save autoscorrig button
def save_autoscoring_button(self):
if self.entry_save_name_autoscoring.get()[-4:] == '.txt':
saving_dir = where_to_save_path + '/' + self.entry_save_name_autoscoring.get()
else:
saving_dir = where_to_save_path + '/' + self.entry_save_name_autoscoring.get() + '.txt'
a_file = open(saving_dir, "w")
a_file.write('=================== Dreamento: an open-source dream engineering toolbox! ===================\nhttps://github.com/dreamento/dreamento')
a_file.write('\nThis file has been autoscored by DreamentoScorer! \nSleep stages: Wake:0, N1:1, N2:2, SWS:3, REM:4.\n')
a_file.write('============================================================================================\n')
for row in self.stage_autoscoring[:,np.newaxis]:
np.savetxt(a_file, row, fmt='%1.0f')
a_file.close()
# Save sleep metrics
with open(saving_dir[:-4]+'_metrics.txt', 'w') as convert_file:
convert_file.write(json.dumps(self.stats))
messagebox.showinfo(title = "Done!", message = f'Autoscoring results successfully saved in {saving_dir}!')
#%% Function: Import EDF (Browse
def load_hypnodyne_file_dialog(self):
"""
The function to load the EEG.L file from the HDrecorder.
:param self: access the attributes and methods of the class
:returns: global hypnodyne_files_list
"""
global hypnodyne_files_list
if platform.system() == "Windows":
self.filenames = filedialog.askopenfilenames(title = 'select EEG L.edf file',
filetype = (("EEG L edf", "*.edf"), ("All Files", "*.*")))
else:
self.filenames = filedialog.askopenfilenames(title = 'select EEG L.edf file',
filetypes = (("EEG L edf", "*.edf"), ("All Files", "*.*")))
# Make a list of imported file names (full path)
hypnodyne_files_list = self.frame_import.tk.splitlist(self.filenames)
self.n_data_files = len(hypnodyne_files_list)
# check if the user chose somthing
if not hypnodyne_files_list:
self.label_data = Label(self.frame_import, text = "No file has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 2, column = 0)
else:
self.label_data = Label(self.frame_import, text = "The EDF files has been loaded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 2, column = 0)
#%% Function: Import Hypnogram (Browse)
def load_Dreamento_file_dialog(self):
"""
Loading the .txt file recorded by Dreamento.
:param self: access the attributes and methods of the class
:returns: global Dreamento_files_list
"""
global Dreamento_files_list
if platform.system() == "Windows":
self.filenames = filedialog.askopenfilenames(title = 'select label files',
filetype = (("txt", "*.txt"),("csv", "*.csv"), ("All Files", "*.*")))
else:
self.filenames = filedialog.askopenfilenames(title = 'select label files',
filetypes = (("txt", "*.txt"),("csv", "*.csv"), ("All Files", "*.*")))
Dreamento_files_list = self.frame_import.tk.splitlist(self.filenames)
self.n_label_files = len(Dreamento_files_list)
# check if the user chose somthing
if not Dreamento_files_list:
self.label_labels = Label(self.frame_import, text = "No hypnogram has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 2, column = 1)
else:
self.label_labels = Label(self.frame_import, text ="The Dreamento data file has been loaded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 2, column = 1)
#%% Load manual scoring1
def import_manual_scorings(self):
if platform.system() == "Windows":
self.filename_scorings = filedialog.askopenfilenames(title = 'txt file of the scorings',
filetype = (("txt", "*.txt"), ("All Files", "*.*")))
else:
self.filename_scorings = filedialog.askopenfilenames(title = 'txt file of the scorings',
filetypes = (("txt", "*.txt"), ("All Files", "*.*")))
self.path_to_manual_scorings_label.config(text=f'Path to scorings: {self.filename_scorings}')
self.popupWin_ImportManualScoring.lift()
# Make a list of imported file names (full path)
self.filename_scoring_paths = self.popupWin_ImportManualScoring.tk.splitlist(self.filename_scorings)
print(f'the following files have been imported: {self.filename_scoring_paths}')
# check if the user chose somthing
if not self.filename_scoring_paths:
self.label_nofile = Label(self.popupWin_ImportManualScoring, text = "No file has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 2, column = 0)
else:
self.label_data = Label(self.popupWin_ImportManualScoring, text = "The scoring files have been loaded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 2, column =1)
if len(self.filename_scoring_paths) >3:
self.label_more_than_3files = Label(self.popupWin_ImportManualScoring, text = "Dreamento doesn't support more than 3 scorings consensus! reload up to 3 scorings!",
fg = 'orange', font = 'Helvetica 9 bold').grid(row = 3, column = 0)
#%% Function: Import Hypnogram (Browse)
def load_marker_file_dialog(self):
"""
Load the anntations file (.json) created by Dreamento.
:param self: access the attributes and methods of the class
:returns: global marker_files_list
"""
global marker_files_list
if platform.system() == "Windows":
self.filenames = filedialog.askopenfilenames(title = 'select marker files',
filetype = (("json", "*.json"),("csv", "*.csv"), ("All Files", "*.*")))
else:
self.filenames = filedialog.askopenfilenames(title = 'select marker files',
filetypes = (("json", "*.json"),("csv", "*.csv"), ("All Files", "*.*")))
marker_files_list = self.frame_import.tk.splitlist(self.filenames)
self.n_label_files = len(marker_files_list)
# check if the user chose somthing
if not marker_files_list:
self.label_labels = Label(self.frame_import, text = "No marker (.json) file has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 2, column = 2)
else:
self.label_labels = Label(self.frame_import, text = "The marker (.json) file has been loaded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 2, column = 2)
#%% Function: Import EMG (Browse)
def load_EMG_file_dialog(self):
"""
Load the optional recorded data (e.g.,EMG.vhdr)
:param self: access the attributes and methods of the class
:returns: global EMG_files_list
"""
global EMG_files_list
if platform.system() == "Windows":
self.filenames = filedialog.askopenfilenames(title = 'select EMG file (.vhdr or .edf)',
filetype = (("vhdr", "*.vhdr"),("edf", "*.edf"), ("All Files", "*.*")))
else:
self.filenames = filedialog.askopenfilenames(title = 'select EMG file (.vhdr or .edf)',
filetypes = (("vhdr", "*.vhdr"),("edf", "*.edf"), ("All Files", "*.*")))
EMG_files_list = self.frame_import.tk.splitlist(self.filenames)
self.n_label_files = len(EMG_files_list)
# check if the user chose somthing
if not EMG_files_list:
self.label_labels = Label(self.frame_import, text = "No (.vhdr) file has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 2, column = 3)
else:
self.label_labels = Label(self.frame_import, text = "The (.vhdr) file has been loaded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 2, column = 3)
#%% load txt files including paths to the folders for bulk autoscoring
def browse_txt_for_bulk_autoscoring(self):
"""
Load the autoscoring bulk (.txt) file comprising paths to all the folders to be scored
:param self: access the attributes and methods of the class
"""
if platform.system() == "Windows":
self.filename_paths_for_bulk_autoscoring = filedialog.askopenfilenames(title = 'select a .txt file including paths to the folders to be autoscored',
filetype = (("txt", "*.txt"),("All Files", "*.*")))
else:
self.filename_paths_for_bulk_autoscoring = filedialog.askopenfilenames(title = 'select a .txt file including paths to the folders to be autoscored',
filetypes = (("txt", "*.txt"),("All Files", "*.*")))
self.bulk_autoscoring_files_list = self.popupWin_bulk_autoscoring.tk.splitlist(self.filename_paths_for_bulk_autoscoring)
# check if the user chose somthing
if not self.bulk_autoscoring_files_list:
self.label_no_bulk_scoring = Label(self.popupWin_bulk_autoscoring , text = "No file has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 3, column = 1)
else:
self.label_no_bulk_scoring = Label(self.popupWin_bulk_autoscoring, text = "The (.txt) file has been loaded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 3, column = 1)
#%% load txt files including paths to the folders for bulk autoscoring
def browse_destination_folder_for_bulk_autoscoring(self):
"""
Load the destination folder of autoscoring bulk (.txt) file
:param self: access the attributes and methods of the class
"""
self.destination_bulk_autoscoring_files_list = filedialog.askdirectory()
# check if the user chose somthing
if not self.destination_bulk_autoscoring_files_list:
self.label_no_bulk_scoring_destination = Label(self.popupWin_bulk_autoscoring, text = "No destination folder has been selected!",
fg = 'red', font = 'Helvetica 9 bold').grid(row = 3, column = 2)
else:
self.label_no_bulk_scoring_destination = Label(self.popupWin_bulk_autoscoring, text = "The destination folder has been selectded!",
fg = 'green', font = 'Helvetica 9 bold').grid(row = 3, column = 2)
#%% Function: Import Hypnogram (Browse)
def save_path_finder(self):
"""
Button to define the directory to save the .mat output
:param self: access the attributes and methods of the class
:returns: global where_to_save_path
"""
global where_to_save_path
where_to_save_path = filedialog.askdirectory()
#%% Function: Import Hypnogram (Browse)
def Apply_button(self):
"""
Start the processing, once all the parameters are set.
:param self: access the attributes and methods of the class
:returns display: The main post-processing Dreamento window, saving options
"""
if int(self.analysis_signal_options.get()) == 2:
self.sync_with_dreamento = True
# Sanitary checks
if 'hypnodyne_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The EEG L.edf file is not selected!")
elif 'Dreamento_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The .txt file recorded by Dreamento is not selected!")
elif 'marker_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The .json file of markers is not selected!")
# =============================================================================
# elif 'EMG_files_list' not in globals() and int(self.analysis_signal_options.get()) == 1:
# messagebox.showerror("Dreamento", "Sorry, but no EMG files is loaded, though the plot EMG check box is activated! Change either of these and try again.")
#
# elif str(self.EMG_scale_options_val.get()) == 'Set desired EMG amplitude ...' and int(self.analysis_signal_options.get()) == 1:
# messagebox.showerror("Dreamento", "Sorry, but a parameter is missing ...\nThe EMG amplitude is not set!")
#
# =============================================================================
Dreamento_files_list, hypnodyne_files_list, marker_files_list
self.ZmaxDondersRecording = Dreamento_files_list[0]
self.HDRecorderRecording = hypnodyne_files_list[0]
self.path_to_json_markers = marker_files_list[0]
data = mne.io.read_raw_edf(self.HDRecorderRecording)
raw_data = data.get_data()
self.sigHDRecorder = np.ravel(raw_data)
data_r = mne.io.read_raw_edf(self.HDRecorderRecording.split('EEG L.edf')[0] + 'EEG R.edf')
raw_data_r = data_r.get_data()
self.sigHDRecorder_r = np.ravel(raw_data_r)
self.noise_path = hypnodyne_files_list[0].split('EEG')[0] + 'NOISE.edf'
self.noise_obj = mne.io.read_raw_edf(self.noise_path)
self.noise_data = self.noise_obj.get_data()[0]
self.ppg_path = hypnodyne_files_list[0].split('EEG')[0] + 'OXY_IR_AC.edf'
self.ppg_obj = mne.io.read_raw_edf(self.ppg_path)
self.ppg_data = self.ppg_obj.get_data()[0]
self.ppg_data = self.butter_bandpass_filter(data = self.ppg_data, lowcut=.3, highcut=100, fs = 256, order = 2)
# Acc
self.acc_x_path = hypnodyne_files_list[0].split('EEG')[0] + 'dX.edf'
self.acc_y_path = hypnodyne_files_list[0].split('EEG')[0] + 'dY.edf'
self.acc_z_path = hypnodyne_files_list[0].split('EEG')[0] + 'dZ.edf'
self.acc_x_obj = mne.io.read_raw_edf(self.acc_x_path)
self.acc_y_obj = mne.io.read_raw_edf(self.acc_y_path)
self.acc_z_obj = mne.io.read_raw_edf(self.acc_z_path)
self.acc_x = self.acc_x_obj.get_data()[0]
self.acc_y = self.acc_y_obj.get_data()[0]
self.acc_z = self.acc_z_obj.get_data()[0]
print('Required files imported successfully ...')
self.samples_before_begin, self.sigHDRecorder_org_synced, self.sigScript_org, self.sigScript_org_R = self.calculate_lag(
plot=(int(self.plot_sync_output.get()) ==1) , path_EDF=self.HDRecorderRecording,\
path_Txt=self.ZmaxDondersRecording,\
T = 30,\
t_start_sync = 100,\
t_end_sync = 200)
# Filter?
if int(self.is_filtering.get()) == 1:
print('Bandpass filtering (.3-30 Hz) started')
self.sigScript_org = self.butter_bandpass_filter(data = self.sigScript_org, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigScript_org_R = self.butter_bandpass_filter(data = self.sigScript_org_R, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigHDRecorder = self.butter_bandpass_filter(data = self.sigHDRecorder, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigHDRecorder_r = self.butter_bandpass_filter(data = self.sigHDRecorder_r, lowcut=.3, highcut=30, fs = 256, order = 2)
# Plot psd?
if int(self.plot_psd.get()) == 1:
print('plotting peridogram ...')
self.plot_welch_periodogram(data = self.sigScript_org, sf = 256, win_size = 5)
# Plot spectrogram as well?
fig, markers_details = self.AssignMarkersToRecordedData_EEG_TFR_noEMG(data = self.sigScript_org, data_R = self.sigScript_org_R, sf = 256,\
path_to_json_markers=self.path_to_json_markers,\
markers_to_show = ['light', 'manual', 'sound'],\
win_sec=30, fmin=0.5, fmax=25,\
trimperc=5, cmap='RdBu_r', add_colorbar = False)
# Activate save section
if int(self.is_autoscoring.get()) == 1:
self.create_save_options_autoscoring()
#%% Analyzing only HDRecorder
elif int(self.analysis_signal_options.get()) == 3:
self.sync_with_dreamento = False
# Sanitary checks
if 'hypnodyne_files_list' not in globals():
messagebox.showerror("Dreamento", "Sorry, but a file is missing ...\n The EEG L.edf file is not selected!")
else:
hypnodyne_files_list
print('Analyzing ZMax Hypndoyne data ...')
self.HDRecorderRecording = hypnodyne_files_list[0]
self.noise_path = hypnodyne_files_list[0].split('EEG')[0] + 'NOISE.edf'
self.noise_obj = mne.io.read_raw_edf(self.noise_path)
self.noise_data = self.noise_obj.get_data()[0]
# Acc
self.acc_x_path = hypnodyne_files_list[0].split('EEG')[0] + 'dX.edf'
self.acc_y_path = hypnodyne_files_list[0].split('EEG')[0] + 'dY.edf'
self.acc_z_path = hypnodyne_files_list[0].split('EEG')[0] + 'dZ.edf'
self.acc_x_obj = mne.io.read_raw_edf(self.acc_x_path)
self.acc_y_obj = mne.io.read_raw_edf(self.acc_y_path)
self.acc_z_obj = mne.io.read_raw_edf(self.acc_z_path)
self.acc_x = self.acc_x_obj.get_data()[0]
self.acc_y = self.acc_y_obj.get_data()[0]
self.acc_z = self.acc_z_obj.get_data()[0]
self.ppg_path = hypnodyne_files_list[0].split('EEG')[0] + 'OXY_IR_AC.edf'
self.ppg_obj = mne.io.read_raw_edf(self.ppg_path)
self.ppg_data = self.ppg_obj.get_data()[0]
self.ppg_data = self.butter_bandpass_filter(data = self.ppg_data, lowcut=.3, highcut=100, fs = 256, order = 2)
print('Required files imported successfully ...')
data = mne.io.read_raw_edf(self.HDRecorderRecording)
raw_data = data.get_data()
self.sigHDRecorder = np.ravel(raw_data)
data_r = mne.io.read_raw_edf(self.HDRecorderRecording.split('EEG L.edf')[0] + 'EEG R.edf')
raw_data_r = data_r.get_data()
self.sigHDRecorder_r = np.ravel(raw_data_r)
# Filter?
if int(self.is_filtering.get()) == 1:
print('Bandpass filtering (.3-30 Hz) started')
self.sigHDRecorder = self.butter_bandpass_filter(data = self.sigHDRecorder, lowcut=.3, highcut=30, fs = 256, order = 2)
self.sigHDRecorder_r = self.butter_bandpass_filter(data = self.sigHDRecorder_r, lowcut=.3, highcut=30, fs = 256, order = 2)
print('Filtered successfully')
# Plot psd?
if int(self.plot_psd.get()) == 1:
print('plotting peridogram ...')
self.plot_welch_periodogram(data = self.sigHDRecorder, sf = 256, win_size = 5)
print('PSD plotted successfully')
self.AnalyzeZMaxHypnodyne(data= self.sigHDRecorder, data_R = self.sigHDRecorder_r, sf = 256,\
win_sec=30, fmin=0.3, fmax=25,\
trimperc=5, cmap='RdBu_r', add_colorbar = False)
#%% band-pass filtering
def butter_bandpass_filter(self, data, lowcut, highcut, fs, order = 2):
"""
Butterworth bandpass filter
:param self: access the attributes and methods of the class
:param data: data to be filtered
:param lowcut: the lowcut of the filter
:param highcut: the highcut of the filter
:param fs: sampling frequency
:param order: filter order
:returns: y
"""
nyq = 0.5 * fs
low = lowcut /nyq
high = highcut/nyq
b, a = butter(order, [low, high], btype='band')
#print(b,a)
y = filtfilt(b, a, data)
return y
#%% Save button
def save_results_button(self):
"""
The command to save the results of the ra and processed data to a matfile.
:param self: access the attributes and methods of the class
:param data_EEG_L: Dreamento EEG L
:param data_EEG_R: Dreamento EEG R
:param Hypnodyne_EEG_L: HDRecorder EEG L
:param markers: List of annotations
:param marker_keys: The keys to define the dictionaries of annotations
:param samples_to_sync: The number of samples to be compensated for sync.
:param microphone_data: mic recording from the headband
:param acc_x: The x-axis of accleration
:param acc_y: The y-axis of accleration
:param acc_z: The z-axis of accleration
:param raw_EMG1: The first recorded EMG channel
:param raw_EMG2: The second recorded EMG channel
:param raw_EMG3: The third recorded EMG channel
:returns: matfile.mat
"""
from scipy.io import savemat
self.dict_all_results = dict()
if int(self.analysis_signal_options.get()) == 1:
self.dict_all_results['EEG_L_Dreamento'] = self.sigScript_org #self.sigScript_org
self.dict_all_results['EEG_R_Dreamento'] = self.sigScript_org_R # self.sigScript_org_R
self.dict_all_results['markers'] = self.markers_details # markers_details
self.dict_all_results['marker_keys'] = self.marker_keys # marker_keys
self.dict_all_results['Hypnodyne_EEG_L']= self.sigHDRecorder_org_synced # marker_key
self.dict_all_results['samples_to_sync']= self.samples_before_begin # marker_keys
self.dict_all_results['microphone_data']= self.noise_data # marker_keys
self.dict_all_results['acc_x']= self.acc_x # marker_keys
self.dict_all_results['acc_y']= self.acc_y # marker_keys
self.dict_all_results['acc_z']= self.acc_z # marker_keys
self.dict_all_results['raw_EMG1']= self.EMG_filtered_data1 # marker_keys
self.dict_all_results['raw_EMG2']= self.EMG_filtered_data2 # marker_keys
self.dict_all_results['raw_EMG1_minus_EMG2']= self.EMG_filtered_data1_minus_2
#self.dict_all_results['sample_to_remove_from_EMG_to_sync_with_Dreamento']=self.samples_before_begin_EMG_Dreamento
elif int(self.analysis_signal_options.get()) == 2:
self.dict_all_results = dict()
self.dict_all_results['EEG_L_Dreamento'] = self.sigScript_org #self.sigScript_org
self.dict_all_results['EEG_R_Dreamento'] = self.sigScript_org_R # self.sigScript_org_R
self.dict_all_results['markers'] = self.markers_details # markers_details
self.dict_all_results['marker_keys'] = self.marker_keys # marker_keys
self.dict_all_results['Hypnodyne_EEG_L']= self.sigHDRecorder_org_synced # marker_key
self.dict_all_results['samples_to_sync']= self.samples_before_begin # marker_keys
self.dict_all_results['microphone_data']= self.noise_data # marker_keys
self.dict_all_results['acc_x']= self.acc_x # marker_keys
self.dict_all_results['acc_y']= self.acc_y # marker_keys
self.dict_all_results['acc_z']= self.acc_z # marker_keys
elif int(self.analysis_signal_options.get()) == 3:
self.dict_all_results['Hypnodyne_EEG_L']= self.sigHDRecorder # marker_key
self.dict_all_results['Hypnodyne_EEG_R']= self.sigHDRecorder_r # marker_key
self.dict_all_results['microphone_data']= self.noise_data # marker_keys
self.dict_all_results['acc_x']= self.acc_x # marker_keys
self.dict_all_results['acc_y']= self.acc_y # marker_keys
self.dict_all_results['acc_z']= self.acc_z # marker_keys
if self.entry_save_name.get()[-4:] == '.mat':
saving_dir = where_to_save_path + '/' + self.entry_save_name.get()
else:
saving_dir = where_to_save_path + '/' + self.entry_save_name.get() + '.mat'
#savemat('saving_dir.MAT', self.dict_all_results)
savemat(saving_dir, self.dict_all_results)
messagebox.showinfo(title = "Output sucessfully saved", message = f'file saved in {saving_dir}!')
#%% Coimpute correlation
def plot_xcorr(self, x, y, ax=None):
"""
Plot the cross-correlation between signals.
:param self: access the attributes and methods of the class
:param signal1: sigHDRecorderTrimmed
:param signal2: sig Dreamento
:param axis:
:returns: plot
"""
# "Plot cross-correlation (full) between two signals."
N = max(len(x), len(y))
n = min(len(x), len(y))
if N == len(y):
lags = np.arange(-N + 1, n)
else:
lags = np.arange(-n + 1, N)
c = signal.correlate(x / np.std(x), y / np.std(y), 'full')
if ax is None:
plt.plot(lags, c / n)
plt.show()
else:
ax.plot(lags, c / n, color='k', label="Cross-correlation")
#%% Compute lag
def calculate_lag(self, plot=False, path_EDF=None, path_Txt=None,
T = 30,
t_start_sync = 5,
t_end_sync = 7):
"""
Calculate the lag between the HDRecorder and Dreamento and compensate it.
:param self: access the attributes and methods of the class
:param plot: activate/deactivate plotting
:param path_EDF: path to HDRecorder EEG L.edf file
:param path_Txt: path to Dreamento .txt file
:param T: The duration to use for synchronization (seconds)
:param t_start_sync: the start time of a nevent for sync.
:param t_end_sync: the end point of a nevent for sync.
:returns: samples_before_begin, sigHDRecorder_org_synced, sigScript_org, sigScript_org_R fwsd
"""
global sigHDRecorder, sigScript
sigScript = np.loadtxt(path_Txt, delimiter=',')
sigScript_org = sigScript
sigScript_org_R = sigScript[:, 0]
sigScript_org = sigScript_org[:, 1]
sigScript = sigScript[int(t_start_sync * 256):int(t_end_sync * 256), 1]
data = mne.io.read_raw_edf(path_EDF)
raw_data = data.get_data()
sigHDRecorder = np.ravel(raw_data)
sigHDRecorder = sigHDRecorder
sigHDRecorder_org = sigHDRecorder
# for example: epoch 15 to 17
# T = 30, t_start_sync = 15, t_end_sync = 17
sigHDRecorder = sigHDRecorder[int(t_start_sync * 256):int(t_end_sync * 256)]
print('Calculating the lag between signals ...')
corr = signal.correlate(sigHDRecorder, sigScript) # Compute correlation
lag = np.argmax(np.abs(corr)) # find lag
corr2 = np.correlate(sigHDRecorder, sigScript, "full")
lag2 = np.argmax(corr2)
print(f'len ({len(sigScript)})')
print(f'lag2 ({lag2})')
samples_before_begin = lag2 + 1 - len(sigScript)
if samples_before_begin < 0:
xCorrZeros = np.zeros(-samples_before_begin)
xShiftedForward = np.append(xCorrZeros, sigScript)
samples_after_end = len(sigHDRecorder) - samples_before_begin - len(sigScript)
index_start_script = samples_before_begin
index_end_script = len(sigHDRecorder) - samples_after_end
print(f"samples_before_begin {samples_before_begin}")
print(f"samples_after_end {samples_after_end}")
print(f"index_start_script {index_start_script}")
print(f"index_end_script {index_end_script}")
sigHDRecorderTrimmed = sigHDRecorder[index_start_script:index_end_script + 1]
print(f"HDRecorder {samples_before_begin} samples longer")
if plot:
fig, ax = plt.subplots(4, 1, figsize=(16, 12))
ax[0].plot(sigScript, color='r', label="sigScript")
ax[1].plot(sigHDRecorder, color='g', label="sigHDRecorder")
ax[2].plot(sigScript, color='r', label="sigScript")
ax[2].plot(sigHDRecorderTrimmed * 1e6, color='g', label=f"HDRecorder {samples_before_begin} longer")
self.plot_xcorr(sigHDRecorderTrimmed, sigScript, ax=ax[3])
ax[0].legend()
ax[1].legend()
ax[2].legend()
ax[3].legend()
plt.tight_layout()
# plt.savefig('similarity.png')
# plt.savefig('similarity.pdf')
# plt.savefig('similarity.svg')
# plt.savefig('similarityT.png', transparent=True)
# plt.savefig('similarityT.pdf', transparent=True)
# plt.savefig('similarityT.svg', transparent=True)
plt.show()
# from scipy.signal import butter, filtfilt
# sigScriptFiltered = sigScript_org / 1e6
# lowcut = 0.3
# highcut = 30
# nyquist_freq = 256 / 2.
# low = lowcut / nyquist_freq
# high = highcut / nyquist_freq
# # Req channel
# b, a = butter(2, [low, high], btype='band')
# sigScriptFiltered_L = filtfilt(b, a, data)
# sigScriptFiltered_R = filtfilt(b, a, data)
# Now plot the complete signals
plt.figure()
plt.title("Synced Signals")
plt.plot(sigScript_org, label='EEG L - Dreamento')
plt.plot(sigHDRecorder_org[samples_before_begin:]*1e6, label='EEG L - HDRecorder')
plt.legend()
plt.show()
# Synced data
sigHDRecorder_org_synced = sigHDRecorder_org[samples_before_begin:]
self.sigHDRecorder_org_synced = sigHDRecorder_org_synced
print('Lag cmputation finished')
return samples_before_begin, sigHDRecorder_org_synced, sigScript_org, sigScript_org_R
#%% Plot PSD
def plot_welch_periodogram(self, data, sf=256, win_size = 4, log_power = False):
"""
Plot the welch periodogram
:param self: access the attributes and methods of the class
:param data: EEG data
:param sf: sampoling frequency
:param win_size: the size of sliding windows for Welch method.
:param log_power: compute logarithmic power
:returns: plot
"""
from scipy import signal
import seaborn as sns
import yasa
# Define window length (4 seconds)
win = win_size * sf
freqs, psd = signal.welch(data, sf, nperseg=win)
if log_power:
psd = 20 * np.log10(psd)
# Compute vvalues:
ret = yasa.bandpower(data, sf=sf)
# Plot the power spectrum
sns.set(font_scale=1.2, style='white')
plt.figure(figsize=(8, 4))
plt.plot(freqs, psd, color='k', lw=2)
plt.xlabel('Frequency (Hz)')
plt.ylabel('Power spectral density (V^2 / Hz)')
plt.ylim([0, psd.max() * 1.1])
plt.title("Welch's periodogram")
plt.xlim([0, 30])
sns.despine()
# Delta
plt.axvline(.5, linestyle='--', color='black')
plt.axvline(4, linestyle='--', color='black')
# Theta
plt.axvline(8, linestyle='--', color='black')
# Alpha
plt.axvline(12, linestyle='--', color='black')
loc_pow_vals = np.mean([np.min(psd), np.max(psd)])
loc_labels = loc_pow_vals
plt.text(1.3, loc_labels, 'Delta', size=8)
plt.text(4.8, loc_labels, 'Theta', size=8)
plt.text(8.8, loc_labels, 'Alpha', size=8)
plt.text(12.8, loc_labels, 'Beta', size=8)
# legend - values
plt.legend([f'Delta (0.5-4 Hz)): {round(ret["Delta"][0] * 100, 2)}% \n\
Theta (4-8 Hz): {round(ret["Theta"][0] * 100, 2)}% \n\
Alpha (8-12 Hz): {round(ret["Alpha"][0] * 100, 2)}% \n\
Sigma (12-16 Hz): {round(ret["Sigma"][0] * 100, 2)}% \n\
Beta (16-30 Hz): {round(ret["Beta"][0] * 100, 2)}% \n\
Gamma (30-40 Hz): {round(ret["Gamma"][0] * 100, 2)}%'], prop={'size': 10}, frameon=False)
#%% AssignMarkersToRecordedData EEG + TFR
def AssignMarkersToRecordedData_EEG_TFR(self, data, data_R, sf, path_to_json_markers,EMG_path, markers_to_show = ['light', 'manual', 'sound'],\
win_sec=30, fmin=0.3, fmax=40,
trimperc=5, cmap='RdBu_r', add_colorbar = False):
"""
The main function: create the main display window of Offline Dreamento
:param self: access the attributes and methods of the class
:param data: EEG L data
:param data_R: EEG R data
:param sf: sampling frequency
:param path_to_json_markers: path to the annotation file (generated by Dreamento)
:param EMG_path: path to the EMG file
:param markers_to_show: the desired type of markers to show ['light', 'manual', 'sound']
:param win_sec: the window size to compute spectrogram
:param fmin: relavant min frequency for spectrogram
:param fmax: relavant max frequency for spectrogram
:param trimperc: bidirectional trim factor for normalizing the spectrogram colormap
:param cmap: colormap of the spectrogram
:param add_colorbar: adding colorbar to spectrogram
:returns: fig, markers_details
"""
# =============================================================================
# Source code for plotting spectrogram: YASA (https://raphaelvallat.com/yasa/build/html/_modules/yasa/plotting.html#plot_spectrogram)
# [modified]
# =============================================================================
# Increase font size while preserving original
old_fontsize = plt.rcParams['font.size']
plt.rcParams.update({'font.size': 12})
# Safety checks
assert isinstance(data, np.ndarray), 'Data must be a 1D NumPy array.'
assert isinstance(sf, (int, float)), 'sf must be int or float.'
assert data.ndim == 1, 'Data must be a 1D (single-channel) NumPy array.'
assert isinstance(win_sec, (int, float)), 'win_sec must be int or float.'
assert isinstance(fmin, (int, float)), 'fmin must be int or float.'
assert isinstance(fmax, (int, float)), 'fmax must be int or float.'
assert fmin < fmax, 'fmin must be strictly inferior to fmax.'
assert fmax < sf / 2, 'fmax must be less than Nyquist (sf / 2).'
# Calculate multi-taper spectrogram
nperseg = int(10 * sf) #int(win_sec * sf / 8)
assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.'
f, t, Sxx = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=0)
Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs = np.logical_and(f >= fmin, f <= fmax)
Sxx = Sxx[good_freqs, :]
f = f[good_freqs]
#t *= 256 # Convert t to hours
# Normalization
vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc])
norm = Normalize(vmin=vmin, vmax=vmax)
#!!!!!!!!!! SHORT SPECTROGRAM!! Calculate multi-taper spectrogram
# =============================================================================
# nperseg2 = int(.2 * sf) #int(win_sec * sf / 8)
# assert data.size > 2 * nperseg2, 'Data length must be at least 2 * win_sec.'
# f2, t2, Sxx2 = spectrogram_lspopt(data, sf, nperseg=nperseg2, noverlap=0)
# Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# print(f'f: {np.shape(f)}, f2: {np.shape(f2)}, t: {np.shape(t)}, t2: {np.shape(t2)}, Sxx: {np.shape(Sxx)}, Sxx2: {np.shape(Sxx2)}')
# # Select only relevant frequencies (up to 30 Hz)
# good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
# Sxx2 = Sxx2[good_freqs2, :]
# f2 = f2[good_freqs2]
# #t *= 256 # Convert t to hours
#
# # Normalization
# vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
# norm2 = Normalize(vmin=vmin2, vmax=vmax2)
# =============================================================================
assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.'
f2, t2, Sxx2 = spectrogram_lspopt(data_R, sf, nperseg = nperseg, noverlap = 0)
Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
Sxx2 = Sxx2[good_freqs2, :]
f2 = f2[good_freqs2]
vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
norm2 = Normalize(vmin=vmin2, vmax=vmax2)
# =============================================================================
# gs1 = gridspec.GridSpec(2, 1)
# gs1.update(wspace=0.005, hspace=0.0001)
# =============================================================================
if int(self.is_autoscoring.get()) == 0:
# If plotting EMG TFR is required
if int(self.plot_EMG_quality_evaluation.get()) == 1:
if int(self.import_scoring.get()) == 0:
fig,AX = plt.subplots(nrows=16, figsize=(16, 9), gridspec_kw={'height_ratios': [1, 1, 10, 10, 3, 3,3,2, 2, 2, 2, 2, 4, 4,4, 10]})
ax1 = plt.subplot(16,1,1)
ax2 = plt.subplot(16,1,2, sharex = ax1)
ax3 = plt.subplot(16,1,3, sharex = ax1)
ax_TFR_short = plt.subplot(16,1,4, sharex = ax1)
ax_TFR_EMG1 = plt.subplot(16,1,5)
ax_TFR_EMG2 = plt.subplot(16,1,6)
ax_TFR_EMG3 = plt.subplot(16,1,7)
ax_epoch_marker = plt.subplot(16,1,8, )
ax_epoch_light = plt.subplot(16,1,9, sharex = ax_epoch_marker)
ax_acc = plt.subplot(16,1,10, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(16,1,11, sharex = ax_epoch_marker)
ax_noise = plt.subplot(16,1,12, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(16,1,13, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(16,1,14, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(16,1,15, sharex = ax_epoch_marker)
ax4 = plt.subplot(16,1,16, sharex = ax_epoch_marker)
else:
if len(self.filename_scoring_paths)==1:
fig,AX = plt.subplots(nrows=17, figsize=(17, 9), gridspec_kw={'height_ratios': [1, 1, 10, 10, 3, 3,1,1, 2, 2, 2, 4, 4, 4,4 ,4, 10]})
ax1 = plt.subplot(17,1,1)
ax2 = plt.subplot(17,1,2, sharex = ax1)
ax3 = plt.subplot(17,1,3, sharex = ax1)
ax_TFR_short = plt.subplot(17,1,4, sharex = ax1)
ax_TFR_EMG1 = plt.subplot(17,1,5)
ax_TFR_EMG2 = plt.subplot(17,1,6)
ax_TFR_EMG3 = plt.subplot(17,1,7)
ax_epoch_marker = plt.subplot(17,1,8, )
ax_epoch_light = plt.subplot(17,1,9, sharex = ax_epoch_marker)
ax_acc = plt.subplot(17,1,10, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(17,1,11, sharex = ax_epoch_marker)
ax_noise = plt.subplot(17,1,12, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(17,1,13, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(17,1,14, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(17,1,15, sharex = ax_epoch_marker)
ax_manual_scoring1 = plt.subplot(17,1,16,)
ax4 = plt.subplot(17,1,17, sharex = ax_epoch_marker)
elif len(self.filename_scoring_paths)==2:
fig,AX = plt.subplots(nrows=18, figsize=(18, 9), gridspec_kw={'height_ratios': [1, 1, 10, 10, 3, 3,1,1, 2, 2, 2, 4, 4,4, 4, 4,4, 10]})
ax1 = plt.subplot(18,1,1)
ax2 = plt.subplot(18,1,2, sharex = ax1)
ax3 = plt.subplot(18,1,3, sharex = ax1)
ax_TFR_short = plt.subplot(18,1,4, sharex = ax1)
ax_TFR_EMG1 = plt.subplot(18,1,5)
ax_TFR_EMG2 = plt.subplot(18,1,6)
ax_TFR_EMG3 = plt.subplot(18,1,7)
ax_epoch_marker = plt.subplot(18,1,8, )
ax_epoch_light = plt.subplot(18,1,9, sharex = ax_epoch_marker)
ax_acc = plt.subplot(18,1,10, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(18,1,11, sharex = ax_epoch_marker)
ax_noise = plt.subplot(18,1,12, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(18,1,13, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(18,1,14, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(18,1,15, sharex = ax_epoch_marker)
ax_manual_scoring1 = plt.subplot(18,1,16,)
ax_manual_scoring2 = plt.subplot(18,1,17,)
ax4 = plt.subplot(18,1,18, sharex = ax_epoch_marker)
elif len(self.filename_scoring_paths)==3:
fig,AX = plt.subplots(nrows=19, figsize=(19, 9), gridspec_kw={'height_ratios': [1, 1, 10, 10, 3, 3,1,1, 2, 2, 2, 4, 4,4,4,4, 4,4, 10]})
ax1 = plt.subplot(19,1,1)
ax2 = plt.subplot(19,1,2, sharex = ax1)
ax3 = plt.subplot(19,1,3, sharex = ax1)
ax_TFR_short = plt.subplot(19,1,4, sharex = ax1)
ax_TFR_EMG1 = plt.subplot(19,1,5)
ax_TFR_EMG2 = plt.subplot(19,1,6)
ax_TFR_EMG3 = plt.subplot(19,1,7)
ax_epoch_marker = plt.subplot(19,1,8, )
ax_epoch_light = plt.subplot(19,1,9, sharex = ax_epoch_marker)
ax_acc = plt.subplot(19,1,10, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(19,1,11, sharex = ax_epoch_marker)
ax_noise = plt.subplot(19,1,12, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(19,1,13, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(19,1,14, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(19,1,15, sharex = ax_epoch_marker)
ax_manual_scoring1 = plt.subplot(19,1,16,)
ax_manual_scoring2 = plt.subplot(19,1,17,)
ax_manual_scoring3 = plt.subplot(19,1,18,)
ax4 = plt.subplot(19,1,19, sharex = ax_epoch_marker)
elif len(self.filename_scoring_paths)>3:
print('Too many scorings! we can"t handle them at this version of Dreamento!')
messagebox.showerror("Dreamento", "Sorry, the current version of Dreamento can import only up to 3 scorings! try again!")
# If plotting EMG TFR is NOT required
else:
fig,AX = plt.subplots(nrows=13, figsize=(16, 9), gridspec_kw={'height_ratios': [1, 1, 10,10,1, 2, 2, 2, 4, 4, 4, 4, 10]})
ax1 = plt.subplot(13,1,1)
ax2 = plt.subplot(13,1,2, sharex = ax1)
ax3 = plt.subplot(13,1,3, sharex = ax1)
ax_epoch_marker = plt.subplot(13,1,5, )
ax_epoch_light = plt.subplot(13,1,6, sharex = ax_epoch_marker)
ax_acc = plt.subplot(13,1,7, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(13,1,8, sharex = ax_epoch_marker)
ax_noise = plt.subplot(13,1,9, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(13,1,10, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(13,1,11, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(13,1,12, sharex = ax_epoch_marker)
ax_TFR_short = plt.subplot(13,1,4, sharex = ax1)
ax4 = plt.subplot(13,1,13, sharex = ax_epoch_marker)
ax4.grid(True)
ax1.get_xaxis().set_visible(False)
ax2.get_xaxis().set_visible(False)
ax3.get_xaxis().set_visible(False)
ax_epoch_marker.get_xaxis().set_visible(False)
ax_epoch_light.get_xaxis().set_visible(False)
ax_EMG.get_xaxis().set_visible(False)
ax_TFR_short.get_xaxis().set_visible(False)
ax_acc.get_xaxis().set_visible(False)
ax_acc.set_yticks([])
ax_noise.set_yticks([])
ax_noise.get_xaxis().set_visible(False)
ax_acc.set_ylim([-1.4, 1.4])
plt.subplots_adjust(hspace = 0)
ax1.set_title('Dreamento: post-processing ')
im = ax3.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax3.set_xlim([0, len(data)/256])
ax3.set_ylim((fmin, 25))
ax3.set_ylabel('EEG L-Freq(Hz)', rotation = 80, fontsize=9)
im2 = ax_TFR_short.pcolormesh(t2, f2, Sxx2, norm=norm2, cmap=cmap, antialiased=True,
shading="auto")
# Add colorbar
if add_colorbar == True:
cbar = fig.colorbar(im, ax=ax3, shrink=0.95, fraction=0.1, aspect=25, pad=0.01)
cbar.ax3.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=5)
#axes[1].set_ylim([-200, 200])
#ax4.set_xlim([0, len(data)])
ax4.set_ylabel('EEG (uV)')
ax4.set_ylim([-150, 150])
# Opening JSON file
f = open(path_to_json_markers,)
# returns JSON object as a dictionary
markers = json.load(f)
markers_details = list(markers.values())
self.markers_details = markers_details
self.marker_keys = list(markers.keys())
self.counter_markers = 0
self.palette = itertools.cycle(sns.color_palette())
for counter, marker in enumerate(markers.keys()):
if marker.split()[0] == 'MARKER':
if 'manual' in markers_to_show:
self.counter_markers = self.counter_markers + 1
self.color_markers = next(self.palette)
marker_loc = int(marker.split()[-1])
ax1.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = str(self.counter_markers)+'. '+markers_details[counter], linewidth = 3, color = self.color_markers)
ax1.set_ylim([fmin, fmax])
ax_epoch_marker.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter], linewidth = 3, color = self.color_markers)
ax_epoch_marker.text(marker_loc/256+.1, int((fmin+fmax)/2), str(self.counter_markers ), verticalalignment='center', color = self.color_markers)
if marker.split()[0] == 'SOUND':
if 'sound' in markers_to_show:
marker_loc = int(marker.split()[-1])
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+ markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Audio', verticalalignment='center', color = 'blue')
elif marker.split()[0] == 'LIGHT':
if 'light' in markers_to_show:
marker_loc = int(marker.split()[-1])
if 'Vib: False' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Light', verticalalignment='center', color = 'red')
elif 'Vib: True' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Vibration', verticalalignment='center', color = 'green')
ax1.set_yticks([])
ax2.set_yticks([])
ax_TFR_EMG3.set_xticks([])
ax_epoch_marker.set_yticks([])
ax_epoch_light.set_yticks([])
#ax_epoch_marker.set_xticks([])
#ax_epoch_light.set_xticks([])
ax2.set_ylabel('Stimulation(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax1.set_ylabel('Markers(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax_epoch_marker.set_ylabel('Markers(epoch)', rotation = 0, labelpad=30, fontsize=8)
ax_epoch_light.set_ylabel('Stimulation(epoch)', rotation = 0, labelpad=30,fontsize=8)
ax_acc.set_ylabel('Acc', rotation = 90)#, labelpad=30, fontsize=8)
ax_noise.set_ylabel('Sound', rotation = 0, labelpad=30, fontsize=8)
ax_EMG.set_ylabel('EMG1 (uV)',rotation = 0, labelpad=30, fontsize=8)
ax_EMG2.set_ylabel('EMG2 (uV)',rotation = 0, labelpad=30, fontsize=8)
ax_EMG3.set_ylabel(' EMG1 - EMG2 (uV) ',rotation = 0, labelpad=30, fontsize=8)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
# =============================================================================
# ax_epoch_light.spines[["left", 'right']].set_visible(False)
# ax_epoch_marker.spines[["left", 'right']].set_visible(False)
# ax_acc.spines[["top", "bottom"]].set_visible(False)
# ax_noise.spines[["top", "bottom"]].set_visible(False)
# =============================================================================
time_axis = np.round(np.arange(0, len(data)) / 256 , 2)
ax4.set_xlabel('time (s)')
#ax4.set_xticks(np.arange(len(data)), time_axis)
ax4.set_xlim([0, 30])#len(data)])
leg = ax1.legend(fontsize=9, bbox_to_anchor=(1, 0), loc = 'upper left')
leg.set_draggable(state=True)
# plot acc
ax_acc.plot(np.arange(len(self.acc_x[self.samples_before_begin:]))/256, self.acc_x[self.samples_before_begin:], linewidth = 2 , color = 'blue')
ax_acc.plot(np.arange(len(self.acc_y[self.samples_before_begin:]))/256, self.acc_y[self.samples_before_begin:], linewidth = 2, color = 'red')
ax_acc.plot(np.arange(len(self.acc_z[self.samples_before_begin:]))/256, self.acc_z[self.samples_before_begin:], linewidth = 2, color = 'green')
# Plot ppg
ax_ppg.plot(np.arange(len(self.ppg_data))/256, self.ppg_data, linewidth = 2 , color = 'olive')
ax_ppg.set_ylim([-100, 100])
ax_ppg.set_ylabel('PPG')
ax_ppg.set_yticks([])
# plot noise
ax_noise.plot(np.arange(len(self.noise_data[self.samples_before_begin:]))/256, self.noise_data[self.samples_before_begin:], linewidth = 2, color = 'navy')
#ax4.get_xaxis().set_visible(False)
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=9)
###########
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = data
roc = data_R
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=None, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data, sf=256, ch_names=None, hypno=None, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
spindle_events_R = yasa.spindles_detect(data_R, sf=256, ch_names=None, hypno=None, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_R * mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data, sf=256, ch_names=None, hypno=None, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_R, sf=256, ch_names=None, hypno=None, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_R * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_L_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_R_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_L_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_R_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax4.plot(np.arange(len(data))/256, data , color = 'slategrey', linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = 'black', linewidth = 1)
else:
ax4.plot(np.arange(len(data))/256, data, color = (160/255, 70/255, 160/255), linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, SO_R_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, spindles_L_highlight, 'green')
ax4.plot(np.arange(len(data_R))/256, spindles_R_highlight, 'green')
##########
self.str_first_subplot = str(ax3)
global manual_scoring
self.data = data
self.data_R = data_R
num_rows = int(np.floor(len(data)/256/30))
manual_scoring = np.column_stack((-np.ones(num_rows), np.zeros(num_rows)))
fig.canvas.mpl_connect('key_press_event', self.pan_nav)
fig.canvas.mpl_connect('button_press_event', self.onclick)
print(f'Data includes overall {int(np.floor(len(data)/256/30))} epochs all initialized as unscored ...')
#ax1.set_xlim([0, 7680])#len(data)])
#ax2.set_xlim([0, 7680])#len(data)])
#ax3.set_xlim([0, len(data)])
#ax4.set_xlim([0, 7680])#len(data)])
# read EMG
if (self.path_to_EMG[-4:] == 'vhdr' or self.path_to_EMG[-4:] == 'VHDR'):
self.EMG_raw = mne.io.read_raw_brainvision(self.path_to_EMG, preload = True)
#self.EMG_raw = self.EMG_raw.resample(int(256))
elif (self.path_to_EMG[-3:] == 'edf' or self.path_to_EMG[-3:] == 'EDF'):
self.EMG_raw = mne.io.read_raw_edf(self.path_to_EMG, preload = True)
self.EMG_filtered = self.EMG_raw.filter(l_freq=10, h_freq=100)
self.EMG_filtered = self.EMG_filtered.resample(int(256))
self.EMG_filtered_data1 = self.EMG_filtered.get_data()[0]
self.EMG_filtered_data2 = self.EMG_filtered.get_data()[1]
self.EMG_filtered_data1_minus_2 = self.EMG_filtered_data1 - self.EMG_filtered_data2
# Notch filter EMG
notch_freq = 50 # set the notch frequency to 50 Hz
# Create the notch filter
q = 30 # quality factor
b, a = signal.iirnotch(notch_freq, q, int(256))
# Apply the notch filter to the data
self.EMG_filtered_data1 = signal.filtfilt(b, a, self.EMG_filtered_data1, axis=0)
self.EMG_filtered_data2 = signal.filtfilt(b, a, self.EMG_filtered_data2, axis=0)
self.EMG_filtered_data1_minus_2 = signal.filtfilt(b, a, self.EMG_filtered_data1_minus_2, axis=0)
self.desired_EMG_scale_val = int(self.EMG_scale_options_val.get())
# =============================================================================
# if (self.path_to_EMG[-4:] == 'vhdr' or self.path_to_EMG[-4:] == 'VHDR'):
#
# self.desired_EMG_scale= [-1e-6* self.desired_EMG_scale_val, 1e-6* self.desired_EMG_scale_val]
#
# elif (self.path_to_EMG[-3:] == 'edf' or self.path_to_EMG[-3:] == 'EDF'):
#
# #self.desired_EMG_scale= [-1 * self.desired_EMG_scale_val, 1* self.desired_EMG_scale_val]
# self.desired_EMG_scale= [-1e-6* self.desired_EMG_scale_val, 1e-6* self.desired_EMG_scale_val]
# =============================================================================
# detect if the EMG data is in uV or V
if np.std(self.EMG_filtered_data1) < 1:
print('data scale is deteceted in Volts range')
self.desired_EMG_scale= [-1e-6* self.desired_EMG_scale_val, 1e-6* self.desired_EMG_scale_val]
else:
self.desired_EMG_scale= [-1 * self.desired_EMG_scale_val, 1* self.desired_EMG_scale_val]
# Check whether the user already synced EMG vs. EEG or not
print(f'shape EMG signals = {np.shape(self.EMG_filtered_data1)}')
if self.flag_sync_EEG_EMG == True:
print(f'sync samples: {str(self.samples_before_begin_EMG_Dreamento)} with shape {str(np.shape(self.samples_before_begin_EMG_Dreamento))}')
print(f'sync criterion: {self.flag_sign_samples_before_begin_EMG_Dreamento}')
if self.flag_sign_samples_before_begin_EMG_Dreamento == 'eeg_event_earlier':
print('Detected that the event occured earlier in EEG than the EMG signal')
self.EMG_filtered_data1 = self.EMG_filtered_data1[self.samples_before_begin_EMG_Dreamento:]
self.EMG_filtered_data2 = self.EMG_filtered_data2[self.samples_before_begin_EMG_Dreamento:]
self.EMG_filtered_data1_minus_2 = self.EMG_filtered_data1_minus_2[self.samples_before_begin_EMG_Dreamento:]
elif self.flag_sign_samples_before_begin_EMG_Dreamento == 'emg_event_earlier':
print('Detected that the event occured earlier in EMG than the EEG signal')
print(f'EMG shape before alignment is : {np.shape(self.EMG_filtered_data1)}')
self.EMG_filtered_data1 = np.append(self.samples_before_begin_EMG_Dreamento, self.EMG_filtered_data1)
self.EMG_filtered_data2 = np.append(self.samples_before_begin_EMG_Dreamento, self.EMG_filtered_data2)
self.EMG_filtered_data1_minus_2 = np.append(self.samples_before_begin_EMG_Dreamento, self.EMG_filtered_data1_minus_2)
print(f'EMG shape after alignment is : {np.shape(self.EMG_filtered_data1)}')
ax_EMG.plot(np.arange(len(self.EMG_filtered_data1))/256, self.EMG_filtered_data1, color = (84/255,164/255,75/255))
ax_EMG2.plot(np.arange(len(self.EMG_filtered_data2))/256, self.EMG_filtered_data2, color = (24/255,100/255,160/255))
ax_EMG3.plot(np.arange(len(self.EMG_filtered_data1_minus_2))/256, self.EMG_filtered_data1_minus_2, color = (160/255,10/255,22/255))
ax_EMG.set_ylim(self.desired_EMG_scale)
ax_EMG2.set_ylim(self.desired_EMG_scale)
ax_EMG3.set_ylim(self.desired_EMG_scale)
ax_EMG.grid(True)
ax_EMG2.grid(True)
ax_EMG3.grid(True)
ax_EMG.set_yticks([])
ax_EMG3.set_yticks([])
ax_EMG2.set_yticks((self.desired_EMG_scale[0], 0, self.desired_EMG_scale[1]))
#plt.subplots_adjust(left=0.1, right=0.1, top=0.9, bottom=0.1)
plt.tight_layout()
plt.show()
global sig_emg_1, sig_emg_2, sig_emg_3
sig_emg_1 = self.EMG_filtered_data1
sig_emg_2 = self.EMG_filtered_data2
sig_emg_3 = self.EMG_filtered_data1_minus_2
print('Computing EMG TFR ...')
if int(self.plot_EMG_quality_evaluation.get()) == 1:
self.assess_EMG_data_quality()
print('preparing EMG TFR ...')
# TFR EMG 1
ax_TFR_EMG1.pcolormesh(self.t1_EMG, self.f1_EMG, self.Sxx1_EMG, norm=self.norm1_EMG, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax_TFR_EMG1.set_xlim(0, self.t1_EMG.max())
ax_TFR_EMG3.set_xticks([])
ax_TFR_EMG1.set_yticks([])
# TFR EMG 2
ax_TFR_EMG2.pcolormesh(self.t2_EMG, self.f2_EMG, self.Sxx2_EMG, norm=self.norm2_EMG, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax_TFR_EMG2.set_xlim(0, self.t2_EMG.max())
ax_TFR_EMG2.set_xticks([])
ax_TFR_EMG2.set_yticks([])
ax_TFR_short.set_ylabel('EEG R-freq(Hz)', rotation = 80, fontsize=9)
# TFR EMG 3
ax_TFR_EMG3.pcolormesh(self.t3_EMG, self.f3_EMG, self.Sxx3_EMG, norm=self.norm2_EMG, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax_TFR_EMG3.set_xlim(0, self.t3_EMG.max())
ax_TFR_EMG3.set_xticks([])
ax_TFR_EMG3.set_yticks([])
# Plotting manual scoring, if exists
if int(self.import_scoring.get()) == 1:
for scoring_file_idx, scoring_file in enumerate(np.arange(len(self.filename_scoring_paths))):
print(f'reading {self.filename_scoring_paths[scoring_file]}')
self.stages_imported[self.filename_scoring_paths[scoring_file]] = np.loadtxt(self.filename_scoring_paths[scoring_file], dtype = int)[:, 0]
self.events_imported[self.filename_scoring_paths[scoring_file]] = np.loadtxt(self.filename_scoring_paths[scoring_file], dtype = int)[:, 1]
print(f'reading shape of {np.shape(self.stages_imported[self.filename_scoring_paths[scoring_file]])}')
# Determine the axis based on the iteration index
if scoring_file_idx == 0:
current_axis = ax_manual_scoring1
scoring1= self.stages_imported[self.filename_scoring_paths[scoring_file]]
scorer1 = self.filename_scoring_paths[scoring_file]
events1 = self.events_imported[self.filename_scoring_paths[scoring_file]]
elif scoring_file_idx == 1:
current_axis = ax_manual_scoring2
scoring2= self.stages_imported[self.filename_scoring_paths[scoring_file]]
scorer2 = self.filename_scoring_paths[scoring_file]
events2 = self.events_imported[self.filename_scoring_paths[scoring_file]]
elif scoring_file_idx == 2:
current_axis = ax_manual_scoring3
scoring3= self.stages_imported[self.filename_scoring_paths[scoring_file]]
scorer3 = self.filename_scoring_paths[scoring_file]
events3 = self.events_imported[self.filename_scoring_paths[scoring_file]]
# Change the order of classes: REM and wake on top
x = []
y = []
for i in np.arange(len(self.stages_imported[self.filename_scoring_paths[scoring_file]])):
s = self.stages_imported[self.filename_scoring_paths[scoring_file]][i]
if s== 0 : p = -0
if s== 4 : p = -1
if s== 1 : p = -2
if s== 2 : p = -3
if s== 3 : p = -4
if i!=0:
y.append(p)
x.append(i-1)
y.append(p)
x.append(i)
current_axis.step(x, y, where='post', color = 'black')
rem = [i for i,j in enumerate(self.stages_imported[self.filename_scoring_paths[scoring_file]]) if (self.stages_imported[self.filename_scoring_paths[scoring_file]][i]==4)]
LRLR = [i for i,j in enumerate(self.events_imported[self.filename_scoring_paths[scoring_file]]) if (self.events_imported[self.filename_scoring_paths[scoring_file]][i]==5757)]
arousal = [i for i,j in enumerate(self.events_imported[self.filename_scoring_paths[scoring_file]]) if (self.events_imported[self.filename_scoring_paths[scoring_file]][i]==1)]
MBM = [i for i,j in enumerate(self.events_imported[self.filename_scoring_paths[scoring_file]]) if (self.events_imported[self.filename_scoring_paths[scoring_file]][i]==2)]
for i in np.arange(len(rem)) -1:
current_axis.plot([rem[i]-1, rem[i]], [-1,-1] , linewidth = 2, color = 'red')
for i in np.arange(len(LRLR)):
current_axis.scatter(LRLR[i], -.5, marker='v', color='red')
for i in np.arange(len(arousal)):
current_axis.scatter(arousal[i], -.5, marker='^', color='navy')
for i in np.arange(len(MBM)):
current_axis.scatter(MBM[i], -.5, marker='^', color='blue')
#ax_autoscoring.scatter(rem, -np.ones(len(rem)), color = 'red')
current_axis.set_yticks([0,-1,-2,-3,-4], ['Wake','REM', 'N1', 'N2', 'SWS'],\
fontsize = 8)
current_axis.set_xlim([0, len(self.stages_imported[self.filename_scoring_paths[scoring_file]])])
current_axis.grid(axis='y')
current_axis.set_xticks([])
print(f'len of scoring {scoring_file}: {np.shape(self.stages_imported[self.filename_scoring_paths[scoring_file]])} epochs')
# Compute agrement between scorers
if len(self.filename_scoring_paths) == 2:
from sklearn.metrics import cohen_kappa_score
# Should the W and N1 scorings be combined?
if int(self.combine_W_N1_for_assessment.get())==1:
# Scoring1
W_N1_indices_scoring1 = np.where((scoring1 == 0) | (scoring1 == 1))
print('Combining N1 and Wake stages before computing Kappa score ....')
scoring1[W_N1_indices_scoring1] = 10
# Scoring1
W_N1_indices_scoring2 = np.where((scoring2 == 0) | (scoring2 == 1))
print('Combining N1 and Wake stages before computing Kappa score ....')
scoring2[W_N1_indices_scoring2] = 10
self.kappa1_2 = cohen_kappa_score(scoring1, scoring2)
messagebox.showinfo("Scorers agreemnt",f"The agreement between {scorer1} and {scorer2} is:\n {round(self.kappa1_2, 2)*100} %.")
elif len(self.filename_scoring_paths) == 3:
# Should the W and N1 scorings be combined?
if int(self.combine_W_N1_for_assessment.get()==1):
# Scoring1
W_N1_indices_scoring1 = np.where((scoring1 == 0) | (scoring1 == 1))
print('Combining N1 and Wake stages before computing Kappa score ....')
scoring1[W_N1_indices_scoring1] = 10
# Scoring2
W_N1_indices_scoring2 = np.where((scoring2 == 0) | (scoring2 == 1))
print('Combining N1 and Wake stages before computing Kappa score ....')
scoring2[W_N1_indices_scoring2] = 10
# Scoring3
W_N1_indices_scoring3 = np.where((scoring3 == 0) | (scoring3 == 1))
print('Combining N1 and Wake stages before computing Kappa score ....')
scoring3[W_N1_indices_scoring3] = 10
print(scoring1)
print(scoring2)
print(scoring3)
from sklearn.metrics import cohen_kappa_score
self.kappa1_2 = cohen_kappa_score(scoring1, scoring2)
self.kappa1_3 = cohen_kappa_score(scoring1, scoring3)
self.kappa2_3 = cohen_kappa_score(scoring2, scoring3)
messagebox.showinfo("Scorers agreement",f"The agreement between {scorer1} and {scorer2} is:\n {round(self.kappa1_2, 2)*100} %\n~~~~\n between {scorer1} and {scorer3} is: {100*round(self.kappa1_3,2)} % \n~~~~\n and between {scorer2} and {scorer3} is:\n {round(self.kappa2_3,2)*100}%.")
else:
# If plotting EMG TFR is required
if int(self.plot_EMG_quality_evaluation.get()) == 1:
fig,AX = plt.subplots(nrows=18, figsize=(16, 9), gridspec_kw={'height_ratios': [2, 2, 8, 8, 3, 3,3 ,3, 2, 2, 2, 2, 2, 2, 4, 4, 4, 10]})
ax1 = plt.subplot(18,1,1)
ax2 = plt.subplot(18,1,2, sharex = ax1)
ax3 = plt.subplot(18,1,3, sharex = ax1)
ax_TFR_EMG1 = plt.subplot(18,1,5)
ax_TFR_EMG2 = plt.subplot(18,1,6)
ax_TFR_EMG3 = plt.subplot(18,1,7)
ax_autoscoring = plt.subplot(18,1,8, )
ax_proba = plt.subplot(18,1,9 )
ax_epoch_marker = plt.subplot(18,1,10, )
ax_epoch_light = plt.subplot(18,1,11, sharex = ax_epoch_marker)
ax_acc = plt.subplot(18,1,12, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(18,1,13, sharex = ax_epoch_marker)
ax_noise = plt.subplot(18,1,14, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(18,1,15, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(18,1,16, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(18,1,17, sharex = ax_epoch_marker)
ax_TFR_short = plt.subplot(18,1,4, sharex = ax1)
ax4 = plt.subplot(18,1,18, sharex = ax_epoch_marker)
# If plotting EMG TFR is NOT required
else:
fig,AX = plt.subplots(nrows=15, figsize=(16, 9), gridspec_kw={'height_ratios': [2, 2, 8, 8, 3,2,2,2, 2, 2,4, 4, 4, 4, 10]})
ax1 = plt.subplot(15,1,1)
ax2 = plt.subplot(15,1,2, sharex = ax1)
ax3 = plt.subplot(15,1,3, sharex = ax1)
ax_autoscoring = plt.subplot(15,1,5, )
ax_proba = plt.subplot(15,1,6, )
ax_epoch_marker = plt.subplot(15,1,7, )
ax_epoch_light = plt.subplot(15,1,8, sharex = ax_epoch_marker)
ax_acc = plt.subplot(15,1,9, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(15,1,10, sharex = ax_epoch_marker)
ax_noise = plt.subplot(15,1,11, sharex = ax_epoch_marker)
ax_EMG = plt.subplot(15,1,12, sharex = ax_epoch_marker)
ax_EMG2 = plt.subplot(15,1,13, sharex = ax_epoch_marker)
ax_EMG3 = plt.subplot(15,1,14, sharex = ax_epoch_marker)
ax_TFR_short = plt.subplot(15,1,4, sharex = ax1)
ax4 = plt.subplot(15,1,15, sharex = ax_epoch_marker)
ax4.grid(True)
ax1.get_xaxis().set_visible(False)
ax2.get_xaxis().set_visible(False)
ax3.get_xaxis().set_visible(False)
ax_epoch_marker.get_xaxis().set_visible(False)
ax_epoch_light.get_xaxis().set_visible(False)
ax_EMG.get_xaxis().set_visible(False)
ax_TFR_short.get_xaxis().set_visible(False)
ax_acc.get_xaxis().set_visible(False)
ax_acc.set_yticks([])
ax_noise.set_yticks([])
ax_noise.get_xaxis().set_visible(False)
ax_acc.set_ylim([-1.4, 1.4])
plt.subplots_adjust(hspace = 0)
ax1.set_title('Dreamento: post-processing ')
im = ax3.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax3.set_xlim([0, len(data)/256])
ax3.set_ylim((fmin, 25))
ax3.set_ylabel('EEG L-Freq(Hz)', rotation = 80, fontsize=9)
im2 = ax_TFR_short.pcolormesh(t2, f2, Sxx2, norm=norm2, cmap=cmap, antialiased=True,
shading="auto")
# Add colorbar
if add_colorbar == True:
cbar = fig.colorbar(im, ax=ax3, shrink=0.95, fraction=0.1, aspect=25, pad=0.01)
cbar.ax3.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=5)
#axes[1].set_ylim([-200, 200])
#ax4.set_xlim([0, len(data)])
ax4.set_ylabel('EEG (uV)')
ax4.set_ylim([-150, 150])
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=9)
# Opening JSON file
f = open(path_to_json_markers,)
# returns JSON object as a dictionary
markers = json.load(f)
markers_details = list(markers.values())
self.markers_details = markers_details
self.marker_keys = list(markers.keys())
self.counter_markers = 0
self.palette = itertools.cycle(sns.color_palette())
for counter, marker in enumerate(markers.keys()):
if marker.split()[0] == 'MARKER':
if 'manual' in markers_to_show:
self.counter_markers = self.counter_markers + 1
self.color_markers = next(self.palette)
marker_loc = int(marker.split()[-1])
ax1.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = str(self.counter_markers)+'. '+markers_details[counter], linewidth = 3, color = self.color_markers)
ax1.set_ylim([fmin, fmax])
ax_epoch_marker.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter], linewidth = 3, color = self.color_markers)
ax_epoch_marker.text(marker_loc/256+.1, int((fmin+fmax)/2), str(self.counter_markers ), verticalalignment='center', color = self.color_markers)
if marker.split()[0] == 'SOUND':
if 'sound' in markers_to_show:
marker_loc = int(marker.split()[-1])
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+ markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Audio', verticalalignment='center', color = 'blue')
elif marker.split()[0] == 'LIGHT':
if 'light' in markers_to_show:
marker_loc = int(marker.split()[-1])
if 'Vib: False' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Light', verticalalignment='center', color = 'red')
elif 'Vib: True' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Vibration', verticalalignment='center', color = 'green')
ax1.set_yticks([])
ax2.set_yticks([])
ax_epoch_marker.set_yticks([])
ax_epoch_light.set_yticks([])
#ax_epoch_marker.set_xticks([])
#ax_epoch_light.set_xticks([])
ax2.set_ylabel('Stimulation(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax1.set_ylabel('Markers(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax_epoch_marker.set_ylabel('Markers(epoch)', rotation = 0, labelpad=30, fontsize=8)
ax_epoch_light.set_ylabel('Stimulation(epoch)', rotation = 0, labelpad=30,fontsize=8)
ax_acc.set_ylabel('Acc', rotation = 0, labelpad=30, fontsize=8)
ax_noise.set_ylabel('Sound', rotation = 0, labelpad=30, fontsize=8)
ax_EMG.set_ylabel('EMG1 (uV)',rotation = 0, labelpad=30, fontsize=8)
ax_EMG2.set_ylabel('EMG2 (uV)',rotation = 0, labelpad=30, fontsize=8)
ax_EMG3.set_ylabel(' EMG1 - EMG2 (uV) ',rotation = 0, labelpad=30, fontsize=8)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
# =============================================================================
# ax_epoch_light.spines[["left", 'right']].set_visible(False)
# ax_epoch_marker.spines[["left", 'right']].set_visible(False)
# ax_acc.spines[["top", "bottom"]].set_visible(False)
# ax_noise.spines[["top", "bottom"]].set_visible(False)
# =============================================================================
time_axis = np.round(np.arange(0, len(data)) / 256 , 2)
ax4.set_xlabel('time (s)')
#ax4.set_xticks(np.arange(len(data)), time_axis)
ax4.set_xlim([0, 30])#len(data)])
leg = ax1.legend(fontsize=9, bbox_to_anchor=(1, 0), loc = 'upper left')
leg.set_draggable(state=True)
# plot acc
ax_acc.plot(np.arange(len(self.acc_x[self.samples_before_begin:]))/256, self.acc_x[self.samples_before_begin:], linewidth = 2 , color = 'blue')
ax_acc.plot(np.arange(len(self.acc_y[self.samples_before_begin:]))/256, self.acc_y[self.samples_before_begin:], linewidth = 2, color = 'red')
ax_acc.plot(np.arange(len(self.acc_z[self.samples_before_begin:]))/256, self.acc_z[self.samples_before_begin:], linewidth = 2, color = 'green')
# Plot ppg
ax_ppg.plot(np.arange(len(self.ppg_data))/256, self.ppg_data, linewidth = 2 , color = 'olive')
ax_ppg.set_ylim([-100, 100])
ax_ppg.set_ylabel('PPG', rotation = 0, labelpad=30, fontsize=8)
ax_ppg.set_yticks([])
# plot noise
ax_noise.plot(np.arange(len(self.noise_data[self.samples_before_begin:]))/256, self.noise_data[self.samples_before_begin:], linewidth = 2, color = 'navy')
#ax4.get_xaxis().set_visible(False)
self.str_first_subplot = str(ax3)
fig.canvas.mpl_connect('key_press_event', self.pan_nav_EMG_autoscoring)
fig.canvas.mpl_connect('button_press_event', self.onclick_EMG_autoscoring)
#ax1.set_xlim([0, 7680])#len(data)])
#ax2.set_xlim([0, 7680])#len(data)])
#ax3.set_xlim([0, len(data)])
#ax4.set_xlim([0, 7680])#len(data)])
self.sync_with_dreamento = True
self.autoscoring()
stages = self.y_pred
#stages = np.row_stack((stages, stages[-1]))
x = np.arange(len(stages))
self.stage_autoscoring = stages
# Change the order of classes: REM and wake on top
x = []
y = []
for i in np.arange(len(stages)):
s = stages[i]
if s== 0 : p = -0
if s== 4 : p = -1
if s== 1 : p = -2
if s== 2 : p = -3
if s== 3 : p = -4
if i!=0:
y.append(p)
x.append(i-1)
y.append(p)
x.append(i)
ax_autoscoring.step(x, y, where='post', color = 'black')
rem = [i for i,j in enumerate(self.y_pred) if (self.y_pred[i]==4)]
for i in np.arange(len(rem)) -1:
ax_autoscoring.plot([rem[i]-1, rem[i]], [-1,-1] , linewidth = 2, color = 'red')
#ax_autoscoring.scatter(rem, -np.ones(len(rem)), color = 'red')
ax_autoscoring.set_yticks([0,-1,-2,-3,-4], ['Wake','REM', 'N1', 'N2', 'SWS'],\
fontsize = 8)
ax_autoscoring.set_xlim([np.min(x), np.max(x)])
ax_proba.set_xlim([np.min(x), np.max(x)])
self.y_pred_proba.plot(ax = ax_proba, kind="area", alpha=0.8, stacked=True, lw=0, color = ['black', 'olive', 'deepskyblue', 'purple', 'red'])
ax_proba.legend(loc = 'right', prop={'size': 6})
ax_proba.set_yticks([])
ax_proba.set_xticks([])
print(f'autoscoring shape {np.shape(self.stage_autoscoring)}')
self.up_sampled_predicted_hypno = yasa.hypno_upsample_to_data(hypno=self.stage_autoscoring, sf_hypno = 1/30, data=data, sf_data=256, verbose=True)
print(f'autoscoring shape after up-sampling: {np.shape(self.up_sampled_predicted_hypno)}')
# read EMG
if (self.path_to_EMG[-4:] == 'vhdr' or self.path_to_EMG[-4:] == 'VHDR'):
self.EMG_raw = mne.io.read_raw_brainvision(self.path_to_EMG)
elif (self.path_to_EMG[-3:] == 'edf' or self.path_to_EMG[-3:] == 'EDF'):
self.EMG_raw = mne.io.read_raw_edf(self.path_to_EMG, preload = True)
self.EMG_filtered = self.EMG_raw.filter(l_freq=10, h_freq=100)
self.EMG_filtered = self.EMG_filtered.resample(int(256))
self.EMG_filtered_data1 = self.EMG_filtered.get_data()[0]
self.EMG_filtered_data2 = self.EMG_filtered.get_data()[1]
self.EMG_filtered_data1_minus_2 = self.EMG_filtered_data1 - self.EMG_filtered_data2
self.desired_EMG_scale_val = int(self.EMG_scale_options_val.get())
# =============================================================================
# if (self.path_to_EMG[-4:] == 'vhdr' or self.path_to_EMG[-4:] == 'VHDR'):
#
# self.desired_EMG_scale= [-1e-6* self.desired_EMG_scale_val, 1e-6* self.desired_EMG_scale_val]
# elif (self.path_to_EMG[-3:] == 'edf' or self.path_to_EMG[-3:] == 'EDF'):
#
# #self.desired_EMG_scale= [-1 * self.desired_EMG_scale_val, 1* self.desired_EMG_scale_val]
# self.desired_EMG_scale= [-1e-6* self.desired_EMG_scale_val, 1e-6* self.desired_EMG_scale_val]
# =============================================================================
# detect if the EMG data is in uV or V
if np.std(self.EMG_filtered_data1) < 1:
self.desired_EMG_scale= [-1e-6* self.desired_EMG_scale_val, 1e-6* self.desired_EMG_scale_val]
else:
self.desired_EMG_scale= [-1 * self.desired_EMG_scale_val, 1* self.desired_EMG_scale_val]
# Check whether the user already synced EMG vs. EEG or not
print(f'shape EMG signals = {np.shape(self.EMG_filtered_data1)}')
if self.flag_sync_EEG_EMG == True:
print(f'sync samples: {str(self.samples_before_begin_EMG_Dreamento)} with shape {str(np.shape(self.samples_before_begin_EMG_Dreamento))}')
print(f'sync criterion: {self.flag_sign_samples_before_begin_EMG_Dreamento}')
if self.flag_sign_samples_before_begin_EMG_Dreamento == 'eeg_event_earlier':
print('Detected that the event occured earlier in EEG than the EMG signal')
self.EMG_filtered_data1 = self.EMG_filtered_data1[self.samples_before_begin_EMG_Dreamento:]
self.EMG_filtered_data2 = self.EMG_filtered_data2[self.samples_before_begin_EMG_Dreamento:]
self.EMG_filtered_data1_minus_2 = self.EMG_filtered_data1_minus_2[self.samples_before_begin_EMG_Dreamento:]
elif self.flag_sign_samples_before_begin_EMG_Dreamento == 'emg_event_earlier':
print('Detected that the event occured earlier in EMG than the EEG signal')
print(f'EMG shape before alignment is : {np.shape(self.EMG_filtered_data1)}')
self.EMG_filtered_data1 = np.append(self.samples_before_begin_EMG_Dreamento, self.EMG_filtered_data1)
self.EMG_filtered_data2 = np.append(self.samples_before_begin_EMG_Dreamento, self.EMG_filtered_data2)
self.EMG_filtered_data1_minus_2 = np.append(self.samples_before_begin_EMG_Dreamento, self.EMG_filtered_data1_minus_2)
print(f'EMG shape after alignment is : {np.shape(self.EMG_filtered_data1)}')
ax_EMG.plot(np.arange(len(self.EMG_filtered_data1))/256, self.EMG_filtered_data1, color = (84/255,164/255,75/255))
ax_EMG2.plot(np.arange(len(self.EMG_filtered_data2))/256, self.EMG_filtered_data2, color = (24/255,100/255,160/255))
ax_EMG3.plot(np.arange(len(self.EMG_filtered_data1_minus_2))/256, self.EMG_filtered_data1_minus_2, color = (160/255,10/255,22/255))
ax_EMG.set_ylim(self.desired_EMG_scale)
ax_EMG2.set_ylim(self.desired_EMG_scale)
ax_EMG3.set_ylim(self.desired_EMG_scale)
ax_EMG.grid(True)
ax_EMG2.grid(True)
ax_EMG3.grid(True)
ax_EMG.set_yticks([])
ax_EMG3.set_yticks([])
ax_EMG2.set_yticks((self.desired_EMG_scale[0], 0, self.desired_EMG_scale[1]))
#plt.subplots_adjust(left=0.1, right=0.1, top=0.9, bottom=0.1)
plt.tight_layout()
plt.show()
if int(self.plot_EMG_quality_evaluation.get()) == 1:
self.assess_EMG_data_quality()
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = data
roc = data_R
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=self.up_sampled_predicted_hypno, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
spindle_events_R = yasa.spindles_detect(data_R, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_R * mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_R, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_R * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_L_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_R_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_L_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_R_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax4.plot(np.arange(len(data))/256, data , color = 'slategrey', linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = 'black', linewidth = 1)
else:
ax4.plot(np.arange(len(data))/256, data, color = (160/255, 70/255, 160/255), linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, SO_R_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, spindles_L_highlight, 'green')
ax4.plot(np.arange(len(data_R))/256, spindles_R_highlight, 'green')
# =============================================================================
# global sig_emg_1, sig_emg_2, sig_emg_3
#
# sig_emg_1 = self.EMG_filtered_data1
# sig_emg_2 = self.EMG_filtered_data2
# sig_emg_3 = self.EMG_filtered_data1_minus_2
#
# =============================================================================
print('Computing EMG TFR ...')
if int(self.plot_EMG_quality_evaluation.get()) == 1:
self.assess_EMG_data_quality()
print('preparing EMG TFR ...')
# TFR EMG 1
ax_TFR_EMG1.pcolormesh(self.t1_EMG, self.f1_EMG, self.Sxx1_EMG, norm=self.norm1_EMG, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax_TFR_EMG1.set_xlim(0, self.t1_EMG.max())
ax_TFR_EMG1.set_xticks([])
ax_TFR_EMG1.set_yticks([])
# TFR EMG 2
ax_TFR_EMG2.pcolormesh(self.t2_EMG, self.f2_EMG, self.Sxx2_EMG, norm=self.norm2_EMG, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax_TFR_EMG2.set_xlim(0, self.t2_EMG.max())
ax_TFR_EMG2.set_xticks([])
ax_TFR_EMG2.set_yticks([])
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=9)
# TFR EMG 3
ax_TFR_EMG3.pcolormesh(self.t3_EMG, self.f3_EMG, self.Sxx3_EMG, norm=self.norm2_EMG, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax_TFR_EMG3.set_xlim(0, self.t3_EMG.max())
ax_TFR_EMG3.set_xticks([])
ax_TFR_EMG3.set_yticks([])
# =============================================================================
# ax_EMG.set_xticks([])
# ax_EMG2.set_xticks([])
# ax_EMG3.set_xticks([])
# =============================================================================
if int(self.deactivate_markers.get())==1:
print('=======================================')
print(f'The user deactivated the markers and stimulation axes. (deactivate = {int(self.deactivate_markers.get())})')
ax1.set_visible(False)
ax2.set_visible(False)
ax_epoch_light.set_visible(False)
ax_epoch_marker.set_visible(False)
return fig, markers_details
#%% AssignMarkersToRecordedData EEG + TFR
def AssignMarkersToRecordedData_EEG_TFR_noEMG(self, data, data_R, sf, path_to_json_markers, markers_to_show = ['light', 'manual', 'sound'],\
win_sec=30, fmin=0.3, fmax=40,
trimperc=5, cmap='RdBu_r', add_colorbar = False):
"""
The main function: create the main display window of Offline Dreamento
:param self: access the attributes and methods of the class
:param data: EEG L data
:param data_R: EEG R data
:param sf: sampling frequency
:param path_to_json_markers: path to the annotation file (generated by Dreamento)
:param markers_to_show: the desired type of markers to show ['light', 'manual', 'sound']
:param win_sec: the window size to compute spectrogram
:param fmin: relavant min frequency for spectrogram
:param fmax: relavant max frequency for spectrogram
:param trimperc: bidirectional trim factor for normalizing the spectrogram colormap
:param cmap: colormap of the spectrogram
:param add_colorbar: adding colorbar to spectrogram
:returns: fig, markers_details
"""
# =============================================================================
# Source code for plotting spectrogram: YASA (https://raphaelvallat.com/yasa/build/html/_modules/yasa/plotting.html#plot_spectrogram)
# [modified]
# =============================================================================
# Increase font size while preserving original
old_fontsize = plt.rcParams['font.size']
plt.rcParams.update({'font.size': 12})
# Safety checks
assert isinstance(data, np.ndarray), 'Data must be a 1D NumPy array.'
assert isinstance(sf, (int, float)), 'sf must be int or float.'
assert data.ndim == 1, 'Data must be a 1D (single-channel) NumPy array.'
assert isinstance(win_sec, (int, float)), 'win_sec must be int or float.'
assert isinstance(fmin, (int, float)), 'fmin must be int or float.'
assert isinstance(fmax, (int, float)), 'fmax must be int or float.'
assert fmin < fmax, 'fmin must be strictly inferior to fmax.'
assert fmax < sf / 2, 'fmax must be less than Nyquist (sf / 2).'
# Calculate multi-taper spectrogram
nperseg = int(10 * sf) #int(win_sec * sf / 8)
assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.'
f, t, Sxx = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=0)
Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs = np.logical_and(f >= fmin, f <= fmax)
Sxx = Sxx[good_freqs, :]
f = f[good_freqs]
#t *= 256 # Convert t to hours
# Normalization
vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc])
norm = Normalize(vmin=vmin, vmax=vmax)
#!!!!!!!!!! SHORT SPECTROGRAM!! Calculate multi-taper spectrogram
# =============================================================================
# nperseg2 = int(.2 * sf) #int(win_sec * sf / 8)
# assert data.size > 2 * nperseg2, 'Data length must be at least 2 * win_sec.'
# f2, t2, Sxx2 = spectrogram_lspopt(data, sf, nperseg=nperseg2, noverlap=0)
# Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# print(f'f: {np.shape(f)}, f2: {np.shape(f2)}, t: {np.shape(t)}, t2: {np.shape(t2)}, Sxx: {np.shape(Sxx)}, Sxx2: {np.shape(Sxx2)}')
# # Select only relevant frequencies (up to 30 Hz)
# good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
# Sxx2 = Sxx2[good_freqs2, :]
# f2 = f2[good_freqs2]
# #t *= 256 # Convert t to hours
#
# # Normalization
# vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
# norm2 = Normalize(vmin=vmin2, vmax=vmax2)
assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.'
f2, t2, Sxx2 = spectrogram_lspopt(data_R, sf, nperseg = nperseg, noverlap = 0)
Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
Sxx2 = Sxx2[good_freqs2, :]
f2 = f2[good_freqs2]
#t *= 256 # Convert t to hours
# Normalization
vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
norm2 = Normalize(vmin=vmin2, vmax=vmax2)
# =============================================================================
# =============================================================================
# gs1 = gridspec.GridSpec(2, 1)
# gs1.update(wspace=0.005, hspace=0.0001)
# =============================================================================
# =============================================================================
# if int(self.automatic_REM_event_deetction.get()) == 1:
#
# print(f'Initiating automatic REM event detection ...')
# loc = data
# roc = data_R
# amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
# duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
# freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
# remove_outliers = int(self.checkbox_remove_outliers_val.get())
# print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
# self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=None, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
# print('REM events have been successfully identified!')
#
# mask = self.REM_events.get_mask()
#
# loc_highlight = loc * mask[0, :]
# roc_highlight = roc * mask[1, :]
#
# loc_highlight[loc_highlight == 0] = np.nan
# roc_highlight[roc_highlight == 0] = np.nan
#
# if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
#
# path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
# print(f'saivng REM detection results in {path_save_REM_results}')
# self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# =============================================================================
if int(self.is_autoscoring.get()) == 0:
fig,AX = plt.subplots(nrows=10, figsize=(16, 9), gridspec_kw={'height_ratios': [1, 1, 10,10,1, 2, 2, 2, 2, 10]})
ax1 = plt.subplot(10,1,1)
ax2 = plt.subplot(10,1,2, sharex = ax1)
ax3 = plt.subplot(10,1,3, sharex = ax1)
ax_epoch_marker = plt.subplot(10,1,5, )
ax_epoch_light = plt.subplot(10,1,6, sharex = ax_epoch_marker)
ax_acc = plt.subplot(10,1,7, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(10,1,8, sharex = ax_epoch_marker)
ax_noise = plt.subplot(10,1,9, sharex = ax_epoch_marker)
ax_TFR_short = plt.subplot(10,1,4, sharex = ax1)
ax4 = plt.subplot(10,1,10, sharex = ax_epoch_marker)
ax4.grid(True)
ax1.get_xaxis().set_visible(False)
ax2.get_xaxis().set_visible(False)
ax3.get_xaxis().set_visible(False)
ax_TFR_short.get_xaxis().set_visible(False)
ax_epoch_marker.get_xaxis().set_visible(False)
ax_epoch_light.get_xaxis().set_visible(False)
ax_acc.get_xaxis().set_visible(False)
ax_acc.set_yticks([])
ax_noise.set_yticks([])
ax_noise.get_xaxis().set_visible(False)
ax_acc.set_ylim([-1.4, 1.4])
plt.subplots_adjust(hspace = 0)
ax1.set_title('Dreamento: post-processing ')
im = ax3.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax3.set_xlim([0, len(data)/256])
ax3.set_ylim((fmin, 25))
ax3.set_ylabel('EEG L-Freq(Hz)', rotation = 80, fontsize=9)
im2 = ax_TFR_short.pcolormesh(t2, f2, Sxx2, norm=norm2, cmap=cmap, antialiased=True,
shading="auto")
# Add colorbar
if add_colorbar == True:
cbar = fig.colorbar(im, ax=ax3, shrink=0.95, fraction=0.1, aspect=25, pad=0.01)
cbar.ax3.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=5)
# =============================================================================
# # PLOT EEG
# if int(self.automatic_REM_event_deetction.get()) == 1:
#
# ax4.plot(np.arange(len(data))/256, data , color = 'slategrey', linewidth = 1)
# ax4.plot(np.arange(len(data_R))/256, data_R, color = 'black', linewidth = 1)
# ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
# ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
# else:
#
# ax4.plot(np.arange(len(data))/256, data, color = (160/255, 70/255, 160/255), linewidth = 1)
# ax4.plot(np.arange(len(data_R))/256, data_R, color = (0/255, 128/255, 190/255), linewidth = 1)
# =============================================================================
#axes[1].set_ylim([-200, 200])
#ax4.set_xlim([0, len(data)])
ax4.set_ylabel('EEG (uV)')
ax4.set_ylim([-150, 150])
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=9)
# Opening JSON file
f = open(path_to_json_markers,)
# returns JSON object as a dictionary
markers = json.load(f)
markers_details = list(markers.values())
self.markers_details = markers_details
self.marker_keys = list(markers.keys())
self.counter_markers = 0
self.palette = itertools.cycle(sns.color_palette())
for counter, marker in enumerate(markers.keys()):
if marker.split()[0] == 'MARKER':
if 'manual' in markers_to_show:
self.counter_markers = self.counter_markers + 1
self.color_markers = next(self.palette)
marker_loc = int(marker.split()[-1])
ax1.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = str(self.counter_markers)+'. '+markers_details[counter], linewidth = 3, color = self.color_markers)
ax1.set_ylim([fmin, fmax])
ax_epoch_marker.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter], linewidth = 3, color = self.color_markers)
ax_epoch_marker.text(marker_loc/256+.1, int((fmin+fmax)/2), str(self.counter_markers ), verticalalignment='center', color = self.color_markers)
if marker.split()[0] == 'SOUND':
if 'sound' in markers_to_show:
marker_loc = int(marker.split()[-1])
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+ markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Audio', verticalalignment='center', color = 'blue')
elif marker.split()[0] == 'LIGHT':
if 'light' in markers_to_show:
marker_loc = int(marker.split()[-1])
if 'Vib: False' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Light', verticalalignment='center', color = 'red')
elif 'Vib: True' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Vibration', verticalalignment='center', color = 'green')
ax1.set_yticks([])
ax2.set_yticks([])
ax_epoch_marker.set_yticks([])
ax_epoch_light.set_yticks([])
#ax_epoch_marker.set_xticks([])
#ax_epoch_light.set_xticks([])
ax2.set_ylabel('Stimulation(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax1.set_ylabel('Markers(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax_epoch_marker.set_ylabel('Markers(epoch)', rotation = 0, labelpad=30, fontsize=8)
ax_epoch_light.set_ylabel('Stimulation(epoch)', rotation = 0, labelpad=30,fontsize=8)
ax_acc.set_ylabel('Acc', rotation = 0, labelpad = 30)#, labelpad=30, fontsize=8)
ax_noise.set_ylabel('Sound', rotation = 0, labelpad=30)#, fontsize=8)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax_epoch_light.spines[["left", 'right']].set_visible(False)
ax_epoch_marker.spines[["left", 'right']].set_visible(False)
ax_acc.spines[["top", "bottom"]].set_visible(False)
ax_noise.spines[["top", "bottom"]].set_visible(False)
time_axis = np.round(np.arange(0, len(data)) / 256 , 2)
ax4.set_xlabel('time (s)')
#ax4.set_xticks(np.arange(len(data)), time_axis)
ax4.set_xlim([0, 30])#len(data)])
leg = ax1.legend(fontsize=9, bbox_to_anchor=(1, 0), loc = 'upper left')
leg.set_draggable(state=True)
# plot acc
ax_acc.plot(np.arange(len(self.acc_x[self.samples_before_begin:]))/256, self.acc_x[self.samples_before_begin:], linewidth = 2 , color = 'blue')
ax_acc.plot(np.arange(len(self.acc_y[self.samples_before_begin:]))/256, self.acc_y[self.samples_before_begin:], linewidth = 2, color = 'red')
ax_acc.plot(np.arange(len(self.acc_z[self.samples_before_begin:]))/256, self.acc_z[self.samples_before_begin:], linewidth = 2, color = 'green')
# Plot ppg
ax_ppg.plot(np.arange(len(self.ppg_data))/256, self.ppg_data, linewidth = 2 , color = 'olive')
ax_ppg.set_ylim([-100, 100])
ax_ppg.set_ylabel('PPG', rotation = 0, labelpad = 30)
ax_ppg.set_yticks([])
# plot noise
ax_noise.plot(np.arange(len(self.noise_data[self.samples_before_begin:]))/256, self.noise_data[self.samples_before_begin:], linewidth = 2, color = 'navy')
#ax4.get_xaxis().set_visible(False)
self.str_first_subplot = str(ax3)
fig.canvas.mpl_connect('key_press_event', self.pan_nav_noEMG)
fig.canvas.mpl_connect('button_press_event', self.onclick_noEMG)
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = data
roc = data_R
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=None, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data, sf=256, ch_names=None, hypno=None, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
spindle_events_R = yasa.spindles_detect(data_R, sf=256, ch_names=None, hypno=None, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_R * mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data, sf=256, ch_names=None, hypno=None, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_R, sf=256, ch_names=None, hypno=None, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_R * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_L_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_R_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_L_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_R_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax4.plot(np.arange(len(data))/256, data , color = 'slategrey', linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = 'black', linewidth = 1)
else:
ax4.plot(np.arange(len(data))/256, data, color = (160/255, 70/255, 160/255), linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, SO_R_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, spindles_L_highlight, 'green')
ax4.plot(np.arange(len(data_R))/256, spindles_R_highlight, 'green')
else:
fig,AX = plt.subplots(nrows=12, figsize=(16, 9), gridspec_kw={'height_ratios': [1, 1, 10,10,4,4,2,2, 2, 2, 2, 10]})
ax1 = plt.subplot(12,1,1)
ax2 = plt.subplot(12,1,2, sharex = ax1)
ax3 = plt.subplot(12,1,3, sharex = ax1)
ax_autoscoring = plt.subplot(12,1,5, )
ax_proba = plt.subplot(12,1,6, )
ax_epoch_marker = plt.subplot(12,1,7, )
ax_epoch_light = plt.subplot(12,1,8, sharex = ax_epoch_marker)
ax_acc = plt.subplot(12,1,9, sharex = ax_epoch_marker)
ax_ppg = plt.subplot(12,1,10, sharex = ax_epoch_marker)
ax_noise = plt.subplot(12,1,11, sharex = ax_epoch_marker)
ax_TFR_short = plt.subplot(12,1,4, sharex = ax1)
ax4 = plt.subplot(12,1,12, sharex = ax_epoch_marker)
ax4.grid(True)
ax1.get_xaxis().set_visible(False)
ax2.get_xaxis().set_visible(False)
ax3.get_xaxis().set_visible(False)
ax_epoch_marker.get_xaxis().set_visible(False)
ax_TFR_short.get_xaxis().set_visible(False)
ax_epoch_light.get_xaxis().set_visible(False)
ax_acc.get_xaxis().set_visible(False)
ax_acc.set_yticks([])
ax_noise.set_yticks([])
ax_noise.get_xaxis().set_visible(False)
ax_acc.set_ylim([-1.4, 1.4])
plt.subplots_adjust(hspace = 0)
ax1.set_title('Dreamento: post-processing ')
im = ax3.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax3.set_xlim([0, len(data)/256])
ax3.set_ylim((fmin, 25))
ax3.set_ylabel('EEG L-Freq(Hz)', rotation = 80, fontsize=9)
im2 = ax_TFR_short.pcolormesh(t2, f2, Sxx2, norm=norm2, cmap=cmap, antialiased=True,
shading="auto")
# Add colorbar
if add_colorbar == True:
cbar = fig.colorbar(im, ax=ax3, shrink=0.95, fraction=0.1, aspect=25, pad=0.01)
cbar.ax3.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=5)
#axes[1].set_ylim([-200, 200])
#ax4.set_xlim([0, len(data)])
ax4.set_ylabel('EEG (uV)')
ax4.set_ylim([-150, 150])
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=10)#, labelpad=30, fontsize=8)
# Opening JSON file
f = open(path_to_json_markers,)
# returns JSON object as a dictionary
markers = json.load(f)
markers_details = list(markers.values())
self.markers_details = markers_details
self.marker_keys = list(markers.keys())
self.counter_markers = 0
self.palette = itertools.cycle(sns.color_palette())
for counter, marker in enumerate(markers.keys()):
if marker.split()[0] == 'MARKER':
if 'manual' in markers_to_show:
self.counter_markers = self.counter_markers + 1
self.color_markers = next(self.palette)
marker_loc = int(marker.split()[-1])
ax1.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = str(self.counter_markers)+'. '+markers_details[counter], linewidth = 3, color = self.color_markers)
ax1.set_ylim([fmin, fmax])
ax_epoch_marker.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter], linewidth = 3, color = self.color_markers)
ax_epoch_marker.text(marker_loc/256+.1, int((fmin+fmax)/2), str(self.counter_markers ), verticalalignment='center', color = self.color_markers)
if marker.split()[0] == 'SOUND':
if 'sound' in markers_to_show:
marker_loc = int(marker.split()[-1])
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = 'Audio: '+ markers_details[counter].split('/')[-1], linewidth = 3, color = 'blue')
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Audio', verticalalignment='center', color = 'blue')
elif marker.split()[0] == 'LIGHT':
if 'light' in markers_to_show:
marker_loc = int(marker.split()[-1])
if 'Vib: False' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'red', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Light', verticalalignment='center', color = 'red')
elif 'Vib: True' in markers_details[counter]:
ax2.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax2.set_ylim([fmin, fmax])
ax_epoch_light.plot([marker_loc/256, marker_loc/256], [fmin, fmax] , label = markers_details[counter].split(',')[0],color = 'green', linewidth = 3)
ax_epoch_light.text(marker_loc/256+.1, int((fmin+fmax)/2),'Vibration', verticalalignment='center', color = 'green')
ax1.set_yticks([])
ax2.set_yticks([])
ax_epoch_marker.set_yticks([])
ax_epoch_light.set_yticks([])
#ax_epoch_marker.set_xticks([])
#ax_epoch_light.set_xticks([])
ax2.set_ylabel('Stimulation(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax1.set_ylabel('Markers(overall)', rotation = 0, labelpad=30, fontsize=8)#.set_rotation(0)
ax_epoch_marker.set_ylabel('Markers(epoch)', rotation = 0, labelpad=30, fontsize=8)
ax_epoch_light.set_ylabel('Stimulation(epoch)', rotation = 0, labelpad=30,fontsize=8)
ax_acc.set_ylabel('Acc', rotation = 0, labelpad=30, fontsize=8)
ax_noise.set_ylabel('Sound', rotation = 0, labelpad=30, fontsize=8)
ax2.spines["right"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax2.spines["left"].set_visible(False)
ax1.spines["right"].set_visible(False)
ax1.spines["left"].set_visible(False)
ax_epoch_light.spines[["left", 'right']].set_visible(False)
ax_epoch_marker.spines[["left", 'right']].set_visible(False)
ax_acc.spines[["top", "bottom"]].set_visible(False)
ax_noise.spines[["top", "bottom"]].set_visible(False)
time_axis = np.round(np.arange(0, len(data)) / 256 , 2)
ax4.set_xlabel('time (s)')
#ax4.set_xticks(np.arange(len(data)), time_axis)
ax4.set_xlim([0, 30])#len(data)])
leg = ax1.legend(fontsize=9, bbox_to_anchor=(1, 0), loc = 'upper left')
leg.set_draggable(state=True)
# plot acc
ax_acc.plot(np.arange(len(self.acc_x[self.samples_before_begin:]))/256, self.acc_x[self.samples_before_begin:], linewidth = 2 , color = 'blue')
ax_acc.plot(np.arange(len(self.acc_y[self.samples_before_begin:]))/256, self.acc_y[self.samples_before_begin:], linewidth = 2, color = 'red')
ax_acc.plot(np.arange(len(self.acc_z[self.samples_before_begin:]))/256, self.acc_z[self.samples_before_begin:], linewidth = 2, color = 'green')
# Plot ppg
ax_ppg.plot(np.arange(len(self.ppg_data))/256, self.ppg_data, linewidth = 2 , color = 'olive')
ax_ppg.set_ylim([-100, 100])
ax_ppg.set_ylabel('PPG', rotation = 0, labelpad=30, fontsize=8)
ax_ppg.set_yticks([])
# plot noise
ax_noise.plot(np.arange(len(self.noise_data[self.samples_before_begin:]))/256, self.noise_data[self.samples_before_begin:], linewidth = 2, color = 'navy')
#ax4.get_xaxis().set_visible(False)
self.sync_with_dreamento = True
self.autoscoring()
stages = self.y_pred
#stages = np.row_stack((stages, stages[-1]))
x = np.arange(len(stages))
self.stage_autoscoring = stages
print(f'autoscoring shape {np.shape(self.stage_autoscoring)}')
self.up_sampled_predicted_hypno = yasa.hypno_upsample_to_data(hypno=self.stage_autoscoring, sf_hypno = 1/30, data=data, sf_data=256, verbose=True)
print(f'autoscoring shape after up-sampling: {np.shape(self.up_sampled_predicted_hypno)}')
# Change the order of classes: REM and wake on top
x = []
y = []
for i in np.arange(len(stages)):
s = stages[i]
if s== 0 : p = -0
if s== 4 : p = -1
if s== 1 : p = -2
if s== 2 : p = -3
if s== 3 : p = -4
if i!=0:
y.append(p)
x.append(i-1)
y.append(p)
x.append(i)
ax_autoscoring.step(x, y, where='post', color = 'black')
rem = [i for i,j in enumerate(self.y_pred) if (self.y_pred[i]==4)]
for i in np.arange(len(rem)) -1:
ax_autoscoring.plot([rem[i]-1, rem[i]], [-1,-1] , linewidth = 2, color = 'red')
ax_autoscoring.set_yticks([0,-1,-2,-3,-4], ['Wake','REM', 'N1', 'N2', 'SWS'], fontsize=8)
ax_autoscoring.set_xlim([np.min(x), np.max(x)])
ax_proba.set_xlim([np.min(x), np.max(x)])
self.y_pred_proba.plot(ax = ax_proba, kind="area", alpha=0.8, stacked=True, lw=0, color = ['black', 'navy', 'deepskyblue', 'purple', 'red'])
ax_proba.legend(loc = 'right', prop={'size': 6})
ax_proba.set_ylabel('Hypnodensity', rotation = 0, labelpad=40, fontsize=8)
ax_proba.set_yticks([])
ax_proba.set_xticks([])
self.str_first_subplot = str(ax3)
fig.canvas.mpl_connect('key_press_event', self.pan_nav_noEMG)
fig.canvas.mpl_connect('button_press_event', self.onclick_noEMG)
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = data
roc = data_R
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=self.up_sampled_predicted_hypno, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
spindle_events_R = yasa.spindles_detect(data_R, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_R * mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_R, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_R * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_L_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_R_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_L_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_R_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax4.plot(np.arange(len(data))/256, data , color = 'slategrey', linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = 'black', linewidth = 1)
else:
ax4.plot(np.arange(len(data))/256, data, color = (160/255, 70/255, 160/255), linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, SO_R_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, spindles_L_highlight, 'green')
ax4.plot(np.arange(len(data_R))/256, spindles_R_highlight, 'green')
return fig, markers_details
#%% Autoscoring caution
# =============================================================================
# def scoring_caution(self):
# if int(self.is_autoscoring.get()) == 1:
# messagebox.showinfo("Caution",f"The current version of DreamentoScorer is alpha - thus its generalizability is limited! Always double-check with manual scoring! \n N.B. For a proper performance of the DreamentoScorer both EEG channels should have acceptable quality. The scoring for the epochs of data loss is not reliable.")
# =============================================================================
#%% Autoscoring
def autoscoring(self, DreamentoScorer_path = '.\\DreamentoScorer\\',\
model_path = "PooledData_Full_20percent_valid.sav",\
standard_scaler_path = "StandardScaler_PooledDataset_Full_20percent_valid.sav",\
feat_selection_path = "Selected_Features_BoturaAfterTD=3_Bidirectional_Donders2022_19-04-2023.pickle",\
apply_post_scoring_N1_correction = True,
fs = 256):
# old CS model
# 'Dreamento_autoscoring_Lightgbm_td=3_bidirectional_version_alpha_trained_on_69_data.sav',\
# 'StandardScaler_Trainedon_3013097-06_1st_iter_091222.sav',\
# 'Selected_Features_BoturaAfterTD=3_Bidirectional_3013097-06_061222.pickle',\
# ==================================
import joblib
path_to_DreamentoScorer = DreamentoScorer_path
# Init dir tio read libraries
try:
# Change the current working Directory
os.chdir(path_to_DreamentoScorer)
print("Loading DreamentoScorer class ... ")
except OSError:
print("Can't change the Current Working Directory")
os.chdir('DreamentoScorer')
print(f'current path is {path_to_DreamentoScorer}')
from entropy.entropy import spectral_entropy
from DreamentoScorer import DreamentoScorer
self.SSN = DreamentoScorer(filename='', channel='', fs = fs, T = 30)
f_min = .3 #Hz
f_max = 30 #Hz
tic = time.time()
if self.sync_with_dreamento == True:
if int(self.is_filtering.get()) == 1:
# If it was initially set to filter data, there is no need to re-filter here
print('already filtered ... proceed with scoring ...')
EEG_L_filtered = self.sigHDRecorder[self.samples_before_begin:]
EEG_R_filtered = self.sigHDRecorder_r[self.samples_before_begin:]
else:
# If it was not initially set to filter data, it has to be filtered here
print('Filtering ...')
# if already filtered, take it, otherwise filter first
EEG_L_filtered = self.butter_bandpass_filter(data = self.sigHDRecorder[self.samples_before_begin:],\
lowcut=.3, highcut=30, fs = 256, order = 2)
EEG_R_filtered = self.butter_bandpass_filter(data = self.sigHDRecorder_r[self.samples_before_begin:],\
lowcut=.3, highcut=30, fs = 256, order = 2)
else:
if int(self.is_filtering.get()) == 1:
# If it was initially set to filter data, there is no need to re-filter here
print('already filtered ... proceed with scoring ...')
EEG_L_filtered = self.sigHDRecorder
EEG_R_filtered = self.sigHDRecorder_r
else:
# If it was not initially set to filter data, it has to be filtered here
print('Filtering ...')
# if already filtered, take it, otherwise filter first
EEG_L_filtered = self.butter_bandpass_filter(data = self.sigHDRecorder,\
lowcut=.3, highcut=30, fs = 256, order = 2)
EEG_R_filtered = self.butter_bandpass_filter(data = self.sigHDRecorder_r,\
lowcut=.3, highcut=30, fs = 256, order = 2)
T = 30 #secs
len_epoch = fs * T
start_epoch = 0
n_channels = 1
print(f'Data shape : {np.shape(EEG_R_filtered)}')
print('Truncating the last epoch tails ...')
EEG_L_filtered = EEG_L_filtered[0:EEG_L_filtered.shape[0] - EEG_L_filtered.shape[0]%len_epoch]
EEG_R_filtered = EEG_R_filtered[0:EEG_R_filtered.shape[0] - EEG_R_filtered.shape[0]%len_epoch]
print('Segmenting data into 30 s epochs ...')
EEG_L_filtered_epoched = np.reshape(EEG_L_filtered,
(n_channels, len_epoch,
int(EEG_L_filtered.shape[0]/len_epoch)), order='F' )
EEG_R_filtered_epoched = np.reshape(EEG_R_filtered,
(n_channels, len_epoch,
int(EEG_R_filtered.shape[0]/len_epoch)), order='F' )
# Ensure equalituy of length for arrays:
assert np.shape(EEG_L_filtered_epoched)[1] == np.shape(EEG_R_filtered_epoched)[1], 'Different epoch numbers!'
print(f'SHAPE OF EEG_L_filtered_epoched {np.shape(EEG_L_filtered_epoched)}')
# Extract features
tic = time.time()
print(f'shape after epoching: {np.shape(EEG_L_filtered_epoched)}')
print('Extracting features for DreamentoScorer ...')
for k in np.arange(np.shape(EEG_L_filtered_epoched)[0]):
print('Extracting features from channel 1 ...')
feat_L = self.SSN.FeatureExtraction_per_subject(Input_data = EEG_L_filtered_epoched[k,:,:], fs = fs)
print('Extracting features from channel 2 ...')
feat_R = self.SSN.FeatureExtraction_per_subject(Input_data = EEG_R_filtered_epoched[k,:,:], fs = fs)
# Concatenate features
print(f'concatenating features of size {np.shape(feat_L)} and {np.shape(feat_R)}')
Feat_all_channels = np.column_stack((feat_L,feat_R))
# Scoring
X_test = Feat_all_channels
X_test = self.SSN.replace_NaN_with_mean(X_test)
# Replace any probable inf
X_test = self.SSN.replace_inf_with_mean(X_test)
# Z-score features
print('Importing DreamentoScorer model ...')
sc_fname = standard_scaler_path#'StandardScaler_TrainedonQS_1st_iter_To_transform_ZmaxDonders'
sc = joblib.load(sc_fname)
X_test = sc.transform(X_test)
# Add time dependence to the data classification
td = 3 # 5epochs of memory
print('Adding time dependency ...')
X_test_td = self.SSN.add_time_dependence_bidirectional(X_test, n_time_dependence=td,\
padding_type = 'sequential')
# Load selected features
print('loading results of feature selection from the trained data')
path_selected_feats = feat_selection_path
with open(path_selected_feats, "rb") as f:
selected_feats_ind = pickle.load(f)
X_test = X_test_td[:, selected_feats_ind]
# Load DreamentoScorer
print('Loading scoring model ...')
model_dir = model_path
DreamentoScorer = joblib.load(model_dir)
self.y_pred = DreamentoScorer.predict(X_test)
y_pred_proba = DreamentoScorer.predict_proba(X_test)
self.y_pred_proba = pd.DataFrame(y_pred_proba, columns = ['Wake', 'N1', 'N2', 'SWS', 'REM'])
if apply_post_scoring_N1_correction == True:
# Post-scoring: Replacing the REM deetcted before the first N2 with N1
self.post_scoring_N1(print_replaced_epochs = True, replace_values_in_prediciton = True)
self.sleep_stats = self.retrieve_sleep_statistics(hypno = self.y_pred, sf_hyp = 1 / 30,\
sleep_stages = [0, 1, 2, 3, 4])
#%% Post-scoring to imrpove N1 detection
def post_scoring_N1(self, print_replaced_epochs = True, replace_values_in_prediciton = True):
self.y_pred_post_processed = self.y_pred
first_N2_index = [i for i,j in enumerate(self.y_pred) if (self.y_pred[i]==2)][0]
scores_before_first_N2 = self.y_pred[:first_N2_index]
REM_before_first_N2 = [i for i,j in enumerate(scores_before_first_N2) if (scores_before_first_N2[i]==4)]
for idx_actual_N1 in REM_before_first_N2:
self.y_pred_post_processed[idx_actual_N1] = 1
if print_replaced_epochs == True:
print(f'the REM detected in epoch {idx_actual_N1} has been replaced by N1 ...')
if replace_values_in_prediciton ==True:
self.y_pred[idx_actual_N1] = 1
#%% Bulk autoscoring function
def bulk_autoscoring(self, DreamentoScorer_path = ".\\DreamentoScorer\\",\
model_path = "PooledData_Full_20percent_valid.sav",\
standard_scaler_path = "StandardScaler_PooledDataset_Full_20percent_valid.sav",\
feat_selection_path = "Selected_Features_BoturaAfterTD=3_Bidirectional_Donders2022_19-04-2023.pickle",\
apply_post_scoring_N1_correction = True,\
fs = 256):
import joblib
path_to_DreamentoScorer = DreamentoScorer_path
messagebox.showinfo(title = "Bulk Autoscoring", message = 'Autoscoring started ... \nDepending on the number of scorings, this may take a while ... \nIf you selected to store the results, they will be stored in the data folder ...\n An Excel file including the results from all subjects will be stored in the same directory as the initial .txt file\nPlease click on OK and be patient ...')
# Init dir tio read libraries
try:
# Change the current working Directory
os.chdir(path_to_DreamentoScorer)
print("Loading DreamentoScorer class ... ")
except OSError:
print("Can't change the Current Working Directory")
os.chdir('DreamentoScorer')
print(f'current path is {path_to_DreamentoScorer}')
from entropy.entropy import spectral_entropy
from DreamentoScorer import DreamentoScorer
self.SSN = DreamentoScorer(filename='', channel='', fs = fs, T = 30)
f_min = fmin = .3 #Hz
f_max = fmax = 30 #Hz
tic = time.time()
print(f'i reached here {self.bulk_autoscoring_files_list[0]}')
self.folders_to_be_autoscored = pd.read_csv(self.bulk_autoscoring_files_list[0], header=None).to_numpy()
print(f'folders to be autoscored are detected as follows: {self.folders_to_be_autoscored}')
counter_scoring = 0
self.all_stats = dict()
for folder_autoscoring in self.folders_to_be_autoscored:
folder_autoscoring = folder_autoscoring[0]
counter_scoring = counter_scoring + 1
print(f'autoscoring folder: {folder_autoscoring} [{counter_scoring} / {len(self.folders_to_be_autoscored)}]')
#sanity check:
if folder_autoscoring[-1] != '/' or folder_autoscoring[-1] != '\\':
folder_autoscoring = folder_autoscoring + '\\'
data_L = mne.io.read_raw_edf(folder_autoscoring + 'EEG L.edf')
raw_data_L = data_L.get_data()
self.sigHDRecorder = np.ravel(raw_data_L)
data_r = mne.io.read_raw_edf(folder_autoscoring + 'EEG R.edf')
raw_data_r = data_r.get_data()
self.sigHDRecorder_r = np.ravel(raw_data_r)
# There has to be filtering in her anyways
print('Filtering ...')
# if already filtered, take it, otherwise filter first
EEG_L_filtered = self.butter_bandpass_filter(data = self.sigHDRecorder,\
lowcut=.3, highcut=30, fs = 256, order = 2)
EEG_R_filtered = self.butter_bandpass_filter(data = self.sigHDRecorder_r,\
lowcut=.3, highcut=30, fs = 256, order = 2)
T = 30 #secs
len_epoch = fs * T
start_epoch = 0
n_channels = 1
print(f'Data shape : {np.shape(EEG_R_filtered)}')
print('Truncating the last epoch tails ...')
EEG_L_filtered = EEG_L_filtered[0:EEG_L_filtered.shape[0] - EEG_L_filtered.shape[0]%len_epoch]
EEG_R_filtered = EEG_R_filtered[0:EEG_R_filtered.shape[0] - EEG_R_filtered.shape[0]%len_epoch]
print('Segmenting data into 30 s epochs ...')
EEG_L_filtered_epoched = np.reshape(EEG_L_filtered,
(n_channels, len_epoch,
int(EEG_L_filtered.shape[0]/len_epoch)), order='F' )
EEG_R_filtered_epoched = np.reshape(EEG_R_filtered,
(n_channels, len_epoch,
int(EEG_R_filtered.shape[0]/len_epoch)), order='F' )
# Ensure equalituy of length for arrays:
assert np.shape(EEG_L_filtered_epoched)[1] == np.shape(EEG_R_filtered_epoched)[1], 'Different epoch numbers!'
print(f'SHAPE OF EEG_L_filtered_epoched {np.shape(EEG_L_filtered_epoched)}')
# Extract features
tic = time.time()
print(f'shape after epoching: {np.shape(EEG_L_filtered_epoched)}')
print('Extracting features for DreamentoScorer ...')
for k in np.arange(np.shape(EEG_L_filtered_epoched)[0]):
t_st = time.time()
print('Extracting features from channel 1 ...')
feat_L = self.SSN.FeatureExtraction_per_subject(Input_data = EEG_L_filtered_epoched[k,:,:], fs = fs)
print('Extracting features from channel 2 ...')
feat_R = self.SSN.FeatureExtraction_per_subject(Input_data = EEG_R_filtered_epoched[k,:,:], fs = fs)
# Concatenate features
print(f'concatenating features of size {np.shape(feat_L)} and {np.shape(feat_R)}')
Feat_all_channels = np.column_stack((feat_L,feat_R))
t_end = time.time()
print(f'Features extracted in {t_end - t_st} s')
# Scoring
X_test = Feat_all_channels
X_test = self.SSN.replace_NaN_with_mean(X_test)
# Replace any probable inf
X_test = self.SSN.replace_inf_with_mean(X_test)
# Z-score features
print('Importing DreamentoScorer model ...')
sc_fname = standard_scaler_path#'StandardScaler_TrainedonQS_1st_iter_To_transform_ZmaxDonders'
sc = joblib.load(sc_fname)
X_test = sc.transform(X_test)
# Add time dependence to the data classification
td = 3 # epochs of memory
print('Adding time dependency ...')
X_test_td = self.SSN.add_time_dependence_bidirectional(X_test, n_time_dependence=td,\
padding_type = 'sequential')
# Load selected features
print('loading results of feature selection from the trained data')
path_selected_feats = feat_selection_path
with open(path_selected_feats, "rb") as f:
selected_feats_ind = pickle.load(f)
X_test = X_test_td[:, selected_feats_ind]
# Load DreamentoScorer
print('Loading scoring model ...')
model_dir = model_path
print(f'DreamentoScorer model retrieved from: {model_dir}')
DreamentoScorer = joblib.load(model_dir)
self.y_pred = DreamentoScorer.predict(X_test)
y_pred_proba = DreamentoScorer.predict_proba(X_test)
self.y_pred_proba = pd.DataFrame(y_pred_proba, columns = ['Wake', 'N1', 'N2', 'SWS', 'REM'])
if apply_post_scoring_N1_correction == True:
# Post-scoring: Replacing the REM deetcted before the first N2 with N1
self.post_scoring_N1(print_replaced_epochs = True, replace_values_in_prediciton = True)
self.sleep_stats = self.retrieve_sleep_statistics(hypno = self.y_pred, sf_hyp = 1 / 30,\
sleep_stages = [0, 1, 2, 3, 4])
data1 = EEG_L_filtered
data2 = EEG_R_filtered
# Safety checks
sf = 256
win_sec = 30
trimperc=2.5
cmap="RdBu_r"
vmin=None
vmax=None
assert isinstance(data1, np.ndarray), "data1 must be a 1D NumPy array."
assert isinstance(data2, np.ndarray), "data1 must be a 1D NumPy array."
assert isinstance(sf, (int, float)), "sf must be int or float."
assert data1.ndim == 1, "data1 must be a 1D (single-channel) NumPy array."
assert data2.ndim == 1, "data1 must be a 1D (single-channel) NumPy array."
assert isinstance(win_sec, (int, float)), "win_sec must be int or float."
assert isinstance(fmin, (int, float)), "fmin must be int or float."
assert isinstance(fmax, (int, float)), "fmax must be int or float."
assert fmin < fmax, "fmin must be strictly inferior to fmax."
assert fmax < sf / 2, "fmax must be less than Nyquist (sf / 2)."
# Calculate multi-taper spectrogram
nperseg = int(win_sec * sf)
assert data1.size > 2 * nperseg, "Data length must be at least 2 * win_sec."
assert data2.size > 2 * nperseg, "Data length must be at least 2 * win_sec."
f, t, Sxx = spectrogram_lspopt(data1, sf, nperseg=nperseg, noverlap=0)
Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz
f2, t2, Sxx2 = spectrogram_lspopt(data2, sf, nperseg=nperseg, noverlap=0)
Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs = np.logical_and(f >= fmin, f <= fmax)
Sxx = Sxx[good_freqs, :]
f = f[good_freqs]
t /= 3600 # Convert t to hours
good_freqs2 = np.logical_and(f2>= fmin, f2 <= fmax)
Sxx2 = Sxx2[good_freqs2, :]
f2 = f2[good_freqs2]
t2 /= 3600 # Convert t to hours
# Normalization
if vmin is None:
vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc])
norm = Normalize(vmin=vmin, vmax=vmax)
else:
norm = Normalize(vmin=vmin, vmax=vmax)
fig,AX = plt.subplots(nrows=4, figsize=(12, 6), gridspec_kw={'height_ratios': [3,3,1,1]})
print('initiating the plot')
# Increase font size while preserving original
#plt.legend(loc = 'right', prop={'size': 6})
old_fontsize = plt.rcParams["font.size"]
plt.rcParams.update({"font.size": 9})
ax0 = plt.subplot(4,1,1)
ax1 = plt.subplot(4,1,2)
ax2 = plt.subplot(4,1,3)
ax3 = plt.subplot(4,1,4)
print('Subplots assigned')
ax0.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax1.pcolormesh(t2, f2, Sxx2, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
print('plots completed')
stages = self.y_pred
#stages = np.row_stack((stages, stages[-1]))
x = np.arange(len(stages))
self.stage_autoscoring = stages
# Change the order of classes: REM and wake on top
x = []
y = []
for i in np.arange(len(stages)):
s = stages[i]
if s== 0 : p = -0
if s== 4 : p = -1
if s== 1 : p = -2
if s== 2 : p = -3
if s== 3 : p = -4
if i!=0:
y.append(p)
x.append(i-1)
y.append(p)
x.append(i)
ax2.step(x, y, where='post', color = 'black', linewidth = 2)
rem = [i for i,j in enumerate(self.y_pred) if (self.y_pred[i]==4)]
for i in np.arange(len(rem)) -1:
ax2.plot([rem[i]-1, rem[i]], [-1,-1] , linewidth = 2, color = 'red')
#ax_autoscoring.scatter(rem, -np.ones(len(rem)), color = 'red')
ax2.set_yticks([0,-1,-2,-3,-4], ['Wake','REM', 'N1', 'N2', 'SWS'],\
fontsize = 8)
ax2.set_xlim([np.min(x), np.max(x)])
ax3.set_xlim([np.min(x), np.max(x)])
ax0.set_title(folder_autoscoring + 'EEG L')
ax1.set_title(folder_autoscoring + 'EEG R')
self.y_pred_proba.plot(ax = ax3, kind="area", alpha=0.8, stacked=True, lw=0, color = ['black', 'olive', 'deepskyblue', 'purple', 'red'])
#ax3.legend().remove()
#plt.tight_layout()
# Save results?
if int(self.checkbox_save_bulk_autoscoring_txt_results_val.get()) == 1:
save_path_autoscoring = folder_autoscoring + 'DreamentoScorer.txt'
if os.path.exists(save_path_autoscoring):
os.remove(save_path_autoscoring)
saving_dir = save_path_autoscoring
a_file = open(saving_dir, "w")
a_file.write('=================== Dreamento: an open-source dream engineering toolbox! ===================\nhttps://github.com/dreamento/dreamento')
a_file.write('\nThis file has been autoscored by DreamentoScorer! \nSleep stages: Wake:0, N1:1, N2:2, SWS:3, REM:4.\n')
a_file.write('============================================================================================\n')
for row in self.stage_autoscoring[:,np.newaxis]:
np.savetxt(a_file, row, fmt='%1.0f')
a_file.close()
# Save sleep metrics
save_path_stats = folder_autoscoring + 'DreamentoScorer_sleep_stats.json'
if os.path.exists(save_path_stats):
os.remove(save_path_stats)
with open(save_path_stats, 'w') as convert_file:
convert_file.write(json.dumps(self.stats))
self.all_stats[folder_autoscoring] = self.stats
#save_figure
if int(self.checkbox_save_bulk_autoscoring_plot_val.get()) == 1:
save_path_plots = folder_autoscoring + 'Dreamento_TFR_autoscoring.png'
if os.path.exists(save_path_plots):
os.remove(save_path_plots)
plt.savefig(save_path_plots,dpi = 300)
if int(self.checkbox_close_plots_val.get()) == 1:
plt.close()
# Store all stats in a single excel file
df = pd.DataFrame(data=self.all_stats)
df = (df.T)
path_to_save_all_stats = os.path.dirname(self.bulk_autoscoring_files_list[0]) + '\\Dreamento_all_sleep_stats.xlsx'
print(f'stroing all stats in: {path_to_save_all_stats}')
# Remove if there is already a file with the same name ...
if os.path.exists(path_to_save_all_stats):
os.remove(path_to_save_all_stats)
df.to_excel(path_to_save_all_stats)
#%% AssignMarkersToRecordedData EEG + TFR
def AnalyzeZMaxHypnodyne(self, data, data_R, sf,\
win_sec=30, fmin=0.3, fmax=40,
trimperc=5, cmap='RdBu_r', add_colorbar = False):
"""
The main function: create the main display window of Offline Dreamento
:param self: access the attributes and methods of the class
:param data: EEG L data
:param data_R: EEG R data
:param sf: sampling frequency
:param win_sec: the window size to compute spectrogram
:param fmin: relavant min frequency for spectrogram
:param fmax: relavant max frequency for spectrogram
:param trimperc: bidirectional trim factor for normalizing the spectrogram colormap
:param cmap: colormap of the spectrogram
:param add_colorbar: adding colorbar to spectrogram
:returns: fig, markers_details
"""
# =============================================================================
# Source code for plotting spectrogram: YASA (https://raphaelvallat.com/yasa/build/html/_modules/yasa/plotting.html#plot_spectrogram)
# [modified]
# =============================================================================
# Increase font size while preserving original
old_fontsize = plt.rcParams['font.size']
plt.rcParams.update({'font.size': 12})
#plt.rcParams["figure.autolayout"] = True
# Safety checks
assert isinstance(data, np.ndarray), 'Data must be a 1D NumPy array.'
assert isinstance(sf, (int, float)), 'sf must be int or float.'
assert data.ndim == 1, 'Data must be a 1D (single-channel) NumPy array.'
assert isinstance(win_sec, (int, float)), 'win_sec must be int or float.'
assert isinstance(fmin, (int, float)), 'fmin must be int or float.'
assert isinstance(fmax, (int, float)), 'fmax must be int or float.'
assert fmin < fmax, 'fmin must be strictly inferior to fmax.'
assert fmax < sf / 2, 'fmax must be less than Nyquist (sf / 2).'
# Calculate multi-taper spectrogram
nperseg = int(10 * sf) #int(win_sec * sf / 8)
assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.'
f, t, Sxx = spectrogram_lspopt(data, sf, nperseg=nperseg, noverlap=0)
Sxx = 10 * np.log10(Sxx) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs = np.logical_and(f >= fmin, f <= fmax)
Sxx = Sxx[good_freqs, :]
f = f[good_freqs]
#t *= 256 # Convert t to hours
# Normalization
vmin, vmax = np.percentile(Sxx, [0 + trimperc, 100 - trimperc])
norm = Normalize(vmin=vmin, vmax=vmax)
# =============================================================================
# #!!!!!!!!!! SHORT SPECTROGRAM!! Calculate multi-taper spectrogram
# nperseg2 = int(.2 * sf) #int(win_sec * sf / 8)
# assert data.size > 2 * nperseg2, 'Data length must be at least 2 * win_sec.'
# f2, t2, Sxx2 = spectrogram_lspopt(data, sf, nperseg=nperseg2, noverlap=0)
# Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# print(f'f: {np.shape(f)}, f2: {np.shape(f2)}, t: {np.shape(t)}, t2: {np.shape(t2)}, Sxx: {np.shape(Sxx)}, Sxx2: {np.shape(Sxx2)}')
# # Select only relevant frequencies (up to 30 Hz)
# good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
# Sxx2 = Sxx2[good_freqs2, :]
# f2 = f2[good_freqs2]
# #t *= 256 # Convert t to hours
#
# # Normalization
# vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
# norm2 = Normalize(vmin=vmin2, vmax=vmax2)
# =============================================================================
#!!!!!!!!!! SHORT SPECTROGRAM!! Calculate multi-taper spectrogram
assert data.size > 2 * nperseg, 'Data length must be at least 2 * win_sec.'
f2, t2, Sxx2 = spectrogram_lspopt(data_R, sf, nperseg = nperseg, noverlap = 0)
Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
Sxx2 = Sxx2[good_freqs2, :]
f2 = f2[good_freqs2]
#t *= 256 # Convert t to hours
# Normalization
vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
norm2 = Normalize(vmin=vmin2, vmax=vmax2)
# =============================================================================
# gs1 = gridspec.GridSpec(2, 1)
# gs1.update(wspace=0.005, hspace=0.0001)
# =============================================================================
if int(self.is_autoscoring.get()) == 0:
# plotting results
fig,AX = plt.subplots(nrows=6, figsize=(16, 9), gridspec_kw={'height_ratios': [2,2,1,1,1,2]})
ax3 = plt.subplot(6,1,1, )
ax_acc = plt.subplot(6,1,5,)
ax_ppg = plt.subplot(6,1,3, sharex = ax_acc)
ax_noise = plt.subplot(6,1,4, sharex = ax_acc)
ax_TFR_short = plt.subplot(6,1,2, sharex = ax3)
ax4 = plt.subplot(6,1,6, sharex = ax_acc)
ax4.grid(True)
ax3.get_xaxis().set_visible(False)
ax_TFR_short.get_xaxis().set_visible(False)
ax_acc.get_xaxis().set_visible(True)
ax_acc.set_yticks([])
ax_noise.set_yticks([])
ax_noise.get_xaxis().set_visible(False)
ax_acc.set_ylim([-1.4, 1.4])
ax3.set_title('Dreamento: post-processing ')
im = ax3.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax3.set_xlim([0, len(data)/256])
ax3.set_ylim((fmin, 25))
ax3.set_ylabel('EEG L-Freq (Hz)')
im2 = ax_TFR_short.pcolormesh(t2, f2, Sxx2, norm=norm2, cmap=cmap, antialiased=True,
shading="auto")
# Add colorbar
if add_colorbar == True:
cbar = fig.colorbar(im, ax=ax3, shrink=0.95, fraction=0.1, aspect=25, pad=0.01)
cbar.ax3.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=5)
# =============================================================================
# ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
# =============================================================================
#axes[1].set_ylim([-200, 200])
#ax4.set_xlim([0, len(data)])
ax4.set_ylabel('EEG (uV)')
ax4.set_ylim([-150, 150])
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=9)
# Opening JSON file]
ax_acc.set_ylabel('Acc')#, labelpad=30, fontsize=8)
ax_noise.set_ylabel('Sound', rotation = 90)#, labelpad=30, fontsize=8)
ax_acc.spines[["top", "bottom"]].set_visible(False)
ax_noise.spines[["top", "bottom"]].set_visible(False)
time_axis = np.round(np.arange(0, len(data)) / 256 , 2)
ax4.set_xlabel('time (s)')
#ax4.set_xticks(np.arange(len(data)), time_axis)
ax4.set_xlim([0, 30])#len(data)])
# plot acc
ax_acc.plot(np.arange(len(self.acc_x))/256, self.acc_x, linewidth = 2 , color = 'blue')
ax_acc.plot(np.arange(len(self.acc_y))/256, self.acc_y, linewidth = 2, color = 'red')
ax_acc.plot(np.arange(len(self.acc_z))/256, self.acc_z, linewidth = 2, color = 'green')
# Plot ppg
ax_ppg.plot(np.arange(len(self.ppg_data))/256, self.ppg_data, linewidth = 2 , color = 'olive')
ax_ppg.set_ylim([-100, 100])
ax_ppg.set_ylabel('PPG')
ax_ppg.set_yticks([])
# plot noise
ax_noise.plot(np.arange(len(self.noise_data))/256, self.noise_data, linewidth = 2, color = 'navy')
fig.canvas.mpl_connect('key_press_event', self.pan_nav_ZMaxHypnodyneOnly)
fig.canvas.mpl_connect('button_press_event', self.onclick_ZMaxHypnodyneOnly)
#ax1.set_xlim([0, 7680])#len(data)])
#ax2.set_xlim([0, 7680])#len(data)])
#ax3.set_xlim([0, len(data)])
#ax4.set_xlim([0, 7680])#len(data)])
plt.subplots_adjust(hspace = 0)
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = data * 1e6
roc = data_R * 1e6
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=None, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data * 1e6, sf=256, ch_names=None, hypno=None, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
spindle_events_R = yasa.spindles_detect(data_R * 1e6, sf=256, ch_names=None, hypno=None, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=False, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data * 1e6 * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_R * 1e6 * mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data * 1e6, sf=256, ch_names=None, hypno=None, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_R * 1e6, sf=256, ch_names=None, hypno=None, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data * 1e6 * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_R * 1e6 * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_L_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_R_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_L_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_R_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax4.plot(np.arange(len(data))/256, data * 1e6, color = 'slategrey', linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R * 1e6, color = 'black', linewidth = 1)
else:
ax4.plot(np.arange(len(data))/256, data * 1e6, color = (160/255, 70/255, 160/255), linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R * 1e6, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, SO_R_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, spindles_L_highlight, 'green')
ax4.plot(np.arange(len(data_R))/256, spindles_R_highlight, 'green')
self.str_first_subplot = str(ax3)
#### If autoscoring is activated ....
elif int(self.is_autoscoring.get()) == 1:
print('Plot results with autoscoring')
fig,AX = plt.subplots(nrows=8, figsize=(16, 9), gridspec_kw={'height_ratios': [2,2,1,1,1,1,1,2]})
ax3 = plt.subplot(8,1,1, )
ax_autoscoring = plt.subplot(8,1,3, )
ax_proba = plt.subplot(8,1,4, )
ax_acc = plt.subplot(8,1,5,)
ax_ppg = plt.subplot(8,1,6, sharex = ax_acc)
ax_noise = plt.subplot(8,1,7, sharex = ax_acc)
ax_TFR_short = plt.subplot(8,1,2, sharex = ax3)
ax4 = plt.subplot(8,1,8, sharex = ax_acc)
ax4.grid(True)
ax3.get_xaxis().set_visible(False)
ax_acc.get_xaxis().set_visible(True)
ax_acc.set_yticks([])
ax_noise.set_yticks([])
ax_noise.get_xaxis().set_visible(False)
ax_acc.set_ylim([-1.4, 1.4])
ax3.set_title('Dreamento: post-processing ')
im = ax3.pcolormesh(t, f, Sxx, norm=norm, cmap=cmap, antialiased=True,
shading="auto")
ax3.set_xlim([0, len(data)/256])
ax3.set_ylim((fmin, 25))
ax3.set_ylabel('EEG L-Freq(Hz)', rotation = 80, fontsize=9)
im2 = ax_TFR_short.pcolormesh(t2, f2, Sxx2, norm=norm2, cmap=cmap, antialiased=True,
shading="auto")
# Add colorbar
if add_colorbar == True:
cbar = fig.colorbar(im, ax=ax3, shrink=0.95, fraction=0.1, aspect=25, pad=0.01)
cbar.ax3.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=5)
#axes[1].set_ylim([-200, 200])
#ax4.set_xlim([0, len(data)])
ax4.set_ylabel('EEG (uV)')
ax4.set_ylim([-150, 150])
ax_TFR_short.set_ylabel('EEG R-Freq(Hz)', rotation = 80, fontsize=10)#, labelpad=30, fontsize=8)
# Opening JSON file]
ax_acc.set_ylabel('Acc', rotation = 90, fontsize=10)#, labelpad=30, fontsize=8)
ax_noise.set_ylabel('Sound', rotation = 90, fontsize=10)#, fontsize=8)
ax_acc.spines[["top", "bottom"]].set_visible(False)
ax_noise.spines[["top", "bottom"]].set_visible(False)
time_axis = np.round(np.arange(0, len(data)) / 256 , 2)
ax4.set_xlabel('time (s)')
#ax4.set_xticks(np.arange(len(data)), time_axis)
ax4.set_xlim([0, 30])#len(data)])
self.samples_before_begin = 0
self.autoscoring()
stages = self.y_pred
#stages = np.row_stack((stages, stages[-1]))
x = np.arange(len(stages))
self.epoch_autoscoring = x
self.stage_autoscoring = stages
print(f'autoscoring shape {np.shape(self.stage_autoscoring)}')
self.up_sampled_predicted_hypno = yasa.hypno_upsample_to_data(hypno=self.stage_autoscoring, sf_hypno = 1/30, data=data, sf_data=256, verbose=True)
print(f'autoscoring shape after up-sampling: {np.shape(self.up_sampled_predicted_hypno)}')
# Change the order of classes: REM and wake on top
x = []
y = []
for i in np.arange(len(stages)):
s = stages[i]
if s== 0 : p = -0
if s== 4 : p = -1
if s== 1 : p = -2
if s== 2 : p = -3
if s== 3 : p = -4
if i!=0:
y.append(p)
x.append(i-1)
y.append(p)
x.append(i)
ax_autoscoring.step(x, y, where='post', color = 'black')
#ax_autoscoring.scatter(rem, -np.ones(len(rem)), color = 'red')
ax_autoscoring.set_yticks([0,-1,-2,-3,-4], ['Wake','REM', 'N1', 'N2', 'SWS'], fontsize=8)
ax_autoscoring.set_xlim([np.min(x), np.max(x)])
ax_proba.set_xlim([np.min(x), np.max(x)])
self.y_pred_proba.plot(ax = ax_proba, kind="area", alpha=0.8, stacked=True, lw=0, color = ['black', 'olive', 'deepskyblue', 'purple', 'red'])
ax_proba.set_ylabel('Hypnodensity', rotation = 0, fontsize=9, labelpad=40)
ax_proba.legend(loc = 'right', prop={'size': 6})
ax_proba.set_yticks([])
ax_proba.set_xticks([])
rem = [i for i,j in enumerate(self.y_pred) if (self.y_pred[i]==4)]
for i in np.arange(len(rem)) -1:
ax_autoscoring.plot([rem[i]-1, rem[i]], [-1,-1] , linewidth = 2, color = 'red')
# plot acc
ax_acc.plot(np.arange(len(self.acc_x))/256, self.acc_x, linewidth = 2 , color = 'blue')
ax_acc.plot(np.arange(len(self.acc_y))/256, self.acc_y, linewidth = 2, color = 'red')
ax_acc.plot(np.arange(len(self.acc_z))/256, self.acc_z, linewidth = 2, color = 'green')
# Plot ppg
ax_ppg.plot(np.arange(len(self.ppg_data))/256, self.ppg_data, linewidth = 2 , color = 'olive')
ax_ppg.set_ylim([-100, 100])
ax_ppg.set_ylabel('PPG', rotation = 90, fontsize=10)
ax_ppg.set_yticks([])
# plot noise
ax_noise.plot(np.arange(len(self.noise_data))/256, self.noise_data, linewidth = 2, color = 'navy')
ax4.get_xaxis().set_visible(True)
plt.subplots_adjust(hspace = 0)
# Apply automatic REM EVENTS detection
if int(self.automatic_REM_event_deetction.get()) == 1:
print(f'Initiating automatic REM event detection ...')
loc = data * 1e6
roc = data_R * 1e6
amplitude = (self.min_amp_REM_detection , self.max_amp_REM_detection)
duration = (self.min_dur_REM_detection , self.max_dur_REM_detection)
freq_rem = (self.min_freq_REM_detection , self.max_freq_REM_detection)
remove_outliers = int(self.checkbox_remove_outliers_val.get())
print(f'{amplitude}, {type(amplitude)}, {duration}, {type(duration)}, {freq_rem}, {type(freq_rem)}, {remove_outliers}, {type(remove_outliers)},')
self.REM_events = yasa.rem_detect(loc = loc, roc = roc, sf=256, hypno=self.up_sampled_predicted_hypno, include=4, amplitude = amplitude, duration = duration, freq_rem = freq_rem, remove_outliers = remove_outliers, verbose=False)
print('REM events have been successfully identified!')
mask = self.REM_events.get_mask()
loc_highlight = loc * mask[0, :]
roc_highlight = roc * mask[1, :]
loc_highlight[loc_highlight == 0] = np.nan
roc_highlight[roc_highlight == 0] = np.nan
self.REM_events.plot_average()
if int(self.checkbox_save_REM_detection_results_val.get()) == 1:
path_save_REM_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'REM_events_summary.csv'
print(f'saivng REM detection results in {path_save_REM_results}')
self.REM_events.summary().to_csv(path_save_REM_results, index=True)
# Apply automatic spindle EVENTS detection
if int(self.automatic_spd_event_deetction.get()) == 1:
print(f'Initiating automatic SO & spindles event detection ...')
freq_sp = (self.min_freq_spd_detection, self.max_freq_spd_detection)
freq_broad = (self.min_freq_broad_threshold, self.max_freq_broad_threshold)
duration = (self.min_dur_spd_detection, self.max_dur_spd_detection)
min_distance = self.min_distance_spd_detection
remove_outliers_SO_spd = int(self.checkbox_spd_remove_outliers_val.get())
spindle_events_L = yasa.spindles_detect(data * 1e6, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=remove_outliers_SO_spd, verbose=False)
spindle_events_R = yasa.spindles_detect(data_R * 1e6, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(1, 2, 3),\
freq_sp = freq_sp, freq_broad = freq_broad, duration = duration,\
min_distance=min_distance, thresh={'corr': 0.65, 'rel_pow': 0.2, 'rms': 1.5},\
multi_only=False, remove_outliers=remove_outliers_SO_spd, verbose=False)
mask = spindle_events_L.get_mask()
spindles_L_highlight = data * 1e6 * mask
spindles_L_highlight[spindles_L_highlight == 0] = np.nan
mask = spindle_events_R.get_mask()
spindles_R_highlight = data_R * 1e6 * mask
spindles_R_highlight[spindles_R_highlight == 0] = np.nan
spindle_events_L.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
spindle_events_R.plot_average(center='Peak', time_before=1, time_after=1, legend = 'full')
# SO
freq_SO = (self.min_freq_SO_detection, self.max_freq_SO_detection)
duration_SO_neg = (self.min_dur_SO_negative_detection, self.max_dur_SO_negative_detection)
duration_SO_pos = (self.min_dur_SO_positive_detection, self.max_dur_SO_positive_detection)
amp_SO_neg = (self.min_amp_SO_negative_detection, self.max_amp_SO_negative_detection)
amp_SO_pos = (self.min_amp_SO_positive_detection, self.max_amp_SO_positive_detection)
amp_SO_p2p = (self.min_amp_SO_p2p_detection, self.max_amp_SO_p2p_detection)
# SO detection
SO_events_L = yasa.sw_detect(data * 1e6, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_R = yasa.sw_detect(data_R * 1e6, sf=256, ch_names=None, hypno=self.up_sampled_predicted_hypno, include=(2, 3), freq_sw=freq_SO, \
dur_neg=duration_SO_neg, dur_pos=duration_SO_pos, amp_neg=amp_SO_neg, amp_pos=amp_SO_pos, amp_ptp=amp_SO_p2p, \
coupling=True, coupling_params={'freq_sp': (10, 16), 'p': 0.05, 'time': 1}, \
remove_outliers=remove_outliers_SO_spd, verbose=False)
SO_events_L.plot_average()
mask = SO_events_L.get_mask()
SO_L_highlight = data * 1e6 * mask
SO_L_highlight[SO_L_highlight == 0] = np.nan
SO_events_R.plot_average()
mask = SO_events_R.get_mask()
SO_R_highlight = data_R * 1e6 * mask
SO_R_highlight[SO_R_highlight == 0] = np.nan
if int(self.checkbox_save_spd_detection_results_val.get()) == 1:
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_L_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_L.summary().to_csv(path_save_spd_results, index=True)
path_save_spd_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'spd_EEG_R_events_summary.csv'
print(f'saivng spd detection results in {path_save_spd_results}')
spindle_events_R.summary().to_csv(path_save_spd_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_L_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
path_save_SO_results = self.HDRecorderRecording.split('EEG L.edf')[0] + 'SO_EEG_R_events_summary.csv'
print(f'saivng SO detection results in {path_save_SO_results}')
SO_events_L.summary().to_csv(path_save_SO_results, index=True)
# PLOT EEG
if int(self.automatic_REM_event_deetction.get()) == 1 or int(self.automatic_spd_event_deetction.get()) == 1:
ax4.plot(np.arange(len(data))/256, data * 1e6, color = 'slategrey', linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R * 1e6, color = 'black', linewidth = 1)
else:
ax4.plot(np.arange(len(data))/256, data * 1e6, color = (160/255, 70/255, 160/255), linewidth = 1)
ax4.plot(np.arange(len(data_R))/256, data_R * 1e6, color = (0/255, 128/255, 190/255), linewidth = 1)
if int(self.automatic_REM_event_deetction.get()) == 1 :
ax4.plot(np.arange(len(data_R))/256, loc_highlight, 'indianred')
ax4.plot(np.arange(len(data_R))/256, roc_highlight, 'indianred')
if int(self.automatic_spd_event_deetction.get()) == 1:# TEMP
ax4.plot(np.arange(len(data_R))/256, SO_L_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, SO_R_highlight, 'blue')
ax4.plot(np.arange(len(data_R))/256, spindles_L_highlight, 'green')
ax4.plot(np.arange(len(data_R))/256, spindles_R_highlight, 'green')
self.str_first_subplot = str(ax3)
fig.canvas.mpl_connect('key_press_event', self.pan_nav_ZMaxHypnodyneOnly)
fig.canvas.mpl_connect('button_press_event', self.onclick_ZMaxHypnodyneOnly)
messagebox.showinfo(title = "AASM sleep metrics", message = self.stats)
return fig
#%% Retrieve sleep statistics (from yasa)
def retrieve_sleep_statistics(self, hypno, sf_hyp = 1 / 30, sleep_stages = [0, 1, 2, 3, 5],\
show_sleep_stats = True):
"""Compute standard sleep statistics from an hypnogram.
.. versionadded:: 0.1.9
Parameters
source: https://github.com/raphaelvallat/yasa/blob/master/yasa/sleepstats.py
----------
hypno : array_like
Hypnogram, assumed to be already cropped to time in bed (TIB,
also referred to as Total Recording Time,
i.e. "lights out" to "lights on").
.. note::
The default hypnogram format in YASA is a 1D integer
vector where:
- -2 = Unscored
- -1 = Artefact / Movement
- 0 = Wake
- 1 = N1 sleep
- 2 = N2 sleep
- 3 = N3 sleep
- 4 = REM sleep
sf_hyp : float
The sampling frequency of the hypnogram. Should be 1/30 if there is one
value per 30-seconds, 1/20 if there is one value per 20-seconds,
1 if there is one value per second, and so on.
Returns
-------
stats : dict
Sleep statistics (expressed in minutes)
Notes
-----
All values except SE, SME and percentages of each stage are expressed in
minutes. YASA follows the AASM guidelines to calculate these parameters:
* Time in Bed (TIB): total duration of the hypnogram.
* Sleep Period Time (SPT): duration from first to last period of sleep.
* Wake After Sleep Onset (WASO): duration of wake periods within SPT.
* Total Sleep Time (TST): SPT - WASO.
* Sleep Efficiency (SE): TST / TIB * 100 (%).
* Sleep Maintenance Efficiency (SME): TST / SPT * 100 (%).
* W, N1, N2, N3 and REM: sleep stages duration. NREM = N1 + N2 + N3.
* % (W, ... REM): sleep stages duration expressed in percentages of TST.
* Latencies: latencies of sleep stages from the beginning of the record.
* Sleep Onset Latency (SOL): Latency to first epoch of any sleep.
References
----------
* Iber, C. (2007). The AASM manual for the scoring of sleep and
associated events: rules, terminology and technical specifications.
American Academy of Sleep Medicine.
* Silber, M. H., Ancoli-Israel, S., Bonnet, M. H., Chokroverty, S.,
Grigg-Damberger, M. M., Hirshkowitz, M., Kapen, S., Keenan, S. A.,
Kryger, M. H., Penzel, T., Pressman, M. R., & Iber, C. (2007).
`The visual scoring of sleep in adults
<https://www.ncbi.nlm.nih.gov/pubmed/17557422>`_. Journal of Clinical
Sleep Medicine: JCSM: Official Publication of the American Academy of
Sleep Medicine, 3(2), 121–131.
"""
stats = {}
hypno = np.asarray(hypno)
assert hypno.ndim == 1, 'hypno must have only one dimension.'
assert hypno.size > 1, 'hypno must have at least two elements.'
# TIB, first and last sleep
stats['TIB'] = len(hypno)
first_sleep = np.where(hypno > sleep_stages[0])[0][0]
last_sleep = np.where(hypno > sleep_stages[0])[0][-1]
# Crop to SPT
hypno_s = hypno[first_sleep:(last_sleep + 1)]
stats['SPT'] = hypno_s.size
stats['WASO'] = hypno_s[hypno_s == sleep_stages[0]].size
stats['TST'] = stats['SPT'] - stats['WASO']
# Duration of each sleep stages
stats['N1'] = hypno[hypno == sleep_stages[1]].size
stats['N2'] = hypno[hypno == sleep_stages[2]].size
stats['N3'] = hypno[hypno == sleep_stages[3]].size
stats['REM'] = hypno[hypno == sleep_stages[4]].size
stats['NREM'] = stats['N1'] + stats['N2'] + stats['N3']
# Sleep stage latencies
stats['SOL'] = first_sleep
stats['Lat_N1'] = np.where(hypno == sleep_stages[1])[0].min() if sleep_stages[1] in hypno else np.nan
stats['Lat_N2'] = np.where(hypno == sleep_stages[2])[0].min() if sleep_stages[2] in hypno else np.nan
stats['Lat_N3'] = np.where(hypno == sleep_stages[3])[0].min() if sleep_stages[3] in hypno else np.nan
stats['Lat_REM'] = np.where(hypno == sleep_stages[4])[0].min() if sleep_stages[4] in hypno else np.nan
# Convert to minutes
for key, value in stats.items():
stats[key] = value / (60 * sf_hyp)
# Percentage
stats['%N1'] = "{:.2f}".format(100 * stats['N1'] / stats['TST'])
stats['%N2'] = "{:.2f}".format(100 * stats['N2'] / stats['TST'])
stats['%N3'] = "{:.2f}".format(100 * stats['N3'] / stats['TST'])
stats['%REM'] = "{:.2f}".format(100 * stats['REM'] / stats['TST'])
stats['%NREM'] = "{:.2f}".format(100 * stats['NREM'] / stats['TST'])
stats['SE'] = "{:.2f}".format(100 * stats['TST'] / stats['TIB'])
stats['SME'] = "{:.2f}".format(100 * stats['TST'] / stats['SPT'])
self.stats = stats
#%% Assess EMG quality
def assess_EMG_data_quality(self,
win_sec = 10,
fmin = 10,
fmax = 100,
sf = 256,
noverlap = 0,
trimperc=5,
cmap='RdBu_r',
log_power = False,
Plot_EMG_Separately = False):
from lspopt import spectrogram_lspopt
from matplotlib.colors import Normalize, ListedColormap
"""
source: https://github.com/raphaelvallat/yasa/blob/master/notebooks/10_spectrogram.ipynb
Adjusted by Mahdad for comparative purposes
"""
sig1 = self.EMG_filtered_data1
sig2 = self.EMG_filtered_data2
sig3 = self.EMG_filtered_data1_minus_2
print('EMG signals for quality assessment were loaded')
# parameters
win_sec = win_sec
fmin = fmin
fmax = fmax
sf = sf
noverlap = noverlap
trimperc=trimperc
cmap=cmap
# Increase font size while preserving original
old_fontsize = plt.rcParams['font.size']
plt.rcParams.update({'font.size': 12})
# Safety checks
assert isinstance(sig1, np.ndarray), 'Data1 must be a 1D NumPy array.'
assert isinstance(sig2, np.ndarray), 'Data2 must be a 1D NumPy array.'
assert isinstance(sig3, np.ndarray), 'Data1 must be a 1D NumPy array.'
assert isinstance(sf, (int, float)), 'sf must be int or float.'
assert sig1.ndim == 1, 'Data1 must be a 1D (single-channel) NumPy array.'
assert sig2.ndim == 1, 'Data2 must be a 1D (single-channel) NumPy array.'
assert sig3.ndim == 1, 'Data1 must be a 1D (single-channel) NumPy array.'
assert isinstance(win_sec, (int, float)), 'win_sec must be int or float.'
assert isinstance(fmin, (int, float)), 'fmin must be int or float.'
assert isinstance(fmax, (int, float)), 'fmax must be int or float.'
assert fmin < fmax, 'fmin must be strictly inferior to fmax.'
assert fmax < sf / 2, 'fmax must be less than Nyquist (sf / 2).'
print('Sanity checks completed!')
# Calculate multi-taper spectrogram
nperseg = int(win_sec * sf)
assert sig1.size > 2 * nperseg, 'Data1 length must be at least 2 * win_sec.'
assert sig2.size > 2 * nperseg, 'Data2 length must be at least 2 * win_sec.'
assert sig3.size > 2 * nperseg, 'Data1 length must be at least 2 * win_sec.'
f1, t1, Sxx1 = spectrogram_lspopt(sig1, sf, nperseg=nperseg, noverlap=noverlap)
f2, t2, Sxx2 = spectrogram_lspopt(sig2, sf, nperseg=nperseg, noverlap=noverlap)
f3, t3, Sxx3 = spectrogram_lspopt(sig3, sf, nperseg=nperseg, noverlap=noverlap)
Sxx1 = 10 * np.log10(Sxx1) # Convert uV^2 / Hz --> dB / Hz
Sxx2 = 10 * np.log10(Sxx2) # Convert uV^2 / Hz --> dB / Hz
Sxx3 = 10 * np.log10(Sxx3) # Convert uV^2 / Hz --> dB / Hz
# Select only relevant frequencies (up to 30 Hz)
good_freqs1 = np.logical_and(f1 >= fmin, f1 <= fmax)
good_freqs2 = np.logical_and(f2 >= fmin, f2 <= fmax)
good_freqs3 = np.logical_and(f3 >= fmin, f3 <= fmax)
Sxx1 = Sxx1[good_freqs1, :]
Sxx2 = Sxx2[good_freqs2, :]
Sxx3 = Sxx3[good_freqs3, :]
f1 = f1[good_freqs1]
f2 = f2[good_freqs2]
f3 = f3[good_freqs3]
t1 /= 3600 # Convert t to hours
t2 /= 3600 # Convert t to hours
t3 /= 3600 # Convert t to hours
# Normalization
vmin1, vmax1 = np.percentile(Sxx1, [0 + trimperc, 100 - trimperc])
vmin2, vmax2 = np.percentile(Sxx2, [0 + trimperc, 100 - trimperc])
vmin3, vmax3 = np.percentile(Sxx3, [0 + trimperc, 100 - trimperc])
norm1 = Normalize(vmin=vmin1, vmax=vmax1)
norm2 = Normalize(vmin=vmin2, vmax=vmax2)
norm3 = Normalize(vmin=vmin3, vmax=vmax3)
# Plot signal 1
#fig, ax = plt.subplots(3, 2)
# Hold the values
self.t1_EMG = t1
self.f1_EMG = f1
self.Sxx1_EMG = Sxx1
self.norm1_EMG = norm1
self.t2_EMG = t2
self.f2_EMG = f2
self.Sxx2_EMG = Sxx2
self.norm2_EMG = norm1
self.t3_EMG = t3
self.f3_EMG = f3
self.Sxx3_EMG = Sxx3
self.norm3_EMG = norm1
if Plot_EMG_Separately == True:
fig, ax = plt.subplots(nrows=3, ncols=2, figsize=(10, 5), gridspec_kw={'width_ratios':[2,1], 'height_ratios':[1,1,1]})
im = ax[0,0].pcolormesh(t1, f1, Sxx1, norm=norm1, cmap=cmap, antialiased=True,
shading="auto")
ax[0,0].set_xlim(0, t1.max())
ax[0,0].set_ylabel('Frequency [Hz]')
ax[0,0].set_xlabel('Time [hrs]')
ax[0,0].set_title('EMG 1')
# Add colorbar
# =============================================================================
# cbar = fig.colorbar(im, ax=ax[0], shrink=0.95, fraction=0.1, aspect=25)
# cbar.ax.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=20)
# =============================================================================
# Plot signal 2
im = ax[1,0].pcolormesh(t2, f2, Sxx2, norm=norm1, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax[1,0].set_xlim(0, t2.max())
ax[1,0].set_ylabel('Frequency [Hz]')
ax[1,0].set_title('EMG 2')
# =============================================================================
# cbar = fig.colorbar(im, ax=ax[1], shrink=0.95, fraction=0.1, aspect=25)
# cbar.ax.set_ylabel('Log Power (dB / Hz)', rotation=270, labelpad=20)
# =============================================================================
# Plot signal 3
im = ax[2,0].pcolormesh(t3, f3, Sxx3, norm=norm1, cmap=cmap, antialiased=True,
shading="auto") # Normalized with respect to the same freq range as sig1
ax[2,0].set_xlim(0, t3.max())
ax[2,0].set_ylabel('Frequency [Hz]')
ax[2,0].set_title('EMG 1 - EMG 2')
# Periodogram
win_size = 5
win = win_size * sf
freqs1, psd1 = signal.welch(x=sig1, fs=sf, nperseg=win)
freqs2, psd2 = signal.welch(x=sig2, fs=sf, nperseg=win)
freqs3, psd3 = signal.welch(x=sig3, fs=sf, nperseg=win)
log_power = log_power
if log_power:
psd1 = 20 * np.log10(psd1)
psd2 = 20 * np.log10(psd2)
psd3 = 20 * np.log10(psd3)
# Compute vvalues:
ret1 = yasa.bandpower(data=sig1, sf=sf)
ret2 = yasa.bandpower(data=sig2, sf=sf)
ret3 = yasa.bandpower(data=sig3, sf=sf)
ax[0,1].plot(freqs1, psd1, color='k', lw=2)
ax[0,1].set_title('EMG 1')
ax[1,1].plot(freqs2, psd2, color='k', lw=2)
ax[1,1].set_title('EMG 2')
ax[2,1].plot(freqs3, psd3, color='k', lw=2)
ax[2,1].set_title('EMG 1 - EMG 2')
plt.subplots_adjust(hspace = 0.2)
#%% Function: Help pop-up
def help_pop_up_func(self):
"""
Help button of the software. Introduction to the applications and hot keeys
:param self: access the attributes and methods of the class
"""
line_msg = "Welcome to Dreamento!\n" +\
"You can Use offline Dreamento in different cases (1) to merely analyze ZMax Hypnodyne recording" +\
" (2) to integrate ZMAX Hypnodyne and Dremento recordings, " +\
" and (3) to integrate ZMax Hypnodyne, Dremento, and EMG recordings \n \n" +\
"From the analysis options, check and uncheck the most relavant properties\n" +\
"Then load the relavant files ...\n \n" +\
"- Hotkeys to navigate in the final plot.\n" +\
"- Keyboard down arrow: zoom out.\n" +\
"- Keyboard up arrow: zoom in.\n" +\
"- Keyboard right arrow: next epoch (30s).\n" +\
"- Keyboard left arrow: previous epoch (30s).\n\n" +\
"- To navigate through data, click on the desired part of the time-freq representation." +\
"- In case you want a specific part of the EEG (in the current epoch) comes to the middle, click on it." +\
"\n\n In case of full ZMax Hypnodyne, Dreamento, and EMG analysis:\n" +\
"Markers and light stimulations of the whole data are shown on first two rows.\n" +\
"However, current-epoch markers are shown in the middle.\n\n" +\
"IMPORTANT: To keep the location navigator accurate, please keep the cursur on " +\
"the spectrogrma plot while pressing left/right buttons to navigate.\n\n" +\
"Matlab compatability: After the analysis you can export an .mat file\n\n\n" +\
"Contact: Mahdad.Jafarzadehesfahani@donders.ru.nl \n" +\
"CopyRight (2021-2024): Mahdad Jafarzadeh Esfahani**"
messagebox.showinfo(title = "Help", message = line_msg)
#%% manual scoring pop-up
def manual_scoring_popup_button_func(self):
"""
Help button of the manual scoring using Dreamento.
:param self: access the attributes and methods of the class
"""
line_msg = "this function is only active in case all 4 required files are loaded\n" +\
"Manual scoring buttons:\n"+\
"on each epoch of data, you can press a button to score sleep (same as all other scoring software) and then it jumps to the next epoch.\n\n" +\
'- Press "w" or "0" to score wake.\n'+\
'- Press "1" to score N1.\n'+\
'- Press "2" to score N2.\n'+\
'- Press "3" to score N3/SWS.\n'+\
'- Press "4" or "5" or "r" to score REM.\n\n'+\
'- Press "a" to mark an arousal.\n'+\
'- Press "m" to mark a major body movement.\n'+\
'- Press "9" to mark a predefined eye signaling,e.g., LRLR,\n\n'+\
'- if an epoch is unscoarable, simply hit the ```right arrow``` key on the keyboard to leave that epoch as unscored/unscorable and go to the next. \n'+\
'- If you changed your mind about scoring one of the past epochs, simply use ```left arrow``` to go back and then change the scoring to the desired (i.e. press relevant key for the sleep stage). If you want to remove the scoring, i.e., mark the epoch as "unscorable", you should push "d" button.\n\n'+\
'- If pressing the letters on keyboard e.g, "a, m, d, e" does not perform the required task, make sure the caps lock is off. \n\n '+\
'- **export scoring** by pressing ```e``` on the keyboard, then you will be presented with a message to select the folder and filename.\n'
messagebox.showinfo(title = "Manual scroing instructions", message = line_msg)
#%% Compute bandpower per epoch
def compute_bandpower_navigating_epochs(self):
# Bandpower computer
global bandpower_epoch
bandpower_epoch = yasa.bandpower(self.data[int(self.lims[0]*256): int(self.lims[1]*256)], \
sf=256, win_sec=4, relative=True)
print(f'computing bandower between {self.lims}: {bandpower_epoch}')
bandpower_delta = str(round(bandpower_epoch['Delta'][0], 2))
bandpower_theta = str(round(bandpower_epoch['Theta'][0], 2))
bandpower_alpha = str(round(bandpower_epoch['Alpha'][0], 2))
bandpower_sgima = str(round(bandpower_epoch['Sigma'][0], 2))
bandpower_beta = str(round(bandpower_epoch['Beta'][0] , 2))
self.ax_tmp.text(self.lims[0]+1, 90, 'Delta: ' + bandpower_delta +\
', Theta: ' + bandpower_theta +\
', Alpha: ' + bandpower_alpha +\
', Sigma: ' + bandpower_sgima +\
', Beta: ' + bandpower_beta, color = 'dimgray')
#%% Navigating via keyboard in the figure
def pan_nav(self, event):
"""
The keyboard controller of the software
:param self: accessing the attributes and methods of the class
:param up arrow keyboard button: increase the EEG amplitude scale
:param down arrow keyboard button: lower the EEG amplitude scale
:param left arrow keyboard button: navigate to the previous epoch
:param right arrow keyboard button: navigate to the next epoch
"""
self.ax_tmp = plt.gca()
self.lims = self.ax_tmp.get_xlim()
if event.key == 'left':
self.lims = self.ax_tmp.get_xlim()
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] - adjust, self.lims[1] - adjust))
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] - adjust, self.lims[1] - adjust))), int(np.mean((self.lims[0] - adjust, self.lims[1] - adjust)))], [-150, 150], color = 'black')
#self.curr_ax.set_ylim((0.1,25))
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
plt.draw()
elif event.key == 'right':
self.lims = self.ax_tmp.get_xlim()
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] + adjust, self.lims[1] + adjust))
print(event.xdata)
print(self.lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust))), int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust)))], [-150, 150], color = 'black')
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
#self.curr_ax.set_ylim((0.1,25))
elif event.key == 'up':
self.lims = self.ax_tmp.get_ylim()
adjust_up = self.lims[1] - self.lims[1]/5
adjust_down = self.lims[0] +self.lims[1]/5
self.ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
elif event.key == 'down':
self.lims = self.ax_tmp.get_ylim()
adjust_up = self.lims[1] + self.lims[1]/5
adjust_down = self.lims[0] - self.lims[1]/5
self.ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
#Enable manual scoring system
elif event.key == 'w' or event.key == '0':
self.lims = self.ax_tmp.get_xlim()
print(f'Epoch {int(np.round(self.lims[1])/30)} marked as Wake')
self.ax_tmp.text(self.lims[1] - 15, 50, 'Wake', color = 'black')
manual_scoring[int(np.round(self.lims[1])/30)-1,0] = 0
# show next epoch
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] + adjust, self.lims[1] + adjust))
print(event.xdata)
print(self.lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust))), int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust)))], [-150, 150], color = 'black')
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
elif event.key == '1':
self.lims = self.ax_tmp.get_xlim()
print(f'Epoch {int(np.round(self.lims[1])/30)} marked as N1')
self.ax_tmp.text(self.lims[1] - 15, 50, 'N1', color = 'cyan')
manual_scoring[int(np.round(self.lims[1])/30)-1,0] = 1
# show next epoch
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] + adjust, self.lims[1] + adjust))
print(event.xdata)
print(self.lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust))), int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust)))], [-150, 150], color = 'black')
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
elif event.key == '2':
self.lims = self.ax_tmp.get_xlim()
print(f'Epoch {int(np.round(self.lims[1])/30)} marked as N2')
self.ax_tmp.text(self.lims[1] - 15, 50, 'N2', color = 'purple')
manual_scoring[int(np.round(self.lims[1])/30)-1,0] = 2
# show next epoch
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] + adjust, self.lims[1] + adjust))
print(event.xdata)
print(self.lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust))), int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust)))], [-150, 150], color = 'black')
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
elif event.key == '3':
self.lims = self.ax_tmp.get_xlim()
print(f'Epoch {int(np.round(self.lims[1])/30)} marked as N3/SWS')
self.ax_tmp.text(self.lims[1] - 15, 50, 'SWS', color = 'blue')
manual_scoring[int(np.round(self.lims[1])/30)-1,0] = 3
# show next epoch
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] + adjust, self.lims[1] + adjust))
print(event.xdata)
print(self.lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust))), int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust)))], [-150, 150], color = 'black')
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
elif event.key == '4' or event.key == '5' or event.key == 'r':
self.lims = self.ax_tmp.get_xlim()
print(f'Epoch {int(np.round(self.lims[1])/30)} marked as REM')
self.ax_tmp.text(self.lims[1] - 15, 50, 'REM', color = 'red')
manual_scoring[int(np.round(self.lims[1])/30)-1,0] = 4
# show next epoch
adjust = (self.lims[1] - self.lims[0])
self.ax_tmp.set_xlim((self.lims[0] + adjust, self.lims[1] + adjust))
print(event.xdata)
print(self.lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
self.curr_ax = event.inaxes
#if str(self.curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(self.curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(self.curr_ax.lines) > 0 :
self.curr_ax.lines[-1].remove()
self.curr_ax.plot([int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust))), int(np.mean((self.lims[0] + adjust, self.lims[1] + adjust)))], [-150, 150], color = 'black')
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
elif event.key == 'd':
self.lims = self.ax_tmp.get_xlim()
print(f'Epoch {int(np.round(self.lims[1])/30)} marked as unscored (scoring removed)')
for text in self.ax_tmp.texts:
if text.get_position() == (self.lims[1] - 15, 50):
text.remove()
self.ax_tmp.text(self.lims[1] - 15, 50, 'Unscored', color = 'gray')
manual_scoring[int(np.round(self.lims[1])/30)-1,0] = -1
plt.draw()
elif event.key == '9':
self.lims = self.ax_tmp.get_xlim()
self.ax_tmp.text(self.lims[1] - 15, 70, 'LRLR', color = 'darkred')
manual_scoring[int(np.round(self.lims[1])/30)-1, 1] = 5757
plt.draw()
elif event.key == 'a':
self.lims = self.ax_tmp.get_xlim()
self.ax_tmp.text(self.lims[1] - 15, 90, 'Mov. arousal', color = 'navy')
manual_scoring[int(np.round(self.lims[1])/30)-1, 1] = 1
plt.draw()
elif event.key == 'm':
self.lims = self.ax_tmp.get_xlim()
self.ax_tmp.text(self.lims[1] - 15, 90, 'MBM', color = 'darkslateblue')
manual_scoring[int(np.round(self.lims[1])/30)-1, 1] = 2
plt.draw()
elif event.key == 'e':
self.ExportManualScoring()
#%% def
#%% Define event while clicking
def onclick(self, event):
"""
Clicking on the TFR to go to the desired epoch.
:param self: access the attributes and methods of the class
:param event: mouse click
"""
self.ax_tmp = plt.gca()
if event.button == 1:
print('mouse cliked --> move plot')
self.ax_tmp.set_xlim((np.floor(event.xdata)- int(7680/256/2), np.floor(event.xdata)+ int(7680/256/2)))
plt.draw()
print(f'clicked sample{ {event.xdata}}')
print(f'adjust xlm {(np.floor(event.xdata)- int(7680/2), np.floor(event.xdata)+ int(7680/2))}')
print(f'{event.inaxes}')
curr_ax = event.inaxes
#if str(curr_ax) == 'AxesSubplot(0.125,0.730968;0.775x0.124194)':
if str(curr_ax) == self.str_first_subplot:
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([event.xdata, event.xdata], [0.3, 40], color = 'black')
curr_ax.set_ylim((0.1,25))
self.lims = self.ax_tmp.get_xlim()
self.compute_bandpower_navigating_epochs()
#%% Navigating via keyboard in the figure
def pan_nav_EMG_autoscoring (self, event):
"""
The keyboard controller of the software
:param self: accessing the attributes and methods of the class
:param up arrow keyboard button: increase the EEG amplitude scale
:param down arrow keyboard button: lower the EEG amplitude scale
:param left arrow keyboard button: navigate to the previous epoch
:param right arrow keyboard button: navigate to the next epoch
"""
ax_tmp = plt.gca()
if event.key == 'left':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
ax_tmp.set_xlim((lims[0] - adjust, lims[1] - adjust))
curr_ax = event.inaxes
#if str(curr_ax) == 'AxesSubplot(0.125,0.728525;0.775x0.12623)':
if str(curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([int(np.mean((lims[0] - adjust, lims[1] - adjust))), int(np.mean((lims[0] - adjust, lims[1] - adjust)))], [-150, 150], color = 'black')
#curr_ax.set_ylim((0.1,25))
plt.draw()
elif event.key == 'right':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
ax_tmp.set_xlim((lims[0] + adjust, lims[1] + adjust))
print(event.xdata)
print(lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
curr_ax = event.inaxes
#if str(curr_ax) == 'AxesSubplot(0.125,0.728525;0.775x0.12623)':
if str(curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([int(np.mean((lims[0] + adjust, lims[1] + adjust))), int(np.mean((lims[0] + adjust, lims[1] + adjust)))], [-150, 150], color = 'black')
#curr_ax.set_ylim((0.1,25))
elif event.key == 'up':
lims = ax_tmp.get_ylim()
adjust_up = lims[1] - lims[1]/5
adjust_down = lims[0] +lims[1]/5
ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
elif event.key == 'down':
lims = ax_tmp.get_ylim()
adjust_up = lims[1] + lims[1]/5
adjust_down = lims[0] - lims[1]/5
ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
#%% Define event while clicking
def onclick_EMG_autoscoring(self, event):
"""
Clicking on the TFR to go to the desired epoch.
:param self: access the attributes and methods of the class
:param event: mouse click
"""
ax_tmp = plt.gca()
if event.button == 1:
print('mouse cliked --> move plot')
ax_tmp.set_xlim((np.floor(event.xdata)- int(7680/256/2), np.floor(event.xdata)+ int(7680/256/2)))
plt.draw()
print(f'clicked sample{ {event.xdata}}')
print(f'adjust xlm {(np.floor(event.xdata)- int(7680/2), np.floor(event.xdata)+ int(7680/2))}')
print(f'{event.inaxes}')
curr_ax = event.inaxes
#if str(curr_ax) == 'AxesSubplot(0.125,0.728525;0.775x0.12623)':
if str(curr_ax) == self.str_first_subplot:
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([event.xdata, event.xdata], [0.3, 40], color = 'black')
curr_ax.set_ylim((0.1,25))
#%% Navigating via keyboard in the figure
def pan_nav_noEMG(self, event):
"""
The keyboard controller of the software
:param self: accessing the attributes and methods of the class
:param up arrow keyboard button: increase the EEG amplitude scale
:param down arrow keyboard button: lower the EEG amplitude scale
:param left arrow keyboard button: navigate to the previous epoch
:param right arrow keyboard button: navigate to the next epoch
"""
ax_tmp = plt.gca()
if event.key == 'left':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
ax_tmp.set_xlim((lims[0] - adjust, lims[1] - adjust))
curr_ax = event.inaxes
#if (str(curr_ax) == 'AxesSubplot(0.125,0.674667;0.775x0.171111)' or str(curr_ax) =='AxesSubplot(0.125,0.654634;0.775x0.187805)'):
if str(curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([int(np.mean((lims[0] - adjust, lims[1] - adjust))), int(np.mean((lims[0] - adjust, lims[1] - adjust)))], [-150, 150], color = 'black')
plt.draw()
elif event.key == 'right':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
ax_tmp.set_xlim((lims[0] + adjust, lims[1] + adjust))
print(event.xdata)
print(lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
curr_ax = event.inaxes
#if (str(curr_ax) == 'AxesSubplot(0.125,0.674667;0.775x0.171111)' or str(curr_ax) =='AxesSubplot(0.125,0.654634;0.775x0.187805)'):
if str(curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([int(np.mean((lims[0] + adjust, lims[1] + adjust))), int(np.mean((lims[0] + adjust, lims[1] + adjust)))], [-150, 150], color = 'black')
#curr_ax.set_ylim((0.1,25))
elif event.key == 'up':
lims = ax_tmp.get_ylim()
adjust_up = lims[1] - lims[1]/5
adjust_down = lims[0] +lims[1]/5
ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
elif event.key == 'down':
lims = ax_tmp.get_ylim()
adjust_up = lims[1] + lims[1]/5
adjust_down = lims[0] - lims[1]/5
ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
#%% Navigating via keyboard in the figure
def pan_nav_ZMaxHypnodyneOnly(self, event):
"""
The keyboard controller of the software
:param self: accessing the attributes and methods of the class
:param up arrow keyboard button: increase the EEG amplitude scale
:param down arrow keyboard button: lower the EEG amplitude scale
:param left arrow keyboard button: navigate to the previous epoch
:param right arrow keyboard button: navigate to the next epoch
"""
ax_tmp = plt.gca()
if event.key == 'left':
print('going back in data')
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
ax_tmp.set_xlim((lims[0] - adjust, lims[1] - adjust))
curr_ax = event.inaxes
#if (str(curr_ax) == 'AxesSubplot(0.125,0.74;0.775x0.14)' or str(curr_ax) == 'AxesSubplot(0.125,0.708889;0.775x0.171111)'):
if str(curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([int(np.mean((lims[0] - adjust, lims[1] - adjust))), int(np.mean((lims[0] - adjust, lims[1] - adjust)))], [-150, 150], color = 'black')
plt.draw()
elif event.key == 'right':
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
ax_tmp.set_xlim((lims[0] + adjust, lims[1] + adjust))
print(event.xdata)
print(lims)
#ax3.axvline(x=event.xdata, color="k")
plt.draw()
print(f'The xdata is : {event.xdata}')
print(f'The ydata is : {event.ydata}')
print(f'The x is : {event.x}')
print(f'The y is : {event.y}')
print(f'favailable axes: {event.inaxes}')
curr_ax = event.inaxes
#if (str(curr_ax) == 'AxesSubplot(0.125,0.74;0.775x0.14)' or str(curr_ax) == 'AxesSubplot(0.125,0.708889;0.775x0.171111)'):
if str(curr_ax) == self.str_first_subplot:
print('spectrogram axis detected')
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([int(np.mean((lims[0] + adjust, lims[1] + adjust))), int(np.mean((lims[0] + adjust, lims[1] + adjust)))], [-150, 150], color = 'black')
elif event.key == 'up':
lims = ax_tmp.get_ylim()
adjust_up = lims[1] - lims[1]/5
adjust_down = lims[0] +lims[1]/5
ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
elif event.key == 'down':
lims = ax_tmp.get_ylim()
adjust_up = lims[1] + lims[1]/5
adjust_down = lims[0] - lims[1]/5
ax_tmp.set_ylim((adjust_down, adjust_up))
plt.draw()
#%% Define event while clicking
def onclick_noEMG(self, event):
"""
Clicking on the TFR to go to the desired epoch.
:param self: access the attributes and methods of the class
:param event: mouse click
"""
ax_tmp = plt.gca()
if event.button == 1:
print('mouse cliked --> move plot')
ax_tmp.set_xlim((np.floor(event.xdata)- int(7680/256/2), np.floor(event.xdata)+ int(7680/256/2)))
plt.draw()
print(f'clicked sample{ {event.xdata}}')
print(f'adjust xlm {(np.floor(event.xdata)- int(7680/2), np.floor(event.xdata)+ int(7680/2))}')
print(f'{event.inaxes}')
curr_ax = event.inaxes
#if (str(curr_ax) == 'AxesSubplot(0.125,0.674667;0.775x0.171111)' or str(curr_ax) =='AxesSubplot(0.125,0.654634;0.775x0.187805)'):
if str(curr_ax) == self.str_first_subplot:
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([event.xdata, event.xdata], [0.3, 40], color = 'black')
curr_ax.set_ylim((0.1,25))
#%% onclick_BrainProdcuts
def onclick_BrainProdcuts(self, event):
"""
Clicking on the TFR to go to the desired epoch.
:param self: access the attributes and methods of the class
:param event: mouse click
"""
ax_tmp = plt.gca()
if event.button == 1:
print('mouse cliked --> move plot')
ax_tmp.set_xlim((np.floor(event.xdata)- 15, np.floor(event.xdata)+ 15))
plt.draw()
print(f'clicked sample{ {event.xdata}}')
print(f'adjust xlm {(np.floor(event.xdata)- int(7680/2), np.floor(event.xdata)+ int(7680/2))}')
print(f'{event.inaxes}')
curr_ax = event.inaxes
if (str(curr_ax) == 'AxesSubplot(0.125,0.764355;0.775x0.115645)'):
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([event.xdata, event.xdata], [0.3, 40], color = 'black')
curr_ax.set_ylim((0.1,25))
#%% key pressed event BrainAmp Prodcuts
def pan_nav_BrainProducts(self, event):
"""
The keyboard controller of the software
:param self: accessing the attributes and methods of the class
:param up arrow keyboard button: increase the EEG amplitude scale
:param down arrow keyboard button: lower the EEG amplitude scale
:param left arrow keyboard button: navigate to the previous epoch
:param right arrow keyboard button: navigate to the next epoch
"""
fig = plt.gcf() # Get the current figure
axs = fig.get_axes()[2:] # Get a list of all axes in the figure
if event.key in ['left', 'right']:
ax_tmp = plt.gca()
lims = ax_tmp.get_xlim()
adjust = (lims[1] - lims[0])
if event.key == 'left':
ax_tmp.set_xlim((lims[0] - adjust, lims[1] - adjust))
elif event.key == 'right':
ax_tmp.set_xlim((lims[0] + adjust, lims[1] + adjust))
curr_ax = event.inaxes
if (str(curr_ax) == 'AxesSubplot(0.125,0.764355;0.775x0.115645)'):
if len(curr_ax.lines) > 0:
curr_ax.lines[-1].remove()
if event.key == 'left':
curr_ax.plot([int(np.mean((lims[0] - adjust, lims[1] - adjust))), int(np.mean((lims[0] - adjust, lims[1] - adjust)))], [-150, 150], color='black')
elif event.key == 'right':
curr_ax.plot([int(np.mean((lims[0] + adjust, lims[1] + adjust))), int(np.mean((lims[0] + adjust, lims[1] + adjust)))], [-150, 150], color='black')
plt.draw()
elif event.key == 'up':
for ax in axs:
lims = ax.get_ylim()
adjust_up = lims[1] - lims[1] / 5
adjust_down = lims[0] + lims[1] / 5
ax.set_ylim((adjust_down, adjust_up))
# =============================================================================
# print(f'for axis {ax}, ylim is {adjust_down} - {adjust_up} ')
# =============================================================================
plt.draw()
elif event.key == 'down':
for ax in axs:
lims = ax.get_ylim()
adjust_up = lims[1] + lims[1] / 5
adjust_down = lims[0] - lims[1] / 5
ax.set_ylim((adjust_down, adjust_up))
# =============================================================================
# print(f'for axis {ax}, ylim is {adjust_down} - {adjust_up} ')
# =============================================================================
plt.draw()
#%% pop-up markers selection
def select_marker_for_sync(self):
self.popupWin = Toplevel(root)
self.alert = Label(self.popupWin, text='Please select an event to sync EMG and EEG (recommendation: teeth clench')
self.markers_sync_event = StringVar()
self.markers_sync_event_option_menu = OptionMenu(self.popupWin, self.markers_sync_event, *self.all_markers_with_timestamps)
self.markers_sync_event_option_menu.pack()
self.button_popupwin = Button(self.popupWin, text = "OK", command=self.select_marker)
self.alert.pack()
self.button_popupwin.pack()
root.wait_window(self.popupWin)
#%% pop-up markers selection
def bulk_autoscoring_popup(self):
self.popupWin_bulk_autoscoring = Toplevel(root)
# button: load a txt file including paths of folders to be autoscored
self.load_txt_bulk_autoscoring_paths_label = Label(self.popupWin_bulk_autoscoring, text='select a txt file including paths to folders')
self.load_txt_bulk_autoscoring_paths_label.grid(row = 1 , column =1)
self.button_load_bulk_autoscoring = Button(self.popupWin_bulk_autoscoring, text = "Browse ...",
font = 'Calibri 11 bold', relief = RIDGE,
command = self.browse_txt_for_bulk_autoscoring)
self.button_load_bulk_autoscoring.grid(row = 2 , column =1)
# button: Path to export autoscoring
# =============================================================================
# self.select_folder_export_bulk_autoscoring_bulk_autoscoring_label = Label(self.popupWin_bulk_autoscoring, text='select destination folder')
# self.select_folder_export_bulk_autoscoring_bulk_autoscoring_label.grid(row = 1 , column =2)
# self.button_select_folder_export_bulk_autoscoring = Button(self.popupWin_bulk_autoscoring, text = "Browse ...",
# font = 'Calibri 11 bold', relief = RIDGE,
# command = self.browse_destination_folder_for_bulk_autoscoring)
# self.button_select_folder_export_bulk_autoscoring.grid(row = 2 , column =2)
# =============================================================================
# Button: start bulk autoscoring
self.button_start_bulk_autoscoring_label = Label(self.popupWin_bulk_autoscoring, text='start autoscoring!')
self.button_start_bulk_autoscoring_label.grid(row = 1 , column =3)
self.button_start_bulk_autoscoring = Button(self.popupWin_bulk_autoscoring, text = "Start!",
font = 'Calibri 11 bold', relief = RIDGE,
command = self.bulk_autoscoring)
self.button_start_bulk_autoscoring.grid(row = 2 , column =3)
self.checkbox_save_bulk_autoscoring_txt_results_val = IntVar(value = 0)
self.checkbox_bulk_autoscoring_txt_results = Checkbutton(self.popupWin_bulk_autoscoring, text = "Save autoscoring results?",
font = 'Calibri 11 ', variable = self.checkbox_save_bulk_autoscoring_txt_results_val)
self.checkbox_bulk_autoscoring_txt_results.grid(row = 1, column = 3)
self.checkbox_save_bulk_autoscoring_plot_val = IntVar(value = 1)
self.checkbox_bulk_autoscoring_plot = Checkbutton(self.popupWin_bulk_autoscoring, text = "Save TFR + autoscoring plots?",
font = 'Calibri 11 ', variable = self.checkbox_save_bulk_autoscoring_plot_val)
self.checkbox_bulk_autoscoring_plot.grid(row = 1, column = 4)
self.checkbox_close_plots_val = IntVar(value = 1)
self.checkbox_close_plot = Checkbutton(self.popupWin_bulk_autoscoring, text = "Close plots after save?",
font = 'Calibri 11 ', variable = self.checkbox_close_plots_val)
self.checkbox_close_plot.grid(row = 1, column = 5)
self.auotscoring_model_Optionmenu_Label = Label(self.popupWin_bulk_autoscoring, text='Autoscoring model')
self.auotscoring_model_Optionmenu_Label.grid(row = 2, column = 0)
self.autoscoring_model_options = ['Select model','Lightgbm_td=3_Bidirectional', 'Usleep for ZMax']
self.autoscoring_model_options_val = StringVar()
self.auotscoring_model_Optionmenu = Optionmenu(self.popupWin_bulk_autoscoring, self.autoscoring_model_options_val, *self.autoscoring_model_options)
# =============================================================================
# self.alert = Label(self.popupWin, text='Please select an event to sync EMG and EEG (recommendation: teeth clench')
#
# self.markers_sync_event = StringVar()
# self.markers_sync_event_option_menu = OptionMenu(self.popupWin, self.markers_sync_event, *self.all_markers_with_timestamps)
# self.markers_sync_event_option_menu.pack()
#
# self.button_popupwin = Button(self.popupWin, text = "OK", command=self.select_marker)
# self.alert.pack()
# self.button_popupwin.pack()
# =============================================================================
root.wait_window(self.popupWin_bulk_autoscoring)
#%% Automatic REM event detection popup
def automatic_REM_event_detection_popup(self):
self.popupWin_automatic_REM_event_detection = Toplevel(root)
self.REM_amplitude_threshold_label = Label(self.popupWin_automatic_REM_event_detection, text='Amp thresh REM detection: (uV)')
self.REM_amplitude_threshold_label.grid(row = 1 , column =1)
self.REM_duration_threshold_label = Label(self.popupWin_automatic_REM_event_detection, text='duration threshold REM detection: (s)')
self.REM_duration_threshold_label.grid(row = 1 , column =2)
self.REM_freq_threshold_label = Label(self.popupWin_automatic_REM_event_detection, text='freq. threshold REM detection: (Hz)')
self.REM_freq_threshold_label.grid(row = 1 , column = 3)
self.checkbox_remove_outliers_val = IntVar(value = 1)
self.checkbox_remove_outliers = Checkbutton(self.popupWin_automatic_REM_event_detection, text = "Remove outliers? (recommended)",
font = 'Calibri 11 ', variable = self.checkbox_remove_outliers_val)
self.checkbox_remove_outliers.grid(row = 1, column = 4)
self.checkbox_save_REM_detection_results_val = IntVar(value = 1)
self.checkbox_save_REM_detection_results = Checkbutton(self.popupWin_automatic_REM_event_detection, text = "Save identified events",
font = 'Calibri 11 ', variable = self.checkbox_save_REM_detection_results_val)
self.checkbox_save_REM_detection_results.grid(row = 1, column = 5)
# user entries
self.entry_REM_amplitude_threshold = Entry(self.popupWin_automatic_REM_event_detection)#, borderwidth = 2, width = 10)
self.entry_REM_amplitude_threshold.insert(0, "30, 325")
self.entry_REM_amplitude_threshold.grid(row = 2, column = 1)#)#, padx = 15, pady = 10)
self.entry_REM_duration_threshold = Entry(self.popupWin_automatic_REM_event_detection)#, borderwidth = 2, width = 10)
self.entry_REM_duration_threshold.insert(0, ".3, 1.2")
self.entry_REM_duration_threshold.grid(row = 2, column = 2)#)#, padx = 15, pady = 10)
self.entry_REM_freq_threshold = Entry(self.popupWin_automatic_REM_event_detection)#, borderwidth = 2, width = 10)
self.entry_REM_freq_threshold.insert(0, ".5, 5")
self.entry_REM_freq_threshold.grid(row = 2, column = 3)#)#, padx = 15, pady = 10)
self.OK_button_popupWin_automatic_REM_event_detection = Button(self.popupWin_automatic_REM_event_detection, text = "OK!",
command = self.receive_values_from_automatic_REM_event_detection_popup,
font = 'Calibri 11 ')
self.OK_button_popupWin_automatic_REM_event_detection.grid(row = 3, column = 3)
#%% Automatic spindle event detection popup
def automatic_spd_event_detection_popup(self):
self.popupWin_automatic_spd_event_detection = Toplevel(root)
self.freq_broad_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='Broad frequency of detection: (Hz)')
self.freq_broad_threshold_label.grid(row = 1 , column =1)
self.spd_duration_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='duration threshold spd detection: (s)')
self.spd_duration_threshold_label.grid(row = 1 , column =2)
self.spd_freq_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='freq. threshold spd detection: (Hz)')
self.spd_freq_threshold_label.grid(row = 1 , column = 3)
self.min_distance_spd_detection_threshold = Label(self.popupWin_automatic_spd_event_detection, text='Min. distance:')
self.min_distance_spd_detection_threshold.grid(row = 1 , column = 4)
self.checkbox_spd_remove_outliers_val = IntVar(value = 0)
self.checkbox_spd_remove_outliers = Checkbutton(self.popupWin_automatic_spd_event_detection, text = "remove outliers? (recommended)",
font = 'Calibri 11 ', variable = self.checkbox_spd_remove_outliers_val)
self.checkbox_spd_remove_outliers.grid(row = 1, column = 5)
self.checkbox_save_spd_detection_results_val = IntVar(value = 1)
self.checkbox_save_spd_detection_results = Checkbutton(self.popupWin_automatic_spd_event_detection, text = "Save identified events",
font = 'Calibri 11 ', variable = self.checkbox_save_spd_detection_results_val)
self.checkbox_save_spd_detection_results.grid(row = 1, column = 6)
# user entries
self.entry_freq_broad_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_freq_broad_threshold.insert(0, "1, 30")
self.entry_freq_broad_threshold.grid(row = 2, column = 1)#)#, padx = 15, pady = 10)
self.entry_spd_duration_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_spd_duration_threshold.insert(0, ".5, 2")
self.entry_spd_duration_threshold.grid(row = 2, column = 2)#)#, padx = 15, pady = 10)
self.entry_spd_freq_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_spd_freq_threshold.insert(0, "11, 15")
self.entry_spd_freq_threshold.grid(row = 2, column = 3)#)#, padx = 15, pady = 10)
self.entry_min_distance_spd_detection_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_min_distance_spd_detection_threshold.insert(0, "500")
self.entry_min_distance_spd_detection_threshold.grid(row = 2, column = 4)#)#, padx = 15, pady = 10)
self.OK_button_popupWin_automatic_spd_event_detection = Button(self.popupWin_automatic_spd_event_detection, text = "OK!",
command = self.receive_values_from_automatic_spd_event_detection_popup,
font = 'Calibri 11 ')
# =============================================================================
# self.checkbox_spd_remove_outliers_val = IntVar(value = 0)
# self.checkbox_spd_remove_outliers = Checkbutton(self.popupWin_automatic_spd_event_detection, text = "remove outliers? (recommended)",
# font = 'Calibri 11 ', variable = self.checkbox_spd_remove_outliers_val)
# self.checkbox_spd_remove_outliers.grid(row = 1, column = 5)
# =============================================================================
# SO
self.freq_broad_threshold_SO_label = Label(self.popupWin_automatic_spd_event_detection, text='SO frequency range: (Hz)')
self.freq_broad_threshold_SO_label.grid(row = 3 , column =1)
self.SO_duration_neg_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='SO negative duration: (s)')
self.SO_duration_neg_threshold_label.grid(row = 3 , column =2)
self.SO_duration_pos_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='SO positive duration: (s')
self.SO_duration_pos_threshold_label.grid(row = 3 , column =3)
self.SO_neg_amp_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='SO negative amp range: (uV)')
self.SO_neg_amp_threshold_label.grid(row = 3 , column = 4)
self.SO_pos_amp_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='SO positive amp range: (uV)')
self.SO_pos_amp_threshold_label.grid(row = 3 , column = 5)
self.SO_p2p_amp_threshold_label = Label(self.popupWin_automatic_spd_event_detection, text='SO p2p amp range: (uV)')
self.SO_p2p_amp_threshold_label.grid(row = 3 , column = 6)
# user entries
self.entry_freq_SO_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_freq_SO_threshold.insert(0, ".3, 1.5")
self.entry_freq_SO_threshold.grid(row = 4 , column =1)
self.entry_SO_duration_neg_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_SO_duration_neg_threshold.insert(0, ".1, 1.5")
self.entry_SO_duration_neg_threshold.grid(row = 4 , column =2)
self.entry_SO_duration_pos_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_SO_duration_pos_threshold.insert(0, ".1, 1")
self.entry_SO_duration_pos_threshold.grid(row = 4 , column =3)
self.entry_SO_amp_neg_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_SO_amp_neg_threshold.insert(0, "10, 200")
self.entry_SO_amp_neg_threshold.grid(row = 4 , column =4)
self.entry_SO_amp_pos_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_SO_amp_pos_threshold.insert(0, "10, 200")
self.entry_SO_amp_pos_threshold.grid(row = 4 , column =5)
self.entry_SO_amp_p2p_threshold = Entry(self.popupWin_automatic_spd_event_detection)#, borderwidth = 2, width = 10)
self.entry_SO_amp_p2p_threshold.insert(0, "20, 400")
self.entry_SO_amp_p2p_threshold.grid(row = 4 , column =6)
self.OK_button_popupWin_automatic_spd_event_detection.grid(row = 5, column = 4)
#%% Automatic REM event detection popup
def DreamentoConverter(self):
self.popupWin_DreamentoConverter = Toplevel(root)
self.path_to_Hypnodyne_folder_label = Label(self.popupWin_DreamentoConverter, text='Path to ZMax Hypnodyne Folder:')
self.path_to_Hypnodyne_folder_label.grid(row = 1 , column =1)
self.entry_path_to_Hypnodyne_folder = Entry(self.popupWin_DreamentoConverter)#, borderwidth = 2, width = 10)
self.entry_path_to_Hypnodyne_folder.insert(0, "C:/Program Files (x86)/Hypnodyne/ZMax/")
self.entry_path_to_Hypnodyne_folder.grid(row = 2 , column = 1)
self.path_to_txt_containing_hyps_to_convert_label = Label(self.popupWin_DreamentoConverter, text='a single .txt file including paths to all raw .hyp files')
self.path_to_txt_containing_hyps_to_convert_label.grid(row = 3 , column =1)
self.entry_path_to_txt_containing_hyps_to_convert = Entry(self.popupWin_DreamentoConverter)#, borderwidth = 2, width = 10)
self.entry_path_to_txt_containing_hyps_to_convert.insert(0, "path/to/txt/including/all/hyps.txt")
self.entry_path_to_txt_containing_hyps_to_convert.grid(row = 4 , column = 1)
self.path_to_txt_containing_destination_paths_label = Label(self.popupWin_DreamentoConverter, text='a single .txt file including paths to all raw .hyp files')
self.path_to_txt_containing_destination_paths_label.grid(row = 5 , column =1)
self.entry_path_to_txt_containing_destination_paths = Entry(self.popupWin_DreamentoConverter)#, borderwidth = 2, width = 10)
self.entry_path_to_txt_containing_destination_paths.insert(0, "path/to/txt/including/all/destinations.txt")
self.entry_path_to_txt_containing_destination_paths.grid(row = 6 , column = 1)
self.convert_button = Button(self.popupWin_DreamentoConverter, text = "Convert!",
command = self.apply_DreamentoConverter,
font = 'Calibri 11 ')
self.convert_button.grid(row = 7, column = 1)
#%% ExportManualScoring
def ExportManualScoring(self):
self.popupWin_ExportManualScoring = Toplevel(root)
self.path_to_manual_scoring_folder_label = Label(self.popupWin_ExportManualScoring, text='Path to Folder:')
self.path_to_manual_scoring_folder_label.grid(row = 1 , column =1)
self.path_to_manual_scoring_folder = Entry(self.popupWin_ExportManualScoring)
self.path_to_manual_scoring_folder.insert(0, self.HDRecorderRecording.split('EEG L.edf')[0])
self.path_to_manual_scoring_folder.grid(row = 1 , column =2)
self.path_to_manual_scoring_filename_label = Label(self.popupWin_ExportManualScoring, text='Filename:')
self.path_to_manual_scoring_filename_label.grid(row = 2 , column =1)
self.entry_filename = Entry(self.popupWin_ExportManualScoring)#, borderwidth = 2, width = 10)
self.entry_filename.insert(0, 'Dreamento_ManualScoring_SCORER-NAME.txt')
self.entry_filename.grid(row = 2 , column = 2)
self.convert_button = Button(self.popupWin_ExportManualScoring, text = "Export!",
command = self.ApplyExportManualScoring,
font = 'Calibri 11 ')
self.convert_button.grid(row = 3, column = 1)
#%% Brain amp channel selector for plotting + autoscoring
def BrainProducts_channel_selector(self):
from tkinter import font
self.popupWin_BrainProducts_channel_selector = Toplevel(root)
self.info_channel_selection_BrainProducts_autoscoring = Label(self.popupWin_BrainProducts_channel_selector, text='Select AUTOSCORING channels:')
self.info_channel_selection_BrainProducts_autoscoring.grid(row = 1 , column =1)
# Select autoscoring channels
self.autoscoring_BrainProducts_EEG_main_label = Label(self.popupWin_BrainProducts_channel_selector, text='EEG (main):')
self.autoscoring_BrainProducts_EEG_main_label.grid(row = 2 , column =2)
self.autoscoring_BrainProducts_EEG_ref_label = Label(self.popupWin_BrainProducts_channel_selector, text='EEG (ref):')
self.autoscoring_BrainProducts_EEG_ref_label.grid(row = 2 , column =3)
self.autoscoring_BrainProducts_EOG_main_label = Label(self.popupWin_BrainProducts_channel_selector, text='EOG (main):')
self.autoscoring_BrainProducts_EOG_main_label.grid(row = 2 , column =4)
self.autoscoring_BrainProducts_EMG_ref_label = Label(self.popupWin_BrainProducts_channel_selector, text='EOG (ref):')
self.autoscoring_BrainProducts_EMG_ref_label.grid(row = 2 , column =5)
self.autoscoring_BrainProducts_EEG_main_label = Label(self.popupWin_BrainProducts_channel_selector, text='EMG (main):')
self.autoscoring_BrainProducts_EEG_main_label.grid(row = 2 , column =6)
self.autoscoring_BrainProducts_EEG_ref_label = Label(self.popupWin_BrainProducts_channel_selector, text='EMG (ref):')
self.autoscoring_BrainProducts_EEG_ref_label.grid(row = 2 , column =7)
# Select Plotting channels
self.info_channel_selection_BrainProducts_plot = Label(self.popupWin_BrainProducts_channel_selector, text='Select PLOTTING channels:')
self.info_channel_selection_BrainProducts_plot.grid(row = 4 , column =1)
# Select autoscoring channels
self.plotting_BrainProducts_EEG_main_label = Label(self.popupWin_BrainProducts_channel_selector, text='channel 1:')
self.plotting_BrainProducts_EEG_main_label.grid(row = 5 , column =2)
self.plotting_BrainProducts_EEG_ref_label = Label(self.popupWin_BrainProducts_channel_selector, text='ref 1:')
self.plotting_BrainProducts_EEG_ref_label.grid(row = 5 , column =3)
self.plotting_BrainProducts_EOG_main_label = Label(self.popupWin_BrainProducts_channel_selector, text='channel 2:')
self.plotting_BrainProducts_EOG_main_label.grid(row = 5 , column =4)
self.plotting_BrainProducts_EMG_ref_label = Label(self.popupWin_BrainProducts_channel_selector, text='EOG (ref):')
self.plotting_BrainProducts_EMG_ref_label.grid(row = 5 , column =5)
self.plotting_BrainProducts_EEG_main_label = Label(self.popupWin_BrainProducts_channel_selector, text='channel 3:')
self.plotting_BrainProducts_EEG_main_label.grid(row = 5 , column =6)
self.plotting_BrainProducts_EEG_ref_label = Label(self.popupWin_BrainProducts_channel_selector, text='ref 3:')
self.plotting_BrainProducts_EEG_ref_label.grid(row = 5, column =7)
# option menus: AUTOSCORING
self.autoscoring_BrainProducts_EEG_main_val = StringVar()
self.autoscoring_BrainProducts_EEG_main_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.autoscoring_BrainProducts_EEG_main_val, *self.ch_names_BrainProducts)
self.autoscoring_BrainProducts_EEG_main_option_menu.grid(row = 3, column =2)
self.autoscoring_BrainProducts_EEG_ref_val = StringVar()
self.autoscoring_BrainProducts_EEG_ref_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.autoscoring_BrainProducts_EEG_ref_val, *self.ch_names_BrainProducts)
self.autoscoring_BrainProducts_EEG_ref_option_menu.grid(row = 3, column =3)
self.autoscoring_BrainProducts_EOG_main_val = StringVar()
self.autoscoring_BrainProducts_EOG_main_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.autoscoring_BrainProducts_EOG_main_val, *self.ch_names_BrainProducts)
self.autoscoring_BrainProducts_EOG_main_option_menu.grid(row = 3, column =4)
self.autoscoring_BrainProducts_EOG_ref_val = StringVar()
self.autoscoring_BrainProducts_EOG_ref_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.autoscoring_BrainProducts_EOG_ref_val, *self.ch_names_BrainProducts)
self.autoscoring_BrainProducts_EOG_ref_option_menu.grid(row = 3, column =5)
self.autoscoring_BrainProducts_EMG_main_val = StringVar()
self.autoscoring_BrainProducts_EMG_main_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.autoscoring_BrainProducts_EMG_main_val, *self.ch_names_BrainProducts)
self.autoscoring_BrainProducts_EMG_main_option_menu.grid(row = 3, column =6)
self.autoscoring_BrainProducts_EMG_ref_val = StringVar()
self.autoscoring_BrainProducts_EMG_ref_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.autoscoring_BrainProducts_EMG_ref_val, *self.ch_names_BrainProducts)
self.autoscoring_BrainProducts_EMG_ref_option_menu.grid(row = 3, column =7)
# option menus: plotting
self.plotting_BrainProducts_EEG_main_val = StringVar()
self.plotting_BrainProducts_EEG_main_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.plotting_BrainProducts_EEG_main_val, *self.ch_names_BrainProducts)
self.plotting_BrainProducts_EEG_main_option_menu.grid(row = 6, column =2)
self.plotting_BrainProducts_EEG_ref_val = StringVar()
self.plotting_BrainProducts_EEG_ref_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.plotting_BrainProducts_EEG_ref_val, *self.ch_names_BrainProducts)
self.plotting_BrainProducts_EEG_ref_option_menu.grid(row = 6, column =3)
self.plotting_BrainProducts_EOG_main_val = StringVar()
self.plotting_BrainProducts_EOG_main_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.plotting_BrainProducts_EOG_main_val, *self.ch_names_BrainProducts)
self.plotting_BrainProducts_EOG_main_option_menu.grid(row = 6, column =4)
self.plotting_BrainProducts_EOG_ref_val = StringVar()
self.plotting_BrainProducts_EOG_ref_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.plotting_BrainProducts_EOG_ref_val, *self.ch_names_BrainProducts)
self.plotting_BrainProducts_EOG_ref_option_menu.grid(row = 6, column =5)
self.plotting_BrainProducts_EMG_main_val = StringVar()
self.plotting_BrainProducts_EMG_main_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.plotting_BrainProducts_EMG_main_val, *self.ch_names_BrainProducts)
self.plotting_BrainProducts_EMG_main_option_menu.grid(row = 6, column =6)
self.plotting_BrainProducts_EMG_ref_val = StringVar()
self.plotting_BrainProducts_EMG_ref_option_menu = OptionMenu(self.popupWin_BrainProducts_channel_selector, self.plotting_BrainProducts_EMG_ref_val, *self.ch_names_BrainProducts)
self.plotting_BrainProducts_EMG_ref_option_menu.grid(row = 6, column =7)
self.OK_button_popupWin_BrainProdcuts = Button(self.popupWin_BrainProducts_channel_selector, text = "OK!",
command = self.receive_values_from_BrainProducts_channel_selector,
font = 'Calibri 11 ')
self.OK_button_popupWin_BrainProdcuts.grid(row = 7, column = 4)
self.checkbox_save_YASA_autoscoring_val = IntVar(value = 1)
self.checkbox_save_YASA_autoscoring = Checkbutton(self.popupWin_BrainProducts_channel_selector, text = "Save autoscoring results",
font = 'Calibri 11 ', variable = self.checkbox_save_YASA_autoscoring_val)
self.checkbox_save_YASA_autoscoring.grid(row = 3, column = 8)
root.wait_window(self.popupWin_BrainProducts_channel_selector)
#%% Receive values from automatic_REM_event_detection_popup
def receive_values_from_automatic_REM_event_detection_popup(self):
print(f'Dreamento received REM amplitude thresholds of {self.entry_REM_amplitude_threshold.get()} Hz of tpye{type(self.entry_REM_amplitude_threshold.get())}')
print(f'Dreamento received REM duration thresholds of {self.entry_REM_duration_threshold.get()} Hz of tpye{type(self.entry_REM_duration_threshold.get())}')
print(f'Dreamento received REM freq threshold of {self.entry_REM_freq_threshold.get()} Hz of tpye{type(self.entry_REM_freq_threshold.get())}')
self.min_amp_REM_detection = int(self.entry_REM_amplitude_threshold.get().split(',')[0])
self.max_amp_REM_detection = int(self.entry_REM_amplitude_threshold.get().split(',')[1])
self.min_dur_REM_detection = float(self.entry_REM_duration_threshold.get().split(',')[0])
self.max_dur_REM_detection = float(self.entry_REM_duration_threshold.get().split(',')[1])
self.min_freq_REM_detection = float(self.entry_REM_freq_threshold.get().split(',')[0])
self.max_freq_REM_detection = float(self.entry_REM_freq_threshold.get().split(',')[1])
self.popupWin_automatic_REM_event_detection.destroy()
# =============================================================================
# print(type(self.min_amp_REM_detection))
# print(type(self.max_amp_REM_detection))
# print(type(self.min_dur_REM_detection))
# print(type(self.max_dur_REM_detection))
# print(type(self.min_freq_REM_detection))
# print(type(self.max_freq_REM_detection))
# print(type(self.checkbox_remove_outliers_val))
# =============================================================================
#%% receive_values_from_BrainProducts_channel_selector
def receive_values_from_BrainProducts_channel_selector(self):
print(f'Autoscoring EEG main selected as {str(self.autoscoring_BrainProducts_EEG_main_val.get())}')
print(f'Autoscoring EEG ref selected as {str(self.autoscoring_BrainProducts_EEG_ref_val.get())}')
print(f'Autoscoring EOG main selected as {str(self.autoscoring_BrainProducts_EOG_main_val.get())}')
print(f'Autoscoring EOG ref selected as {str(self.autoscoring_BrainProducts_EOG_ref_val.get())}')
print(f'Autoscoring EMG main selected as {str(self.autoscoring_BrainProducts_EMG_main_val.get())}')
print(f'Autoscoring EMG ref selected as {str(self.autoscoring_BrainProducts_EMG_ref_val.get())}')
print(f'plotting ch1 main selected as {str(self.plotting_BrainProducts_EEG_main_val.get())}')
print(f'plotting ch1 ref selected as {str(self.plotting_BrainProducts_EEG_ref_val.get())}')
print(f'plotting ch2 main selected as {str(self.plotting_BrainProducts_EOG_main_val.get())}')
print(f'plotting ch2 ref selected as {str(self.plotting_BrainProducts_EOG_ref_val.get())}')
print(f'plotting ch3 main selected as {str(self.plotting_BrainProducts_EMG_main_val.get())}')
print(f'plotting ch3 ref selected as {str(self.plotting_BrainProducts_EMG_ref_val.get())}')
self.popupWin_BrainProducts_channel_selector.destroy()
#%% Receive values from automatic_spd_event_detection_popup
def receive_values_from_automatic_spd_event_detection_popup(self):
print(f'Dreamento received spd broad freq thresholds of {self.entry_freq_broad_threshold.get()} Hz of tpye{type(self.entry_freq_broad_threshold.get())}')
print(f'Dreamento received spd duration thresholds of {self.entry_spd_duration_threshold.get()} Hz of tpye{type(self.entry_spd_duration_threshold.get())}')
print(f'Dreamento received spd freq threshold of {self.entry_spd_freq_threshold.get()} Hz of tpye{type(self.entry_spd_freq_threshold.get())}')
print(f'Dreamento received spd min distance thresholds of { self.entry_min_distance_spd_detection_threshold.get()} Hz of tpye{type( self.entry_min_distance_spd_detection_threshold.get())}')
print(f'Dreamento received SO detection freq thresholds of {self.entry_freq_SO_threshold.get()} Hz of tpye{type(self.entry_freq_SO_threshold.get())}')
print(f'Dreamento received SO negative duration thresholds of {self.entry_SO_duration_neg_threshold.get()} Hz of tpye{type(self.entry_SO_duration_neg_threshold.get())}')
print(f'Dreamento received SO positive duration thresholds of {self.entry_SO_duration_pos_threshold.get()} Hz of tpye{type(self.entry_SO_duration_pos_threshold.get())}')
print(f'Dreamento received SO negative amp thresholds of {self.entry_SO_amp_neg_threshold.get()} Hz of tpye{type(self.entry_SO_amp_neg_threshold.get())}')
print(f'Dreamento received SO positive amp thresholds of {self.entry_SO_amp_pos_threshold.get()} Hz of tpye{type(self.entry_SO_amp_pos_threshold.get())}')
print(f'Dreamento received SO p2p amp thresholds of {self.entry_SO_amp_p2p_threshold.get()} Hz of tpye{type(self.entry_SO_amp_p2p_threshold.get())}')
self.min_freq_broad_threshold = int(self.entry_freq_broad_threshold.get().split(',')[0])
self.max_freq_broad_threshold = int(self.entry_freq_broad_threshold.get().split(',')[1])
self.min_dur_spd_detection = float(self.entry_spd_duration_threshold.get().split(',')[0])
self.max_dur_spd_detection = float(self.entry_spd_duration_threshold.get().split(',')[1])
self.min_freq_spd_detection = float(self.entry_spd_freq_threshold.get().split(',')[0])
self.max_freq_spd_detection = float(self.entry_spd_freq_threshold.get().split(',')[1])
self.min_distance_spd_detection = float(self.entry_min_distance_spd_detection_threshold.get())
# SO
self.min_freq_SO_detection = float(self.entry_freq_SO_threshold.get().split(',')[0])
self.max_freq_SO_detection = float(self.entry_freq_SO_threshold.get().split(',')[1])
self.min_dur_SO_negative_detection = float(self.entry_SO_duration_neg_threshold.get().split(',')[0])
self.max_dur_SO_negative_detection = float(self.entry_SO_duration_neg_threshold.get().split(',')[1])
self.min_dur_SO_positive_detection = float(self.entry_SO_duration_pos_threshold.get().split(',')[0])
self.max_dur_SO_positive_detection = float(self.entry_SO_duration_pos_threshold.get().split(',')[1])
self.min_amp_SO_negative_detection = float(self.entry_SO_amp_neg_threshold.get().split(',')[0])
self.max_amp_SO_negative_detection = float(self.entry_SO_amp_neg_threshold.get().split(',')[1])
self.min_amp_SO_positive_detection = float(self.entry_SO_amp_pos_threshold.get().split(',')[0])
self.max_amp_SO_positive_detection = float(self.entry_SO_amp_pos_threshold.get().split(',')[1])
self.min_amp_SO_p2p_detection = float(self.entry_SO_amp_p2p_threshold.get().split(',')[0])
self.max_amp_SO_p2p_detection = float(self.entry_SO_amp_p2p_threshold.get().split(',')[1])
self.popupWin_automatic_spd_event_detection.destroy()
print(type(self.min_freq_broad_threshold))
print(type(self.max_freq_broad_threshold))
print(type(self.min_dur_spd_detection))
print(type(self.max_dur_spd_detection))
print(type(self.min_freq_spd_detection))
print(type(self.max_freq_spd_detection))
print(type(self.checkbox_spd_remove_outliers_val))
print(type(self.min_freq_SO_detection))
print(type(self.max_freq_SO_detection))
print(type(self.min_dur_SO_negative_detection))
print(type(self.max_dur_SO_negative_detection))
print(type(self.min_dur_SO_positive_detection))
print(type(self.max_dur_SO_positive_detection))
print(type(self.min_amp_SO_negative_detection))
print(type(self.max_amp_SO_negative_detection))
print(type(self.min_amp_SO_positive_detection))
print(type(self.max_amp_SO_positive_detection))
print(type(self.min_amp_SO_p2p_detection))
print(type(self.max_amp_SO_p2p_detection))
#%% Apply DreamentoConverter
def apply_DreamentoConverter(self):
import shutil
import subprocess
# define path to HDRecorder.exe
path_to_HDRecorder = str(self.entry_path_to_Hypnodyne_folder.get())
os.chdir(path_to_HDRecorder)
print(f'Changing directory to {path_to_HDRecorder}')
# define files to be converted
filenames_path = str(self.entry_path_to_txt_containing_hyps_to_convert.get())
# Remove quotations if exist
filenames = np.loadtxt(filenames_path, dtype = 'str', delimiter = ',')
print(f'raw hyp files received: {filenames}')
# define path to converted folders
destination_folders_path = str(self.entry_path_to_txt_containing_destination_paths.get())
destination_folders = np.loadtxt(destination_folders_path, dtype = 'str', delimiter = ',')
print(f'Locating the destination files: {destination_folders}')
for conv in np.arange(len(filenames)):
# Copy the .hyp file to HDRecoder folder for conversion
src_path = filenames[conv]
print(f'locating {src_path}')
current_file = filenames[conv].split('/')[-1]
dst_path = path_to_HDRecorder + current_file
print(dst_path)
shutil.copy(src_path, dst_path)
# Create a batch file to run conversion syntax
myBat = open(r'DreamentoConverter.bat','w+')
myBat.write('HDRecorder.exe -conv '+ current_file)
myBat.close()
# run the created .bat --> conversion
print(f'Converting the file {conv+1}/{len(filenames)}...please be patient...\n')
subprocess.call(path_to_HDRecorder + 'DreamentoConverter.bat')
print(f'{conv+1}/{len(filenames)} files have been successfully converted\n')
print(f'file {src_path} converted to path {destination_folders[conv]}')
# Copy generated folder to the desired path
shutil.copytree(path_to_HDRecorder + 'SDConvert\\', destination_folders[conv])
# Remove the .hyp and .bat files from HDRecorder folder
os.remove(path_to_HDRecorder + 'DreamentoConverter.bat')
os.remove(dst_path)
print('All files have been successfully converted!')
#%% Apply export of manual scoring
def ApplyExportManualScoring(self):
path_manual_scoring = str(self.path_to_manual_scoring_folder.get()) + str(self.entry_filename.get())
messagebox.showinfo("Scoring saved!",f" The autoscoring results saved in {path_manual_scoring}")
np.savetxt(path_manual_scoring, \
manual_scoring, fmt='%d', delimiter='\t')
#%% select marker for sync command
def select_marker(self):
print(f'syncing based on the following event: {self.markers_sync_event.get()}')
self.popupWin.destroy()
#%% plot hypno from YASA
def plot_hypnogram(self, hypno, axis, sf_hypno=1/30, lw=1.5):
"""
Plot a hypnogram.
.. versionadded:: 0.6.0
Parameters
----------
hypno : array_like
Sleep stage (hypnogram).
.. note::
The default hypnogram format in YASA is a 1D integer vector where:
* -2 = Unscored
* -1 = Artefact / Movement
* 0 = Wake
* 1 = N1 sleep
* 2 = N2 sleep
* 3 = N3 sleep
* 4 = REM sleep
sf_hypno : float
The current sampling frequency of the hypnogram, in Hz, e.g.
* 1/30 = 1 value per each 30 seconds of EEG data,
* 1 = 1 value per second of EEG data
lw : float
Linewidth.
figsize : tuple
Width, height in inches.
Returns
-------
ax : :py:class:`matplotlib.axes.Axes`
Matplotlib Axes
Examples
--------
.. plot::
>>> import yasa
>>> import numpy as np
>>> hypno = np.loadtxt("https://github.com/raphaelvallat/yasa/raw/master/notebooks/data_full_6hrs_100Hz_hypno_30s.txt")
>>> ax = yasa.plot_hypnogram(hypno)
"""
# Increase font size while preserving original
old_fontsize = plt.rcParams['font.size']
plt.rcParams.update({'font.size': 18})
# Safety checks
assert isinstance(hypno, (np.ndarray, pd.Series, list)), 'hypno must be an array.'
hypno = np.asarray(hypno).astype(int)
assert (hypno >= -2).all() and (hypno <= 4).all(), "hypno values must be between -2 to 4."
assert hypno.ndim == 1, 'hypno must be a 1D array.'
assert isinstance(sf_hypno, (int, float)), 'sf must be int or float.'
t_hyp = np.arange(hypno.size) / (sf_hypno * 3600)
# Make sure that REM is displayed after Wake
hypno = pd.Series(hypno).map({-2: -2, -1: -1, 0: 0, 1: 2, 2: 3, 3: 4, 4: 1}).values
hypno_rem = np.ma.masked_not_equal(hypno, 1)
hypno_art_uns = np.ma.masked_greater(hypno, -1)
# =============================================================================
# fig, ax0 = plt.subplots(nrows=1, figsize=figsize)
# =============================================================================
ax0 = axis
# Hypnogram (top axis)
ax0.step(t_hyp, -1 * hypno, color='k', lw=lw)
ax0.step(t_hyp, -1 * hypno_rem, color='red', lw=lw)
ax0.step(t_hyp, -1 * hypno_art_uns, color='grey', lw=lw)
if -2 in hypno and -1 in hypno:
# Both Unscored and Artefacts are present
ax0.set_yticks([2, 1, 0, -1, -2, -3, -4])
ax0.set_yticklabels(['Uns', 'Art', 'W', 'R', 'N1', 'N2', 'N3'])
ax0.set_ylim(-4.5, 2.5)
elif -2 in hypno and -1 not in hypno:
# Only Unscored are present
ax0.set_yticks([2, 0, -1, -2, -3, -4])
ax0.set_yticklabels(['Uns', 'W', 'R', 'N1', 'N2', 'N3'])
ax0.set_ylim(-4.5, 2.5)
elif -2 not in hypno and -1 in hypno:
# Only Artefacts are present
ax0.set_yticks([1, 0, -1, -2, -3, -4])
ax0.set_yticklabels(['Art', 'W', 'R', 'N1', 'N2', 'N3'])
ax0.set_ylim(-4.5, 1.5)
else:
# No artefacts or Unscored
ax0.set_yticks([0, -1, -2, -3, -4])
ax0.set_yticklabels(['W', 'R', 'N1', 'N2', 'N3'])
ax0.set_ylim(-4.5, 0.5)
ax0.set_xlim(0, t_hyp.max())
ax0.set_ylabel('Stage')
ax0.set_xlabel('Time [hrs]')
ax0.spines['right'].set_visible(False)
ax0.spines['top'].set_visible(False)
# Revert font-size
# =============================================================================
# plt.rcParams.update({'font.size': old_fontsize})
# return ax0
# =============================================================================
#%% Click ZMax Hypnodyne only
def onclick_ZMaxHypnodyneOnly(self, event):
"""
Clicking on the TFR to go to the desired epoch.
:param self: access the attributes and methods of the class
:param event: mouse click
"""
ax_tmp = plt.gca()
if event.button == 1:
print('mouse cliked --> move plot')
ax_tmp.set_xlim((np.floor(event.xdata)- int(7680/256/2), np.floor(event.xdata)+ int(7680/256/2)))
plt.draw()
print(f'clicked sample{ {event.xdata}}')
print(f'adjust xlm {(np.floor(event.xdata)- int(7680/2), np.floor(event.xdata)+ int(7680/2))}')
print(f'{event.inaxes}')
curr_ax = event.inaxes
#if (str(curr_ax) == 'AxesSubplot(0.125,0.74;0.775x0.14)' or str(curr_ax) == 'AxesSubplot(0.125,0.708889;0.775x0.171111)'):
if str(curr_ax) == self.str_first_subplot:
if len(curr_ax.lines) > 0 :
curr_ax.lines[-1].remove()
curr_ax.plot([event.xdata, event.xdata], [0.3, 40], color = 'black')
curr_ax.set_ylim((0.1,25))
if __name__ == "__main__":
#%% Test section
root = Tk()
my_gui = OfflineDreamento(root)
#root.iconphoto(False, PhotoImage(file=".\\Donders_Logo.png"))
root.mainloop()