Source code for GUI

import subprocess
import sys
import importlib
import os
import re
import utils as utl

# def is_package_installed(package_name):
#     '''
#     Checks if package installed: tries to import package, if exception thrown return False (signalizes for package to be installed)
    
#     :param package_name: str, package name
#     '''
#     try:
#         importlib.import_module(package_name)
#         return True
#     except ImportError:
#         return False

# def install_requirements_if_needed(requirements_file="requirements.txt"):
#     '''
#     Checks requirements, installs packages missing.
    
#     :param requirements_file: .txt file, 
#     '''
#     requirements_file=utl.resource_path("requirements.txt")
#     if not os.path.exists(requirements_file):
#         print(f"{requirements_file} not found.")
#         return

#     with open(requirements_file, "r") as f:
#         lines = [line.strip() for line in f if line.strip() and not line.startswith("#")]

#     missing = []
#     for line in lines:
#         package = re.split(r'[<>=]', line.strip())[0]
#         if not is_package_installed(package):
#             missing.append(line)

#     if missing:
#         print("Installing missing packages:", missing)
#         try:
#             subprocess.check_call([sys.executable, "-m", "pip", "install", *missing])
#             print("All missing requirements installed successfully.")
#         except subprocess.CalledProcessError as e:
#             print("Failed to install some requirements:", e)
#     else:
#         print("All required packages are already installed.")

# # --- Run before your imports ---
# install_requirements_if_needed()

from datetime import timedelta, datetime
import dearpygui.dearpygui as dpg
from dearpygui_ext import themes as themes
import numpy as np 
import DataHandling as DH # type: ignore
import Preprocessing as pre
import Features as ft
import time
import csv

#----------------------------------------GLOBAL-----------------------------
global_bands = ft.bands
electrodes_names, all_event_names = {}, {}
exporting_tags = {'Plots': [], 'Signals': [], 'Tables': []}
cwh, cww = 110, 140
app_state = {"windows": {}}

directories = {
    'rec': "C:/Users/OCD/Desktop/DATA/RN",
    'events': "C:/Users/OCD/Desktop/LRodrigues/NeoDBS",
    'save': ""
}

colors = [
        (0, 0, 255, 255),      # blue
        (255, 0, 0, 255),      # red
        (0, 255, 0, 255),      # green
        (255, 165, 0, 255),    # orange
        (255, 255, 0, 255),    # yellow
        (211, 211, 211, 204), #light grey
        (255, 51, 255, 255), #pink
        (102, 0, 204, 255), #purple
        (0, 255, 255, 255), #cian
        (34, 139, 34, 255), #forest green
    ]
#-----------------------------------------UTILS----------------------------------------
[docs]def add_band(sender, app_data, user_data): ''' Internal: Add user-defined band to internal app_state memory dictinary ''' tab_tag = user_data old = getBandsByTab(tab_tag) min = dpg.get_value(f"{tab_tag}_band_min") max = dpg.get_value(f"{tab_tag}_band_max") new = {f'Band {min}-{max}': [min,max]} t_bands = old | new window_id = extract_window_id(tab_tag) app_state['windows'][window_id]['Bands'] = t_bands b = f"{tab_tag}_add_band" text = 'Current Bands (Hz): \n ___________________\n\n' for name, value in t_bands.items(): text = text + f"- {name}: {value}\n" dpg.delete_item(f"{b}_toast",children_only=True) dpg.add_text(text, parent=f"{b}_toast")
[docs]def reset_bands(sender,app_data,user_data): ''' Internal: reset to default bands (D,T,A,B,G) to internal app_state memory dictinary ''' tab_tag = user_data window_id = extract_window_id(tab_tag) b = f"{tab_tag}_add_band" app_state['windows'][window_id]['Bands'] = ft.bands text = 'Current Bands (Hz): \n ___________________\n\n' for name, value in ft.bands.items(): text = text + f"- {name}: {value}\n" dpg.delete_item(f"{b}_toast",children_only=True) dpg.add_text(text, parent=f"{b}_toast")
[docs]def sanitize_filename(filename): '''Internal: Removes or replaces invalid characters from filenames.''' return re.sub(r'[<>:"/\\|?*]', '_', filename) # Replace invalid characters with '_'
[docs]def save_table_to_file(sender, app_data, user_data): '''Internal: Saves DPG table data to a .csv or .txt file''' table_id, tag = user_data # Unpack table ID and file type file_type = 'csv' # Get table columns (headers) column_children = dpg.get_item_children(table_id, slot=0) headers = [dpg.get_item_label(col) for col in column_children] if column_children else [] # Get table rows (data) rows = [] row_children = dpg.get_item_children(table_id, slot=1) if row_children: for row in row_children: cell_children = dpg.get_item_children(row, slot=1) if cell_children: row_data = [dpg.get_value(cell) for cell in cell_children] rows.append(row_data) new_title = dpg.get_value(f"{tag}_file_name") if new_title is not None: filename=new_title else: filename = dpg.get_item_label(tag) filename = sanitize_filename(f"{filename}.{file_type}") if isinstance(app_data,str): last = app_data[app_data.find(' ')+1:] if last != 'None': last = dpg.get_item_label(f"window_{last}") filename = f"Window {last}//{filename}" with open(filename, mode="w", newline="") as file: writer = csv.writer(file) if headers: writer.writerow(headers) writer.writerows(rows) if not dpg.does_item_exist(f"{sender}_toast"): with dpg.tooltip(parent=sender,tag=f"{sender}_toast"): dpg.add_text(f"Saved file: {filename}!")
[docs]def save_band_powers_to_txt(sender, app_data, user_data): ''' Internal: saves band mean power over day signals info[channel][band] = { timestamp: [mean, std], } ''' info, filename=user_data filename = filename + ".txt" with open(filename, "w") as f: for channel, band_data in info.items(): bands = list(band_data.keys()) f.write(f"{channel}:\n") f.write("date," + ",".join(bands) + "\n") timestamps = [] timestamps.extend([entry[0] for entry in band_data[bands[0]]]) for ts in timestamps: row = [ts] for band in bands: mean_val = "" for entry in band_data[band]: if entry[0] == ts: mean_val = str(entry[1][0]) # only mean power break row.append(mean_val) f.write(",".join(row) + "\n") f.write("\n") # space between channels print(f"Saved file: {filename}")
[docs]def add_exporting_tag(window_id,plot=None,signal=None,table=None): if window_id not in exporting_tags.keys(): exporting_tags[window_id] = {'Plots': [], 'Signals': [], 'Tables': []} keys = [] if plot: keys.append(['Plots',plot]) if signal: keys.append(['Signals',signal]) if table: keys.append(['Tables',table]) for value in keys: key, tag = value exporting_tags[key].append(tag) exporting_tags[window_id][key].append(tag)
[docs]def filename_window(sender,app_data,user_data): label = dpg.get_item_label(sender) if 'Table' in label: #get collapsing header label plot_tag = dpg.get_item_parent(sender) else: plot_tag= user_data[1] title = dpg.get_item_label(plot_tag) x, y = dpg.get_item_rect_min(sender) window_pos = (x - 10, y + 10) final_userdata = user_data parent_sender = sender if dpg.does_item_exist(f"{plot_tag}_file"): dpg.delete_item(f"{plot_tag}_file") def handle_submit(sender,app_data,user_data): label = dpg.get_item_label(parent_sender) if dpg.does_item_exist(f"{plot_tag}_file"): dpg.configure_item(f"{plot_tag}_file",show=False) if 'Plot' in label: save_plot(parent_sender,app_data,final_userdata) elif 'Save Table' == label: other, _ = final_userdata save_table_to_file(parent_sender,app_data,(other,plot_tag)) else: file_name = dpg.get_value(f"{plot_tag}_file_name") if not isinstance(file_name,str): file_name = dpg.get_item_label(plot_tag) ud = final_userdata + (file_name,) save_signals(parent_sender,app_data,ud) if app_data and 'All' in app_data: handle_submit(None,app_data,None) return with dpg.window(label='File Name',tag=f"{plot_tag}_file", width = 200,pos=window_pos,no_collapse=True,modal=True): dpg.add_text("Save file as: ") dpg.add_input_text(default_value=title, width=150,tag=f"{plot_tag}_file_name") if 'plot' in label.lower(): with dpg.group(horizontal=True): dpg.add_text("DPI: ") dpg.add_input_int(default_value=300,min_value=300,max_value=600,min_clamped=True,max_clamped=True,step=50,width=150,tag=f"{plot_tag}_dpi") dpg.add_button(label="Submit", callback=handle_submit)
[docs]def save_plot(sender,app_data,user_data): ''' Prepares data from selected plot to be exported into png file. plot_type, plot_tag, y_axis, colormap_int = user_data colomap_int: - int: for regular plots - rgba array: timeline (specific colors) - string: 3D (name of colormap) ''' plot_type, plot_tag, y_axis, colormap_int = user_data tags = dpg.get_item_children(y_axis)[1] mask = [dpg.get_item_configuration(tag)['show'] for tag in tags] if isinstance(colormap_int,int): #follow colormap order colors = [dpg.get_colormap_color(colormap_int,i) for i in range(len(tags))] elif isinstance(colormap_int,str): colors = colormap_int elif sum(colormap_int[0])>4: #specified colors colors = [tuple(np.round(np.array(c)/255,3)) for c in colormap_int] else: colors = colormap_int if not isinstance(colormap_int,str) and plot_type!='SMP': colors = np.array(colors)[mask].tolist() if plot_type not in ['3D','3DC','SMP']: tags = np.array(tags)[mask].tolist() title = dpg.get_item_label(plot_tag) axis_tags = dpg.get_item_children(plot_tag)[1] axis = [dpg.get_item_label(ax) for ax in axis_tags] limits = [dpg.get_axis_limits(ax) for ax in axis_tags] size = dpg.get_item_width(plot_tag), dpg.get_item_height(plot_tag) line_type = [dpg.get_item_info(tag)['type'] for tag in tags] labels = [dpg.get_item_label(tag) for tag in tags] if plot_type in ['3D','3DC']: # for '3D', '3DC' == coherogram children = dpg.get_item_children(plot_tag)[1] #if its in subplots colormap, subplots = children[0], children[1] title = dpg.get_item_label(subplots) line_type = None if plot_type == '3D': subplots = dpg.get_item_children(subplots)[1] else: subplots=[subplots] labels = [dpg.get_item_label(plot) for plot in subplots] axs = [dpg.get_item_children(plot)[1] for plot in subplots] axis = [dpg.get_item_label(ax) for ax in axs[-1]] axis.append(dpg.get_item_label(colormap)) size = dpg.get_item_width(subplots[-1]), dpg.get_item_height(subplots[-1]) limits = [[dpg.get_axis_limits(ax) for ax in plot_axis] for plot_axis in axs] tags = [dpg.get_item_children(ax[1])[1][0] for ax in axs] signals = [] for _,tag in enumerate(tags): values = dpg.get_value(tag) config = dpg.get_item_configuration(tag) density = [config.get('scale_min'),config.get('scale_max')] limits[_].append(density) rows = int(config.get('rows')) cols = int(config.get('cols')) sxx = values[0] # sxx = np.flipud(np.array(sxx)).reshape(rows,cols) sxx = np.flipud(np.array(sxx).reshape(rows,cols)) mins = config.get('bounds_min') maxs = config.get('bounds_max') xlim = [mins[0],maxs[0]] ylim = [mins[1],maxs[1]] freqs = np.linspace(*ylim,rows) time = np.linspace(*xlim,cols) signals.append([freqs,time,sxx]) elif plot_type == 'SMP': extra_tags = [] title = dpg.get_item_label(plot_tag) axis_tags = dpg.get_item_children(plot_tag)[1] tags = np.array(tags)[mask].tolist() axis = [dpg.get_item_label(tag) for tag in axis_tags] limits = [dpg.get_axis_limits(tag) for tag in axis_tags] size = dpg.get_item_width(plot_tag), dpg.get_item_height(plot_tag) signals = {'Y1': [], 'Y2':[]} labels = {'Y1': [], 'Y2':[]} line_type = {'Y1': [], 'Y2':[]} if axis[-1] != '': extra_tags = dpg.get_item_children(axis_tags[-1])[1] mask2 = [dpg.get_item_configuration(tag)['show'] for tag in extra_tags] tags = np.array(tags).tolist() mask = mask + mask2 colors = np.array(colors)[mask].tolist() for _, tag in enumerate(tags): x,y = dpg.get_value(tag)[:2] labels['Y1'].append(dpg.get_item_label(tag)) line = dpg.get_item_info(tag)['type'] line_type['Y1'].append(line) signals['Y1'].append({'X': x, 'Y': y}) for _, tag in enumerate(extra_tags): x,y = dpg.get_value(tag)[:2] labels['Y2'].append(dpg.get_item_label(tag)) line_type['Y2'].append(dpg.get_item_info(tag)['type']) signals['Y2'].append({'X': x, 'Y': y[:len(x)]}) #when line series are added (ex.: y-bocs) axis tags are in following order: x1,y1,x2,y2 elif plot_type in ['2D','CC']: # for '2D' signals = [] for i,tag in enumerate(tags): values = dpg.get_value(tag) if 'Std. Dev' in labels[i]: colors[i][-1]=0.5 x,y1,y2 = values[:3] sign = {'X':x,'Y1':y1,'Y2':y2} elif 'InfLine' in line_type[i]: sign = {'X': values[0]} else: x,y = values[:2] sign = {'X':x,'Y':y} if any(keyword in title for keyword in ("Timeline", "Circadian", "Stimulation", "Event-locked")): sign['X'] = [pre.get_date_from_ts(x) for x in sign['X']] signals.append(sign) elif plot_type in ['Bar','Events']: if plot_type == 'Events': labels = [dpg.get_item_label(tag) for tag in tags] signals = [] for tag in tags: x,y = dpg.get_value(tag)[:2] x = [pre.get_date_from_ts(t) for t in x] signals.append({'X':x,'Y':y}) else: values = [dpg.get_value(tag)[:2] for tag in tags] signals = [{'X': v[0], 'Y': v[1]} for v in values] ticks = dpg.get_item_user_data(axis_tags[0]) if ticks is not None: ticks = np.array(dpg.get_item_user_data(axis_tags[0])) ticks_pos = [float(t) for t in ticks[:,1]] ticks = ticks[:,0] axis.append([ticks_pos, ticks.tolist()]) elif plot_type == 'Pie': tag = tags[0] labels = dpg.get_item_configuration(tag)['labels'] signals = dpg.get_value(tag)[0] colors = [dpg.get_colormap_color(colormap_int,i) for i in range(len(signals))] new_title = dpg.get_value(f"{plot_tag}_file_name") if new_title is not None: title=new_title if directories['save'] != "": title = directories['save'] + f"\\{title}" if isinstance(app_data,str): last = app_data[app_data.find(' ')+1:] if last != 'None': last = dpg.get_item_label(f"window_{last}") title = f"Window {last}//{title}" info = signals,labels,colors,title,axis,limits #signals, labels, colors, title, axis = info dpi = dpg.get_value(f"{plot_tag}_dpi") if dpi is None: dpi = 600 ft.export_plot(info,plot_type,size=size,line_types=line_type,dpi=dpi) dpg.bind_item_theme(sender,change_button_color(-1)) if not dpg.does_item_exist(f"{sender}_toast"): with dpg.tooltip(parent=sender,tag=f"{sender}_toast"): dpg.add_text(f"Saved plot: {title}.png!")
[docs]def save_signals(sender,app_data,user_data): ''' Internal: Prepares data from selected plot to save signals into .csv file parent, plot, plot_type = user_data ''' print(user_data) parent, plot, plot_type, file_name = user_data children = dpg.get_item_children(parent)[1] tfd = False match plot_type: case 'Pie': tag = children[0] labels = dpg.get_item_configuration(tag)['labels'] signals = dpg.get_value(tag)[0] info = {labels[i]: [signals[i]] for i in range(len(signals))} case 'Events': labels = [dpg.get_item_label(tag) for tag in children] signals = [dpg.get_value(tag)[:2] for tag in children] info = {label: [] for label in labels} for idx,signal in enumerate(signals): x,y = signal x = sorted([pre.get_date_from_ts(s) for s in x]) info[labels[idx]] = x case 'Timeline': children = [c for c in children if dpg.get_item_configuration(c)['show'] == True] labels = [dpg.get_item_label(tag) for tag in children] signals = [dpg.get_value(tag)[:2] for tag in children] timeline = [t for s in signals for t in s[0]] timeline = sorted(np.unique(timeline).tolist()) temp = ['']*len(timeline) timeline_str = [pre.get_date_from_ts(t) for t in timeline] info = {'X': timeline_str, 'Left': temp.copy(), 'Right': temp.copy(), 'Events': temp.copy()} for idx,signal in enumerate(signals): x,y = signal if 'LineSeries' not in dpg.get_item_info(children[idx])['type']: info['Events'][timeline.index(x[0])] = labels[idx] continue if x[-1]<x[0]: x, y = x[::-1], y[::-1] beg, end = timeline.index(x[0]), timeline.index(x[-1]) if 'left' in labels[idx].lower(): info['Left'][beg:end] = y else: info['Right'][beg:end] = y if info['Left'] == info['Right'] == temp: info.pop('Left') info.pop('Right') case '2D': #only save signals shown in plot children = [c for c in children if dpg.get_item_configuration(c)['show'] == True] labels = [dpg.get_item_label(tag) for tag in children] if ('Timeline' not in file_name) and ('Circadian' not in file_name): for i,l in enumerate(labels): #Specifically for Survey Peaks if l in ['', ' ']: labels[i] = f"{labels[i-1]} Peak" signals = [dpg.get_value(tag)[1] for tag in children] if labels[-2] in ['Std. Dev','Std. Dev']: #get the actual standard deviation, not the min/max signals[-2] = list(np.array(signals[-1]) - np.array(signals[-2])) mini,maxi,maxX = get_limits(children,plot) cut = maxX[mini:maxi] if 'Timeline' in file_name: cut = [pre.get_date_from_ts(c) for c in cut] elif 'Circadian' in file_name: cut = [str(utl.extract_time(pre.get_date_from_ts(c))) for c in cut] info = {'X': cut} more = {labels[i]: signals[i][mini:maxi] for i in range(len(signals))} info = info | more case 'CC': children = [c for c in children if dpg.get_item_configuration(c)['show'] == True] labels = [dpg.get_item_label(tag) for tag in children] types = [dpg.get_item_info(tag)['type'] for tag in children] signals = [] mini,maxi,maxX = get_limits(children,plot) for i,tag in enumerate(children): if 'Half Max' == labels[i]: signals.append([dpg.get_value(tag)[0][0]]*len(maxX)) elif ('Scatter' in types[i]) or ('Half Max Width' == labels[i]): x,_ = dpg.get_value(tag)[:2] if 'Half Max Width' == labels[i]: x = np.round(np.array(x),3).tolist() new_y = [''] * len(maxX) for xx in x: if xx in maxX: new_y[maxX.index(xx)] = 0 else: temp = np.abs(np.array(maxX)-xx).tolist() idx = min(temp) if idx < 1/250: new_y[temp.index(idx)] = 0 signals.append(new_y) else: signals.append(dpg.get_value(tag)[1]) info, more = {'X': maxX}, {label: signals[i] for i, label in enumerate(labels)} info = info | more case 'PAC': signal = dpg.get_value(children[0])[:2] axis = dpg.get_item_children(plot)[1] labels = [dpg.get_item_label(ax) for ax in axis] info = {labels[i]: signal[i] for i in range(len(axis))} case 'Stimulation': x,stim = dpg.get_value(children[0])[:2] mini,maxi,_=get_limits(children,plot) x,stim = x[mini:maxi],stim[mini:maxi] new_x,new_s = [],[] for i in range(1,len(x)): if i==len(x): continue if stim[i-1] ==stim[i]:continue new_x.append(str(pre.get_date_from_ts(x[i-1]))) new_s.append(stim[i-1]) new_x.append(str(pre.get_date_from_ts(x[-1]))) new_s.append(stim[-1]) info = {'X':new_x,'Stimulation (mA)': new_s} case '3D' | '3DC': # group = plot # subplots = parent file_name = dpg.get_item_label(parent) tfd = True plots = children if plot_type=='3DC': plots = [parent] y_axis = [dpg.get_item_children(plot)[1][1] for plot in plots] signals = [dpg.get_item_children(y)[1][0] for y in y_axis] plot_title = [dpg.get_item_label(plot) for plot in plots] info = [] for i,signal in enumerate(signals): values = dpg.get_value(signal) config = dpg.get_item_configuration(signal) rows = int(config.get('rows')) cols = int(config.get('cols')) sxx = values[0] sxx = np.array(sxx).reshape(rows,cols) mins = config.get('bounds_min') maxs = config.get('bounds_max') xlim = [mins[0],maxs[0]] ylim = [mins[1],maxs[1]] freqs = np.linspace(*ylim,rows).tolist() time = np.linspace(*xlim,cols).tolist() info.append({plot_title[i]: [freqs[::-1],time,sxx]}) if isinstance(app_data,str): last = app_data[app_data.find(' ')+1:] if last != 'None': last = dpg.get_item_label(f"window_{last}") file_name = f"Window {last}//{file_name}" if directories['save'] != '': file_name = directories['save'] + '\\' + file_name ft.export_signals(info,file_name,tfd=tfd) dpg.bind_item_theme(sender,change_button_color(-1)) if not dpg.does_item_exist(f"{sender}_toast"): with dpg.tooltip(parent=sender,tag=f"{sender}_toast"): dpg.add_text(f"Saved file: {file_name}.csv!")
[docs]def save_all(sender,app_data,user_data): key, window_id = user_data if window_id: buttons = exporting_tags[window_id][key] else: buttons = exporting_tags[key] for button in buttons: if not dpg.does_item_exist(button): continue callback = dpg.get_item_callback(button) new_user_data = dpg.get_item_user_data(button) if callback: ad = 'All ' + str(window_id) callback(button,ad,new_user_data)
[docs]def set_directory(sender,app_data,user_data): if isinstance(app_data['selections'], dict): # Check if files were selected try: new = list(app_data['selections'].values())[0] except Exception: return repeated = str(new[::-1]).find("\\") directories[user_data] = new[:-(repeated+1)]
[docs]def change_directory(sender,app_data,user_data): label = dpg.get_item_label(sender) dpg.add_file_dialog(directory_selector=True, show=True, width=700, height=400,callback=set_directory,label=label,user_data=user_data)
#--- Add common buttons
[docs]def add_filters_buttons(tab_tag,signal_tags): ''' Internal: adds filter-related buttons to tab :param tab_tag: (int or str) alias of tab :param signal_tags: list of signals ploted in main plot of tab ''' if dpg.does_item_exist(tab_tag): with dpg.group(parent=tab_tag, horizontal=True, width=600): with dpg.collapsing_header(label="Filters", default_open=False, parent=tab_tag, tag = f"{tab_tag}_filters"): with dpg.group(horizontal=True): dpg.add_input_intx(label='Frequencies',size=2,min_value=1,max_value=100,default_value=[1,100], max_clamped=True, min_clamped=True, tag = f"{tab_tag}_default_wn") dpg.add_button(label="Default Filtering",callback=DefaultFilter, user_data=(tab_tag,signal_tags), tag = f"{tab_tag}_default_filter") with dpg.tooltip(f"{tab_tag}_default_filter"): dpg.add_text("5th Order Butterworth Filter and Removal of Cardiac Component") dpg.add_button(label="Custom Filtering",width=-1, callback=add_combo_filters, user_data=(tab_tag,signal_tags)) else: print(f"Tab {tab_tag} does not exist yet.")
[docs]def add_features_buttons(tab_tag, signal_tags): ''' Internal: adds features-related buttons to tab :param tab_tag: (int or str) alias of tab :param signal_tags: list of signals ploted in main plot of tab ''' if dpg.does_item_exist(tab_tag): with dpg.group(parent=tab_tag, horizontal=True, width=600,tag=f"{tab_tag}_features_group"): b = f"{tab_tag}_add_band" with dpg.collapsing_header(label="Signal Analysis", default_open=False, parent=tab_tag,tag = f"{tab_tag}_features"): add_combo_df_features(tab_tag,signal_tags) add_combo_features(tab_tag,signal_tags) with dpg.group(horizontal=True): dpg.add_input_int(label='Hz', tag=f"{tab_tag}_band_min", width=90, default_value=0, min_value=0,max_value=98, callback=enforce_min_max, user_data=(f"{tab_tag}_band_min", f"{tab_tag}_band_max", 2)) dpg.add_input_int(label='Hz', tag=f"{tab_tag}_band_max", width=90, default_value=100, min_value=2,max_value=100, callback=enforce_min_max, user_data=(f"{tab_tag}_band_min", f"{tab_tag}_band_max", 2)) dpg.add_button(label='Insert Band of Interest', show=True, callback=add_band,user_data=(tab_tag), tag = b) bands = getBandsByTab(tab_tag) text = 'Current Bands (Hz): \n ___________________\n\n' for name, value in bands.items(): text = text + f"- {name}: {value}\n" with dpg.tooltip(parent=b,tag=f"{b}_toast"): dpg.add_text(text) dpg.add_button(label='Reset Bands', show=True, callback=reset_bands,user_data=tab_tag) else: print(f"Tab {tab_tag} does not exist yet.")
[docs]def add_timeline_features_buttons(tab_tag, signal_tags): ''' Internal: adds timeline specific features-related buttons to tab :param tab_tag: (int or str) alias of tab :param signal_tags: list of signals ploted in main plot of tab ''' if dpg.does_item_exist(tab_tag): with dpg.group(parent=tab_tag, horizontal=True, width=600,tag=f"{tab_tag}_features_group"): # "Features" menu simulation with dpg.collapsing_header(label="Signal Analysis", default_open=False, parent=tab_tag,tag = f"{tab_tag}_features"): with dpg.group(horizontal=True): dpg.add_button(label='Circadian Rhythm', callback=circadian_rhythm, user_data=(tab_tag, signal_tags, tab_tag)) dpg.add_button(label='Stimulation',callback=stimulation_timeline, user_data=(tab_tag, signal_tags, tab_tag)) else: print(f"Tab {tab_tag} does not exist yet.")
[docs]def add_combo_filters(sender, app_data, user_data): ''' Internal: Callback to show custom filters :param sender: sender id/alias :param app_data: value of sender in moment of callback :param user_data: data saved to callback ''' tab_tag,signal_tags = user_data filters = ['butter','cheby1','cheby2','ellip', 'bessel','iirnotch','iirpeak'] dpg.add_spacer(parent=f"{tab_tag}_filters") with dpg.group(horizontal=True,parent=f"{tab_tag}_filters"): dpg.add_combo(label='IIR Filters',callback = CustomFilter, user_data=(tab_tag,signal_tags),items = filters, parent=f"{tab_tag}_filters", tag = f"{tab_tag}_combo_filters",width=200)
[docs]def add_combo_features(tab_tag,signal_tags): ''' Internal: adds custom features option to tab :param tab_tag: (int or str) alias of tab :param signal_tags: list of signals ploted in main plot of tab ''' features = ['periodogram','welch','spectrogram', 'dpss', 'wavelet', 'coherence', 'multitaper coherence','cross-correlation','PAC'] dpg.add_spacer(parent=f"{tab_tag}_features") with dpg.group(parent=f"{tab_tag}_features", tag = f"{tab_tag}_custom_features"): dpg.add_combo(label='Customizable Signal Analysis',callback = CustomFeatures, user_data=(tab_tag,signal_tags),items = features, parent=f"{tab_tag}_custom_features", tag = f"{tab_tag}_combo_features",width=200)
[docs]def add_combo_df_features(tab_tag,signal_tags): ''' Internal: adds default features option to tab :param tab_tag: (int or str) alias of tab :param signal_tags: list of signals ploted in main plot of tab ''' features = ['periodogram','welch','spectrogram', 'dpss', 'wavelet', 'coherence', 'multitaper coherence', 'cross-correlation', 'PAC'] #,'multitaper' dpg.add_spacer(parent=f"{tab_tag}_features") with dpg.group(parent=f"{tab_tag}_features", tag = f"{tab_tag}_df_features"): dpg.add_combo(label='Default Signal Analysis',callback = DefaultFeatures, user_data=(tab_tag,signal_tags),items = features, parent=f"{tab_tag}_df_features", tag = f"{tab_tag}_combo_df_features",width=200)
#--- Toggle buttons
[docs]def deviation(sender, app_data, user_data): ''' Internal: Recalculates and plots user-defined data :param sender: sender id/alias :param app_data: choosen deviation, via radio button :param user_data: before, tab_label (objects) before: value of sender, before. if equals: just return tab_label: id/alias of tab ''' before, tab_label = user_data if app_data==before: return if not dpg.does_item_exist(f"{tab_label}_mean"): return media = np.array(dpg.get_value(f"{tab_label}_mean")[1]) std_dev = np.array(dpg.get_value(f"{tab_label}_std")[1]) if app_data == '95% C.I.': dev = (std_dev - media) * -2 else: dev = (std_dev - media) * -0.5 minus = media - dev plus = media + dev dpg.configure_item(f"{tab_label}_std", y1=minus.tolist(), y2=plus.tolist()) dpg.configure_item(sender,user_data=(app_data,tab_label))
[docs]def toggleVisibility(signal_tags, parent, group, user_data): ''' Internal: creates checkbox for each plotted tag (in signal_tags). Callback defines tag/plot visibility. :param signal_tags: list of tags, identifiers of each added plot series :param parent: parent of tags (y axis) :param group: group that encapsulates plot :param user_data: window_id ''' # Create a group for visibility controls window_id = user_data tab_tag = parent def toggle_all(sender, app_data): for tag, checkbox in checkboxes.items(): dpg.set_value(checkbox, app_data) dpg.configure_item(tag, show=app_data) if dpg.does_item_exist(f"{tag}_peak"): dpg.configure_item(f"{tag}_peak",show=app_data) if dpg.does_item_exist(f"{tab_tag}_std"): dpg.configure_item(f"{tab_tag}_std", show=app_data) dpg.configure_item(f"{tab_tag}_mean", show=app_data) if dpg.does_item_exist(f"{tab_tag}_mean") and app_data: #only in events for now mean_power, minus, plus = getMeanActiveSeries(window_id,tab_tag,signal_tags) dpg.configure_item(f"{tab_tag}_std", y1=minus,y2=plus) current = dpg.get_value(f"{tab_tag}_mean") dpg.set_value(f"{tab_tag}_mean", [current[0], mean_power]) def toggle_visibility(sender, app_data, user_data): if dpg.does_item_exist(f"{tab_tag}_hide"): if dpg.get_value(f"{tab_tag}_hide"): hide(sender, False, (signal_tags, [None])) dpg.set_value(f"{tab_tag}_hide",False) dpg.configure_item(user_data, show=app_data) item_id = f"{parent}_select_all" if dpg.does_item_exist(item_id): # Check if the item exists dpg.set_value(item_id, False) else: pass mode = extract_mode(tab_tag) if mode == 'Events': mean_power, minus, plus = getMeanActiveSeries(window_id,tab_tag,signal_tags) if isinstance(mean_power,(int,float,np.float64)): dpg.configure_item(f"{tab_tag}_mean",show=False) dpg.configure_item(f"{tab_tag}_std",show=False) return else: dpg.configure_item(f"{tab_tag}_mean",show=True) dpg.configure_item(f"{tab_tag}_std",show=True) current = dpg.get_value(f"{tab_tag}_mean") dpg.set_value(f"{tab_tag}_mean", [current[0], mean_power]) dpg.configure_item(f"{tab_tag}_std", y1=minus,y2=plus) with dpg.group(parent=group, tag = f"{tab_tag}_tav"): dpg.add_text("Choose signals:") checkboxes = {} dpg.add_checkbox(label="Select All", default_value=True, callback=toggle_all, tag = f"{tab_tag}_all") with dpg.child_window(width=-1, height=cwh, border=True): # Create checkboxes for each plot for series_tag, visible in signal_tags.items(): series_config = dpg.get_item_configuration(series_tag) try: label = series_config.get('label', series_tag.split('_')[-1]) except: label = series_config.get('label') checkbox = dpg.add_checkbox(label=label, default_value=visible, callback=toggle_visibility, user_data=series_tag) checkboxes[series_tag] = checkbox
[docs]def toggleType(signal_tags, parent, user_data): ''' Internal: toggles visibility according to the :param signal_tags: Description :param parent: Description :param user_data: Description ''' type_filter, window_id, tab_tag = user_data def toggle_filtered_series(sender, app_data, user_data): if dpg.does_item_exist(f"{tab_tag}_hide"): if dpg.get_value(f"{tab_tag}_hide"): hide(sender, False, (signal_tags, [None])) dpg.set_value(f"{tab_tag}_hide",False) window_id = extract_window_id(tab_tag) key = 'Y' toggle_label = dpg.get_item_label(sender) for series_tag in signal_tags: series_label = str(dpg.get_item_label(series_tag)) if toggle_label == 'CT': toggle_label = 'CT_A' if toggle_label.lower() in series_label.lower(): # Check if the label contains the type dpg.configure_item(series_tag, show=app_data) if 'psd' in tab_tag: key = 'LFPMagnitude' if dpg.does_item_exist(f"{series_tag}_peak"): dpg.configure_item(f"{series_tag}_peak", show=app_data) if dpg.does_item_exist(f"{tab_tag}_mean"): mean_power, minus, plus = getMeanActiveSeries(window_id,tab_tag,signal_tags,key) if isinstance(mean_power,(int,float,np.float64)): dpg.configure_item(f"{tab_tag}_mean",show=False) dpg.configure_item(f"{tab_tag}_std",show=False) return else: dpg.configure_item(f"{tab_tag}_mean",show=True) dpg.configure_item(f"{tab_tag}_std",show=True) current = dpg.get_value(f"{tab_tag}_mean") dpg.configure_item(f"{tab_tag}_std", y1=minus,y2=plus) dpg.set_value(f"{tab_tag}_mean", [current[0], mean_power]) dpg.add_checkbox(label=type_filter, default_value=True, callback=toggle_filtered_series,tag = f"{tab_tag}_{type_filter}_checkbox", parent=parent)
[docs]def toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag=None,tables=[],dates=None,colors=None): ''' Documentation ''' days, hours, artifacts = [], [], [] #by date normal = True mode = extract_mode(tab_tag) if ('artifacts' not in tab_tag.lower()) and ('psd' not in tab_tag.lower()): # signals = getActiveSignals(tab_tag,signal_tags) for signal in signals: date = signal['Date'] hour = f"{date.hour:02d}:{date.minute:02d}" hours.append(hour) day = f"{date.year}-{date.month:02d}-{date.day:02d}" days.append(day) elif 'artifacts' in tab_tag.lower(): artifacts = [extract_artifact_type(signal['Artifact']) for signal in signals] else: normal = False #(['Hemisphere', 'SensingElectrodes', 'ArtifactStatus', 'LFPFrequency', 'LFPMagnitude']) colormap = 0 if colors: colormap = colors channels = [] sides = ['Left','Right'] if normal: days = sorted(np.unique(days).tolist()) hours = np.unique(hours).tolist() for signal in signals: # by channel # channels.append(extract_channel_from_series_tag(signal['Channel']).upper()) ch = signal['Channel'] if ch in electrodes_names.keys(): ch = electrodes_names[ch] else: electrodes_names[ch] = ch channels.append(extract_channel_from_series_tag(ch)) channels = np.unique(channels).tolist() channels = [ch.upper() for ch in channels] types = [hours,days,channels,sides] label_types=['By Recording:','By Day:','By Channel:','By Hemisphere:'] if len(artifacts)>0: label_types.append('By Artifact:') artifacts = np.unique(artifacts).tolist() types.append(artifacts) else: types, label_types = [], [] if dates: dates = sorted(np.unique(dates).tolist()) types = [dates] label_types = ['By Day:'] for signal in signals: channels.append(utl.after_point(signal['SensingElectrodes'])) channels = np.unique(channels).tolist() types = types + [channels,sides] label_types= label_types + ['By Channel:','By Hemisphere:'] all_toggles = {} for l, array in zip(label_types,types): if len(array)>1: all_toggles[l] = array with dpg.group(tag=f"{tab_tag}_buttons"): # Ensure toggleVisibility is inside a vertical group with dpg.group(tag = f"{tab_tag}_all_streams"): toggleVisibility(signal_tags, tab_tag, group=f"{tab_tag}_all_streams", user_data=window_id) with dpg.group(horizontal=True, parent=f"{tab_tag}_buttons", tag=f"{tab_tag}_types"): no_groups = len(list(all_toggles.keys())) if no_groups<4: for text, values in all_toggles.items(): with dpg.child_window(tag=f"{tab_tag}_{text}_buttons",height=cwh,width=cww,horizontal_scrollbar=True): dpg.add_text(text) for v in values: toggleType(signal_tags,parent=f"{tab_tag}_{text}_buttons", user_data=(v, window_id, tab_tag)) if ('channel' in text.lower()) or ('artifact' in text.lower()): dpg.configure_item(f"{tab_tag}_{text}_buttons",width=cww,horizontal_scrollbar=True) else: g1 = dpg.add_group(parent=f"{tab_tag}_buttons") g2 = dpg.add_group(parent=f"{tab_tag}_buttons") for i in range(4): if i<2: parent=g1 else: parent=g2 text, values = label_types[i], types[i] with dpg.child_window(tag=f"{tab_tag}_{text}_buttons", height=cwh,width=cww,horizontal_scrollbar=True,parent=parent): dpg.add_text(text) for v in values: toggleType(signal_tags,parent=f"{tab_tag}_{text}_buttons", user_data=(v, window_id, tab_tag)) if ('channel' in text.lower()) or ('artifact' in text.lower()): dpg.configure_item(f"{tab_tag}_{text}_buttons",width=cww,horizontal_scrollbar=True) if len(artifacts)==0 and normal: segment_signal(tab_tag, signal_tags,plot_tag) else: with dpg.group(horizontal=True): dpg.add_checkbox(label='Mean Visible', default_value=True, callback=show_mean, parent=f"{tab_tag}_buttons", tag=f"{tab_tag}_mean_visibility",user_data=tab_tag) dpg.add_checkbox(label='Mean Only', default_value=False,callback=hide,parent=f"{tab_tag}_buttons",user_data=(signal_tags,[None]), tag=f"{tab_tag}_hide") dpg.add_checkbox(label='Change to dB',callback=Volt2dB, user_data=(f"y_axis_{tab_tag}", tab_tag, signal_tags, tables),parent=f"{tab_tag}_buttons",default_value=False) dpg.add_separator() dpg.add_text('Shaded Area') dpg.add_radio_button(['Std. Deviation', '95% C.I.'], horizontal=True, callback=deviation, tag = f"{tab_tag}_deviation",user_data=('Standard Deviation',tab_tag)) with dpg.group(): dpg.add_separator(label='Export') b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,callback = filename_window,user_data = ('2D', plot_tag, f"y_axis_{tab_tag}",colormap)) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(f"y_axis_{tab_tag}",f"plot_{tab_tag}",'2D')) add_exporting_tag(window_id,plot=b1,signal=b2) ''' with dpg.group(horizontal=True, parent=f"{tab_tag}_buttons", tag=f"{tab_tag}_types"): with dpg.group(): if len(hours) > 1: with dpg.child_window(tag=f"{tab_tag}_buttons_hours", width=120, height=120): dpg.add_text('By recording:') for day in hours: toggleType(signal_tags, parent=f"{tab_tag}_buttons_hours", user_data=(day, window_id, tab_tag)) if len(days) > 1: dpg.add_child_window(tag=f"{tab_tag}_buttons_day", height=120, width=120) dpg.add_text('By day:', parent=f"{tab_tag}_buttons_day") for day in days: toggleType(signal_tags, parent=f"{tab_tag}_buttons_day", user_data=(day, window_id, tab_tag)) with dpg.group(): if len(channels) > 1: with dpg.child_window(tag=f"{tab_tag}_buttons_channel", height=120, width=120): dpg.add_text('By channel:') for day in channels: toggleType(signal_tags, parent=f"{tab_tag}_buttons_channel", user_data=(day, window_id, tab_tag)) if len(sides) > 1: with dpg.child_window(tag=f"{tab_tag}_buttons_side", height=120, width=120): dpg.add_text('By hemisphere:') for day in sides: toggleType(signal_tags, parent=f"{tab_tag}_buttons_side", user_data=(day, window_id, tab_tag)) '''
[docs]def show_mean(sender,app_data,user_data): tab_tag = user_data c = f"{tab_tag}_hide" if dpg.get_value(c): dpg.set_value(c,False) callback = dpg.get_item_callback(c) if callback: callback(c, False, dpg.get_item_user_data(c)) dpg.set_value(sender,app_data) dpg.configure_item(f"{tab_tag}_mean",show=app_data) dpg.configure_item(f"{tab_tag}_std",show=app_data)
[docs]def toggleDisplay2(signals, signal_tags, window_id, tab_tag, tables=None,plot_tag=None): events = [signal['EventName'] for signal in signals] for i,e in enumerate(events): events[i] = getUpdatedEventName(e) # if e in all_event_names.keys(): # events[i] = all_event_names[e] # else: # all_event_names[e] = e dates = [] for signal in signals: date = utl.parse_datetime(signal['DateInitial']) date = f"{date.day:02d}/{date.month:02d}" dates.append(date) dates = sorted(np.unique(dates).tolist()) events = np.unique(events).tolist() sides = ['Left', 'Right'] with dpg.group(label = f"{tab_tag}_buttons"): toggleVisibility(signal_tags, tab_tag, group = f"{tab_tag}_buttons",user_data=window_id) dpg.add_spacer(parent=f"{tab_tag}_buttons") with dpg.group(horizontal=True, parent=f"{tab_tag}_buttons"): #check days if len(dates)>1: with dpg.child_window(height=cwh,width=cww,horizontal_scrollbar=True): dpg.add_text('By days \n (Day/Month):') for day in dates: toggleType(signal_tags,parent=f"{tab_tag}_buttons_day", user_data=(day, window_id, tab_tag)) #check channels if len(events)>1: with dpg.child_window(label = f"{tab_tag}_buttons_events",height=cwh,width=cww,horizontal_scrollbar=True): dpg.add_text('By events:') for day in events: toggleType(signal_tags,parent=f"{tab_tag}_buttons_events", user_data=(day, window_id, tab_tag)) #check sides if len(sides)>1: with dpg.child_window(label = f"{tab_tag}_buttons_side",height=cwh,width=cww,horizontal_scrollbar=True): dpg.add_text('By hemisphere:') for day in sides: toggleType(signal_tags,parent=f"{tab_tag}_buttons_side", user_data=(day, window_id, tab_tag)) dpg.add_text('Shaded Area') dpg.add_radio_button(['Std. Deviation', '95% C.I.'], horizontal=True, callback=deviation, tag = f"{tab_tag}_deviation",user_data=('Standard Deviation',tab_tag)) dpg.add_separator() with dpg.group(horizontal=True): dpg.add_checkbox(label='Mean Visible', default_value=True, callback=show_mean, parent=f"{tab_tag}_buttons", tag=f"{tab_tag}_mean_visibility",user_data=tab_tag) dpg.add_checkbox(label='Mean Only', default_value=False,callback=hide,parent=f"{tab_tag}_buttons",user_data=(signal_tags,[None]), tag=f"{tab_tag}_hide") dpg.add_checkbox(label='Change to dB',callback=Volt2dB, user_data=(f"y_axis_{tab_tag}", tab_tag, signal_tags, tables),parent=f"{tab_tag}_buttons",default_value=False,tag=f"{tab_tag}_2db") dpg.add_separator(label='Export') b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,callback = filename_window,user_data =('2D', plot_tag, f"y_axis_{tab_tag}",0)) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(f"y_axis_{tab_tag}",f"plot_{tab_tag}",'2D')) add_exporting_tag(window_id,plot=b1,signal=b2) dpg.add_separator() dpg.add_button(label='Recalculate Mean Metrics', callback=recalculateMeanMetrics, width=-1, user_data=tab_tag, parent=f"{tab_tag}_buttons")
[docs]def recalculateMeanMetrics(sender, app_data, user_data): ''' Internal: recaulculates mean metrics of Events :param sender: button id :param app_data: - :param user_data: tab_tag ''' tab_tag = user_data bands = getBandsByTab(tab_tag) if dpg.does_item_exist(f"{tab_tag}_mean"): x,y = dpg.get_value(f"{tab_tag}_mean") mean_power = {'X': x, 'Y': y} dpg.delete_item(f"{tab_tag}_mean_metric", children_only=True) for band in bands.items(): info = ft.extract_psd_features(mean_power, band,bands) create_table(f"{tab_tag}_mean_metric",f"{tab_tag}_mean_metric_table", info, trial_label=band[0])
[docs]def hide(sender,app_data,user_data): ''' Hides streams in plot, except mean and standard deviation and updates checkbox calue :param sender: button id :param app_data: value of checkbox :param user_data: streams, checkboxes ''' streams, checkboxes = user_data tab_tag = dpg.get_item_alias(sender)[:-5] sender_name = dpg.get_item_label(sender) if dpg.does_item_exist(f"{tab_tag}_mean_visibility") and not dpg.get_value(f"{tab_tag}_mean_visibility"): c = f"{tab_tag}_mean_visibility" dpg.set_value(c,True) callback = dpg.get_item_callback(c) if callback: callback(c, True, dpg.get_item_user_data(c)) if checkboxes != [None]: if app_data == False: for c in checkboxes: dpg.set_value(c,True) callback = dpg.get_item_callback(c) ud = dpg.get_item_user_data(c) if sender_name == 'Hide Days': #what ud = 'dont' if callback: callback(c, True, ud) for s in streams: bol = not app_data dpg.configure_item(s, show=bol) if 'psd' in tab_tag: #survey peaks if dpg.does_item_exist(f"{s}_peak"): dpg.configure_item(f"{s}_peak",show=bol) if dpg.does_item_exist(f"{tab_tag}_2db"): if app_data: dpg.configure_item(f"{tab_tag}_2db",enabled=False) if not dpg.does_item_exist(f"{tab_tag}_2db_toast"): with dpg.tooltip(parent=f"{tab_tag}_2db",tag=f"{tab_tag}_2db_toast"): dpg.add_text(f"To enable unit conversion uncheck {sender_name}") else: dpg.configure_item(f"{tab_tag}_2db",enabled=True) if dpg.does_item_exist(f"{tab_tag}_2db_toast"): dpg.delete_item(f"{tab_tag}_2db_toast")
#--- Visualization functions
[docs]def plot_td(signals, parent, tab_tag, signal_tags, files = None, events = False, count = []): ''' Plot Time Domain signals (Streaming, Indefinite, Setup, Survey) :param signals: array of signals dictionaries :param parent: y axis, parent of streams :param tab_tag: tab id :param signal_tags: tags of signals :param files: files id, for Combined window :param events: events data :param count: aiding object --> plots events according to their file ''' window_id = extract_window_id(tab_tag) if events: event_types = [signal['Type'] for signal in signals] event_types = np.unique(event_types).tolist() if count != []: fdx = files for idx, signal in enumerate(signals): if count != []: files = fdx[idx] idx = count[idx] series_tag = f"{tab_tag}_series_{idx}" #+extra combined_tag = f"{tab_tag}_file_{files}_series_{idx}" if events == False: x = signal['X'] y = np.ascontiguousarray(signal['Y']) date = signal['Date'] channel = signal['Channel'] if channel in electrodes_names.keys(): channel = electrodes_names[channel] else: electrodes_names[channel] = channel if 'Data' in app_state['windows'][window_id].keys(): if not dpg.does_item_exist(series_tag): dpg.add_line_series(x, y, label=f"{channel} ({date})", parent=parent, tag=series_tag) signal_tags[series_tag] = True # Track visibility app_state['windows'][window_id]['Data'][tab_tag]['Signals'].append(signal) else: print(f"Skipping duplicate series: {series_tag}") elif not dpg.does_item_exist(combined_tag): dpg.add_line_series(x, y, label=f"{channel} ({date})", parent=parent, tag=combined_tag) signal_tags[combined_tag] = True app_state['windows'][window_id]['Files'][files]['Data'][tab_tag]['Signals'].append(signal) else: print(f"Skipping duplicate series: {combined_tag}") elif events == True: old_channel = signal['Channel'] if old_channel in electrodes_names.keys(): old_channel = electrodes_names[old_channel] else: electrodes_names[old_channel] = old_channel x, y, type_event, channel, serie_idx = signal['X'],signal['Y'], signal['Type'], old_channel, signal['Serie'] if extract_type_window(window_id) == 'Combined': segment_tag = f"{tab_tag}_file_{files}_series_{serie_idx}_{type_event}_{idx}" else: segment_tag = f"{tab_tag}_series_{serie_idx}_{type_event}_{idx}" if not dpg.does_item_exist(segment_tag): dpg.add_line_series(x, y, label=f"{type_event} {signal['Date']} ({channel})", parent=parent, tag=segment_tag) dpg.bind_item_theme(segment_tag,change_inf_line_color(event_types.index(type_event))) signal_tags[segment_tag] = True else: print(f"Skipping duplicate series: {combined_tag}") return signal_tags
[docs]def update_plot_td(tab_tag, signal_tags, title = None, signal_mode = None): ''' Updates tab plot, after filtering and segmentation :param tab_tag: tab id :param signal_tags: array of plotted signals' id :param title: updates title after performed task :param signal_mode: default to None (normal), else considered segments (epoch) ''' active_series = getActiveSeries(signal_tags) window_id = extract_window_id(tab_tag) mode = extract_mode(tab_tag) plot_tag = f"plot_{tab_tag}" previous = dpg.get_item_label(item=plot_tag) if title != None: dpg.set_item_label(item=plot_tag,label=f"{previous} - {title}") for i,serie in enumerate(active_series): if mode == 'Events': signal = getSignalbyIdxSide(window_id,tab_tag,serie) elif signal_mode == None: signal = getSignalbyIdx(window_id,tab_tag,serie) else: signal = getSignalbyIdx(window_id,tab_tag,serie, mode='segment') x = dpg.get_value(serie)[0][:len(signal['X'])] y = signal['Y'] if dpg.does_item_exist(serie): dpg.set_value(serie, [x, y]) # Update existing series data else: #dpg.add_line_series(x, y, label=f"{series}", parent=f"y_axis_{tab_tag}", tag=serie) print('No existing series: ', serie) if isinstance(signal_tags, (list, np.ndarray)): signal_tags[i] = True # Track visibility if isinstance(signal_tags, dict): signal_tags[serie] = True dpg.set_axis_limits_auto(f"x_axis_{tab_tag}")
[docs]def create_table(tab_label,table_tag,features, trial_label = "New Trial"): ''' Creates table, if it already existis adds row. Adds only one row per use, so it need to be inside for-cycle to add several rows. Includes 'Save Table' button. :param tab_label: str/int, tab id :param table_tag: table id :param features: dictionary of information. Keys will be columns, features[key] will be cell :param trial_label: Name of row, editable by user. ''' def add_row(): with dpg.table_row(parent=table_tag): dpg.add_input_text(default_value=trial_label, width=-1) for _, value in features.items(): if isinstance(value, (list, np.ndarray, tuple)): # Check if value is a list or NumPy array if len(value) == 1: # Single-element array dpg.add_input_text(default_value=f"{float(value[0]):.2f}",readonly=True) # Convert to float for clean display else: # Multiple values rounded_values = [float(v) for v in np.round(value, 2)] # Convert each np.float64 to float dpg.add_input_text(default_value=f"{rounded_values}",readonly=True) # Display clean list elif isinstance(value, str): dpg.add_input_text(default_value=value,readonly=True,width=200) else: # Single number case dpg.add_input_text(default_value=f"{float(value):.2f}",readonly=True) # Ensure it's a clean float with dpg.group(parent=tab_label): if not dpg.does_item_exist(table_tag): with dpg.table(header_row=True, borders_innerH=True, borders_innerV=True, borders_outerH=True, borders_outerV=True, tag=table_tag): dpg.add_table_column(label="Trial", width=100) for band in features.keys(): width =100 if band == 'Electrodes': width = -1 sender = dpg.add_table_column(label=band, width=width) if 'std. dev' in band: if not dpg.does_item_exist(f"{sender}_toast"): with dpg.tooltip(parent=sender,tag=f"{sender}_toast"): dpg.add_text(f"Standard Deviation negative values must be considered as positive. \nNegative values are maintained for convertional purpouses.") add_row() if not dpg.does_item_exist(f"{table_tag}_save"): b1 = dpg.add_button(label='Save Table', tag = f"{table_tag}_save", callback=filename_window, user_data=(table_tag, 'csv'), parent=tab_label) add_exporting_tag(extract_window_id(tab_label),table=b1)
[docs]def fill_params_features(app_data): ''' Returns app_data function parameters, and respective default values, and documentation. :param app_data: name of function ''' match app_data: case 'correlate': params = pre.get_required_params(app_data,module='np') functions = pre.get_functions(module='np') doc = functions[app_data].__doc__ if functions[app_data].__doc__ else "No documentation available" case 'dpss': windows = pre.get_required_params(app_data, module = 'ssw') welch = pre.get_required_params('welch') params = windows | welch #join dictionaries replace = { 'x': '---', 'M': '---', 'fs': 250, 'window': 'skip', 'NW': 3, #min1 'Kmax': 5 } params.update(replace) functions = pre.get_functions('ssw') doc1 = functions[app_data].__doc__ if functions[app_data].__doc__ else "No documentation available" functions = pre.get_functions() doc2 = functions['welch'].__doc__ if functions['welch'].__doc__ else "No documentation available" doc = doc1 + doc2 case 'wavelet': app_data = 'tfr_array_morlet' params = pre.get_required_params(app_data, module='mtf') rep = {'freqs':100,'n_cycles': 2,'sfreq':250, 'output':'power'} params.update(rep) functions = pre.get_functions('mtf') doc = functions['tfr_array_morlet'].__doc__ if functions['tfr_array_morlet'].__doc__ else "No documentation available" case 'multitaper': app_data = 'psd_array_multitaper' params = pre.get_required_params(app_data, module='mtf') # rep = {'fmax':100, adaptive = True} functions = pre.get_functions('mtf') doc = functions['psd_array_multitaper'].__doc__ if functions['psd_array_multitaper'].__doc__ else "No documentation available" case _: params = pre.get_required_params(app_data) functions = pre.get_functions() doc = functions[app_data].__doc__ if functions[app_data].__doc__ else "No documentation available" return params, doc
[docs]def apply_feat(func, parameter_values, raw): ''' Applies function (func), with defined parameter_values, to raw signal. :param func: str, function name :param parameter_values: dict, previously filled with default and user-defined :param raw: list, signal to be aplied feature ''' signal = None for param in parameter_values.keys(): if param == 'fs' or param =='sfreq': parameter_values[param] = 250 if param == 'x' or param =='data': parameter_values[param] = np.array(raw) match func: case 'multitaper': func = 'psd_array_multitaper' signal = pre.call_function(func, parameter_values, module='mtf') return signal case 'dpss': if not isinstance(raw,float): parameter_values['M'] = len(np.array(raw)) try: window_len = nfft = int(parameter_values['nfft']) except Exception: window_len = nfft = 512 step = window_len // 4 #75% overlap (since step = ¼ of window) kmax = 2*int(parameter_values['NW']) - 1 if parameter_values['Kmax'] == ' ': parameter_values['Kmax'] = kmax parameter_values['M'] = window_len windows = pre.call_function(func, parameter_values, module = 'ssw') Sxx_sum = np.zeros(nfft//2+1) n_windows = 0 for start in range(0, len(raw) - window_len + 1, step): xw = raw[start:start+window_len] Sxx_tapers = [] for taper in windows: S1 = np.fft.rfft(xw * taper, n=nfft) scale = 1 / (250 * np.sum(taper**2)) psd = (np.abs(S1)**2) * scale Sxx_tapers.append(psd) Sxx_sum += np.mean(Sxx_tapers, axis=0) n_windows += 1 Sxx = Sxx_sum / n_windows freq = np.fft.rfftfreq(nfft, d=1/250) signal = freq, Sxx return signal case 'wavelet': func = 'tfr_array_morlet' a = np.array(raw) try: #older version was 'data' and not 'epoch_data' --> 10.06.2025 - changed again to prior name: this way it has acess to both values parameter_values['data'] = a[np.newaxis, np.newaxis, :] parameter_values['epoch_data'] = a[np.newaxis, np.newaxis, :] # parameter_values['epoch_data'] = parameter_values['data'] if isinstance(parameter_values['freqs'], str): parameter_values['freqs'] = int(parameter_values['freqs']) if isinstance(parameter_values['n_cycles'], str): parameter_values['n_cycles'] = float(parameter_values['n_cycles']) if isinstance(parameter_values['n_cycles'],(int, float)): parameter_values['freqs'] = np.arange(1,parameter_values['freqs']+1, 1) parameter_values['n_cycles'] = parameter_values['freqs'] / parameter_values['n_cycles'] signal = pre.call_function(func, parameter_values, module = 'mtf') return signal except Exception as e: print(e) case _: try: signal = pre.call_function(func, parameter_values) except Exception as e: return str(e) return signal
[docs]def display_error(tab_tag,e): ''' Displays error e in doc container of tab_tag :param tab_tag: str/int, tab parent id :param e: str, exception thrown ''' doc_container = f"{tab_tag}_ft_doc" if dpg.does_item_exist(doc_container): v = dpg.get_value(f"{doc_container}_doc") dpg.delete_item(doc_container,children_only=True) ee = dpg.add_text(e, parent=doc_container) dpg.add_separator(parent=doc_container) dpg.add_text(v, parent=doc_container, tag = f"{doc_container}_doc") dpg.bind_item_theme(ee, change_text_color(1))
[docs]def plot_spectrogram_joined(tags, tab_label, func, tab_tag, parameter_values, mode = None): ''' Computes and plots spectrogram of joined recordings, per channel. :param tags: list, signals' id :param tab_label: str/id, tab id :param func: str, function name :param tab_tag: str/id, parent tab id :param parameter_values: dict, :param mode: Description ''' window_id = extract_window_id(tab_tag) wt = extract_type_window(window_id) new_signal, identifiers, event_names = {}, {}, [] for tag in tags: signal = getSignalbyIdx(window_id,tab_tag,tag,mode) ch = signal['Channel'] ch = getUpdatedElectrodeName(ch) if func in signal.keys(): signal.pop(func) temp = {key: np.array([]) for key in signal.keys()} temp['dX'] = [] if ch not in new_signal.keys(): new_signal[ch] = temp else: new_signal[ch] = new_signal[ch] | temp current_start, prev_end_time = {}, {} # breakpoint() for tag in tags: signal = getSignalbyIdx(window_id,tab_tag,tag,mode) '''X = signal['X'] arr = np.array(X) if np.issubdtype(arr.dtype, np.number): valid = arr[~np.isnan(arr)] if len(valid) > 0:# and np.round(valid[0]) == 0: # 'X' is in seconds signal['dX'] = [signal['Date'] + timedelta(seconds=val) for val in X] else: # 'X' is in Unix timestamp signal['dX'] = [pre.get_date_from_ts(val) for val in X] ch = signal['Channel'] for key in signal.keys(): if key == 'dX' or key == 'Y': new_signal[ch][key] = np.append(new_signal[ch][key], np.array(signal[key])) identifiers[ch] = len(signal[key]) else: new_signal[ch][key] = np.append(new_signal[ch][key], signal[key])''' X = signal['X'] arr = np.array(X) if np.issubdtype(arr.dtype, np.number): valid = arr[~np.isnan(arr)] if len(valid) > 0:# and np.round(valid[0]) == 0: try: signal['dX'] = [signal['Date'] + timedelta(seconds=val) for val in X] except Exception: signal['dX'] = [datetime.fromtimestamp(val) for val in X] else: # 'X' is in Unix timestamp signal['dX'] = [pre.get_date_from_ts(val) for val in X] ch = getUpdatedElectrodeName(signal['Channel']) prev = len(new_signal[ch]['X']) current_start[ch] = signal['dX'][0] # Insert zero gap if there's a time difference if ch in prev_end_time: gap = current_start[ch] - prev_end_time[ch] if gap.total_seconds() > 0: # Limit the gap to 10 min gap = min(gap, timedelta(seconds=15)) # Determine how many points to fill (based on sampling rate) sample_rate = 250 n_fill = int(gap.total_seconds() * sample_rate) if n_fill > 0: # Generate filler time and zero signal filler_dX = [prev_end_time[ch] + timedelta(seconds=i / sample_rate) for i in range(n_fill)] filler_Y = np.zeros(n_fill) # Append the filler new_signal[ch]['dX'] = np.append(new_signal[ch]['dX'], filler_dX) new_signal[ch]['Y'] = np.append(new_signal[ch]['Y'], filler_Y) else: prev_end_time[ch] = None for key in signal.keys(): if key == 'dX' or key == 'Y': new_signal[ch][key] = np.append(new_signal[ch][key], np.array(signal[key])) identifiers[ch] = len(signal[key]) elif key == 'Events': for e in signal[key]: event_names.append(e['Type']) e['sIdx'] = e['Idx'] + prev new_signal[ch][key] = np.append(new_signal[ch][key], signal[key]) else: new_signal[ch][key] = np.append(new_signal[ch][key], signal[key]) prev_end_time[ch] = signal['dX'][-1] event_names = np.unique(event_names).tolist() num_plots = len(new_signal.keys()) num_cols = 1 num_rows = num_plots global_min, global_max = 0,0 per_tag, spectrogram_tags = {}, [] group_tag = f"{tab_label}_plot_group" with dpg.group(horizontal=True, parent=f"{tab_label}", tag=group_tag): dpg.add_colormap_scale(tag=f"{group_tag}_colormap_scale", label="Density (dB)", colormap=dpg.mvPlotColormap_Jet, min_scale = global_min, max_scale=global_max, height=400*num_rows) with dpg.subplots(rows=num_rows, columns=num_cols, label="Spectrograms", width=800, height=400*num_rows, tag = f"{tab_label}_subplots"): for channel in new_signal: title =f"Joined Spectrogram - {channel}" subplot_tag = f"{tab_label}_joined_{channel}" with dpg.plot(label=title, width=800, height=400, tag=subplot_tag, parent=f"{tab_label}_subplots"): x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", scale= dpg.mvPlotScale_Time) y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") y = new_signal[channel]['Y'] if current_start == {}: x = new_signal[channel]['dX'] if isinstance(x[0], (int, float)): x = [pre.get_date_from_ts(val) for val in x] x = [(xx + timedelta(hours=1)).timestamp() for xx in x] else: x = np.arange(0,len(y)/250,1) dpg.configure_item(x_axis, scale = dpg.mvPlotScale_Linear) try: events = new_signal[channel]['Events'] except Exception as e: events = 'None' parameter_values['x'] = np.array(y) signal = pre.call_function(func, parameter_values) frequencies, times, Sxx = signal Sxx_log = 10 * np.log10(Sxx + 1e-10) # Convert to dB scale Sxx_log = np.flipud(Sxx_log) # channels.append(Sxx_log) per_tag[channel] = [Sxx_log,subplot_tag, title, events,x] global_min = min(global_min, Sxx_log.min()) global_max = max(global_max, Sxx_log.max()) tag = dpg.add_heat_series(Sxx_log.flatten(), rows=Sxx_log.shape[0], cols=Sxx_log.shape[1], scale_min=global_min, scale_max=global_max, parent=y_axis, format="",bounds_min=[x[0],0], bounds_max=[x[-1],125]) spectrogram_tags.append(tag) dpg.bind_colormap(subplot_tag, dpg.mvPlotColormap_Jet) if not isinstance(events,str): dpg.add_plot_legend() for event in events: stamp = event['sIdx']/250 #axis is seconds, not datetime type = event['Type'] type = getUpdatedEventName(type) if stamp<=x[-1]: ev = dpg.add_inf_line_series(x=[stamp],label=f'{type}',parent=y_axis) dpg.bind_item_theme(ev,change_inf_line_color(event_names.index(type))) dpg.configure_item(f"{group_tag}_colormap_scale", min_scale=global_min,max_scale=global_max) def link(sender, app_data): dpg.configure_item(f"{tab_label}_subplots", link_all_x=app_data, link_all_y=app_data) '''def mean(sender,app_data): mean_Sxx = np.mean(np.stack(channels), axis=0) with dpg.plot(label='Mean Spectrogram', width=-1, height=400, tag=f"{tab_label}_join_mean", parent=f"{tab_label}_merged"): x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") if mode and wt=='Combined': emin = -int(dpg.get_value(f"{tab_tag}_emin")) emax = int(dpg.get_value(f"{tab_tag}_emax")) else: emin, emax = 0, len(times) dpg.add_heat_series(mean_Sxx.flatten(), rows=mean_Sxx.shape[0], cols=mean_Sxx.shape[1], scale_min=global_min, scale_max=global_max, parent=y_axis, format="", bounds_min=[emin,0], bounds_max=[emax,125]) dpg.bind_colormap(f"{tab_label}_join_mean", dpg.mvPlotColormap_Jet) if not isinstance(events,str): for event in events: stamp = event['Idx']/250 #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample type = event['Type'] dpg.add_plot_legend() dpg.add_inf_line_series(x=[stamp],label=f'{type}',parent=y_axis) ''' def spectrograms_power_over_time(sender,app_data,user_data): bands = getBandsByTab(tab_tag) if dpg.does_item_exist(f"{tab_label}_merged"): dpg.delete_item(f"{tab_label}_merged") with dpg.group(parent=tab_label, tag = f"{tab_label}_merged"): alpha_theme = create_alpha_theme() dpg.add_text('Band Power over Time', parent=f"{tab_label}_merged") dpg.add_separator(parent=f"{tab_label}_merged") for _, value in per_tag.items(): sxx, plot_tag, plot_label, stream_events, time = value header_tag = f"{plot_tag}_header" new_plot_tag = f"{plot_tag}_overtime" if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag, children_only=True) with dpg.collapsing_header(tag=header_tag, parent=f"{tab_label}_merged", label=plot_label): with dpg.plot(label=plot_label, width=-1,tag= f"{plot_tag}_overtime"): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Power (dB)") for b,v in bands.items(): mask = (frequencies*125 >= v[0]) & (frequencies*125 < v[1]) if np.any(mask): power = np.mean(sxx[mask, :],axis=0) # mean across freqs -> shape: (time,) # power = sxx[mask, :] std = np.std(sxx[mask, :], axis=0) min_std = power-std max_std = power+std x_time = np.linspace(0,max(time),len(power)) dpg.add_shade_series(x_time,min_std,y2=max_std,label = f"{b} Std. Deviation", tag=f"{plot_tag}_{b}_std", parent=y_axis) dpg.bind_item_theme(f"{plot_tag}_{b}_std", alpha_theme) dpg.add_line_series(x_time,power,label = f"{b} Power", tag=f"{plot_tag}_{b}_mean", parent=y_axis) if not isinstance(stream_events,str) and wt == 'Individual': for event in stream_events: # stamp = [event['Idx']/(len(times)*250)] #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample stamp = [event['Idx']/250] type = event['Type'] if stamp[0] <= time[-1]: dpg.add_plot_legend() dpg.add_inf_line_series(x=stamp,label=f'{type}',parent=y_axis) b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', new_plot_tag, y_axis,0),width=-1) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,new_plot_tag,'2D')) add_exporting_tag(window_id,plot=b1,signal=b2) with dpg.group(horizontal=False, tag = f"{group_tag}_buttons"): dpg.add_checkbox(label='Synchronize Axis', callback=link) dpg.add_button(label='Band Power over Time', callback=spectrograms_power_over_time) dpg.add_separator() colormap_buttons(group_tag, (global_min, global_max), subplot_tag, spectrogram_tags, group_tag=f"{group_tag}_buttons")
[docs]def plot_df_coherence(parameter_values,tab_tag,tags): ''' Plots default coherence (welch method), computed in plot_coherence :param parameter_values: Description :param tab_tag: Description :param tags: Description ''' labels = [dpg.get_item_label(tag) for tag in tags] signals = [dpg.get_value(tag)[:2] for tag in tags] tab_label = f"{tab_tag}_df_coherence" window_id = extract_window_id(tab_tag) def plot(): labs = [] for no in ['0','1']: tag = f"{tab_label}_s{no}" lab = dpg.get_value(tag) idx = labels.index(lab) values = signals[idx] signal = {} signal['X'], signal['Y'] = values signal['Label'] = lab signal['Channel'] = lab[:lab.find(' ')] # signal = getSignalbyIdx(window_id,tab_tag,tags[idx]) if no =='0': parameter_values['x'] = signal else: parameter_values['y'] = signal labs.append(lab) if labs[0]==labs[1]:return plot_coherence(parameter_values,tab_label) combos = {} with dpg.group(horizontal=True,parent=tab_label): for i,s in enumerate(['Signal 1','Signal 2']): input_id = dpg.add_combo( label=s, items=labels, # <-- customize your options default_value='---', callback=different_combo, user_data=(tab_label, combos), tag = f"{tab_label}_s{i}", width=200 ) combos[s] = input_id dpg.add_button(label='Plot coherence', callback=plot,width=200)
[docs]def plot_coherence(parameter_values, tab_label): ''' Computes default coherence (welch) :param parameter_values: Description :param tab_label: Description ''' if not dpg.does_item_exist(f"{tab_label}_clean"): dpg.add_checkbox(label = 'Clean Previous Plot', parent=tab_label,default_value=True,tag=f"{tab_label}_clean") signal1 = parameter_values['x'] signal2 = parameter_values['y'] parameter_values['x'] = signal1['Y'] parameter_values['y'] = signal2['Y'] parameter_values['fs'] = 250 clean = dpg.get_value(f"{tab_label}_clean") if clean==True: dpg.delete_item(f"{tab_label}_plot_group_text",children_only=True) freq, coh = pre.call_function('coherence', parameter_values) mask = freq <= 100 freq, coh = freq[mask], coh[mask] # parameter_values['nfft'] = 512 p2 = parameter_values.copy() _, pxy = pre.call_function('csd',parameter_values) _, pxx = pre.call_function('welch',parameter_values) p2['x'] = parameter_values['y'] _, pyy = pre.call_function('welch', p2) im = pxy/np.sqrt(pxx*pyy) ab = np.abs(im)[mask] IMcoh = np.abs(np.imag(im)[mask]) if not dpg.does_item_exist(f"{tab_label}_plot_group"): dpg.add_group(parent=tab_label, tag=f"{tab_label}_plot_group") dpg.add_group(parent=f"{tab_label}_plot_group", tag=f"{tab_label}_plot_group_text") # with dpg.group(parent=f"{tab_label}_plot_group", tag = f"{tab_label}_plot_group_text"): dpg.add_text(f"Signal 1: {signal1['Label']}", parent=f"{tab_label}_plot_group_text") dpg.add_text(f"Signal 2: {signal2['Label']}",parent=f"{tab_label}_plot_group_text") plot_tag = f"{tab_label}_plot" if clean: with dpg.plot(label = 'Coherence', width=-1, height=400, tag=plot_tag, parent=f"{tab_label}_plot_group_text"): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)") #tag=f"{tab_label}_x_axis" y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Coherence", tag= f"{tab_label}_y_axis") dpg.add_line_series(freq.tolist(), coh.tolist(), label=f"Coherence | {signal1['Channel']}-{signal2['Channel']}", parent=f"{tab_label}_y_axis") dpg.add_line_series(freq.tolist(), IMcoh.tolist(), label=f"Imaginary Coherence | {signal1['Channel']}-{signal2['Channel']}", parent=f"{tab_label}_y_axis") bands = getBandsByTab(tab_label) if not dpg.does_item_exist(f"{tab_label}_header"): dpg.add_collapsing_header(tag=f"{tab_label}_header",parent=tab_label,label='Extracted Features',default_open=False) for b,band in bands.items(): p = {'X': freq, 'Y':coh} info = ft.extract_psd_features(p,[b,band],bands=bands) create_table(f"{tab_label}_header",f"{tab_label}_table", info, trial_label=f"{b} | {signal1['Channel']}-{signal2['Channel']}") if not dpg.does_item_exist(f"{tab_label}_exp"): with dpg.group(tag=f"{tab_label}_exp", parent=tab_label): dpg.add_separator(label='Export') b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', plot_tag, y_axis,0),width=-1) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,plot_tag,'2D')) add_exporting_tag(extract_window_id(tab_label),plot=b1,signal=b2)
# time.sleep(0.2) # dpg.set_axis_limits_auto(f"{tab_label}_y_axis")
[docs]def plot_multitaper_coherence(parameter_values,tab_tag,tags,flag=None): ''' Computes and Plots multitaper coherence :param parameter_values: Description :param tab_tag: Description :param tags: Description :param flag: Description ''' labels = [dpg.get_item_label(tag) for tag in tags] if flag == None: #flag is None if default feature tab_label = f"{tab_tag}_df_multitaper coherence" else: tab_label = f"{tab_tag}_multitaper coherence" window_id = extract_window_id(tab_tag) bands = getBandsByTab(tab_tag) def calculate(): if dpg.does_item_exist(f"{tab_label}_plot_group"): dpg.delete_item(f"{tab_label}_plot_group",children_only=True) if dpg.does_item_exist(f"{tab_label}_spec_group"): dpg.delete_item(f"{tab_label}_spec_group") dpg.delete_item(f"{tab_label}_progress_bar") if flag==None: for no in ['0','1']: tag = f"{tab_label}_s{no}" lab = dpg.get_value(tag) idx = labels.index(lab) signal = getSignalbyIdx(window_id,tab_tag,tags[idx]) if no =='0': signal1 = signal else: signal2 = signal else: for s in ['x','y']: idx = labels.index(parameter_values[s]) signal = getSignalbyIdx(window_id,tab_tag,tags[idx]) if s == 'x': signal1 = signal else: signal2= signal if len(signal1['Y'])>len(signal2['Y']): raw=signal2['Y'] else: raw = signal1['Y'] window_len = 512 if flag==None: step = window_len // 8 nfft = window_len kmax = 2*parameter_values['NW'] - 1 parameter_values['Kmax'] = kmax parameter_values['M'] = window_len else: step = parameter_values['step'] nfft = parameter_values['nfft'] if step in [' ','']: step =8 parameter_values['step'] = window_len//step else: step = int(step) if nfft in [' ','']: nfft = 256 else: nfft = int(nfft) parameter_values['nfft'] = max(window_len,int(nfft)) parameter_values['M'] = window_len tapers = pre.call_function('dpss', parameter_values, module = 'ssw') Sxx_sum = np.zeros(nfft//2+1, dtype=np.complex128) Syy_sum = np.zeros(nfft//2+1, dtype=np.complex128) Sxy_sum = np.zeros(nfft//2+1, dtype=np.complex128) n_windows = 0 coh_segments,imcoh_segments = [], [] for start in range(0, len(raw) - window_len + 1, step): xw = signal1['Y'][start:start+window_len] yw = signal2['Y'][start:start+window_len] Sxx_tapers, Syy_tapers, Sxy_tapers = [], [], [] for taper in tapers: S1 = np.fft.rfft(xw * taper, n=nfft) S2 = np.fft.rfft(yw * taper, n=nfft) # Sxx_tapers.append(np.abs(S1)**2) # Syy_tapers.append(np.abs(S2)**2) Sxx_tapers.append(S1*np.conj(S1)) Syy_tapers.append(S2*np.conj(S2)) Sxy_tapers.append(S1 * np.conj(S2)) Sxx_sum += np.mean(Sxx_tapers, axis=0) Syy_sum += np.mean(Syy_tapers, axis=0) Sxy_sum += np.mean(Sxy_tapers, axis=0) n_windows += 1 coh_seg = np.abs(Sxy_sum)**2 / (Sxx_sum.real * Syy_sum.real) imcoh_seg = np.abs(np.imag(Sxy_sum) / np.sqrt(Sxx_sum.real * Syy_sum.real)) coh_segments.append(coh_seg) imcoh_segments.append(imcoh_seg) #3rd coh = np.mean(coh_segments, axis=0) freq = np.fft.rfftfreq(nfft, d=1/250) mask = freq <= 100 freq, coh = freq[mask], coh[mask] #3rd imcoh = np.mean(imcoh_segments, axis=0) imcoh = imcoh[mask] label=f"{signal1['Channel']}-{signal2['Channel']}" plot(signal1,signal2,freq,coh,imcoh, label) if not dpg.does_item_exist(f"{tab_label}_coh_spec"): dpg.add_group(tag=f"{tab_label}_space",parent=tab_label) dpg.add_separator(parent=f"{tab_label}_space") dpg.add_button(label = 'Plot Coherogram',width=-1,tag=f"{tab_label}_coh_spec", callback=spectrogram, user_data = (signal1,signal2, tapers, freq, mask), parent=f"{tab_label}_space") dpg.bind_item_theme(f"{tab_label}_coh_spec",change_button_color(3)) else: dpg.set_item_user_data(f"{tab_label}_coh_spec", user_data=(signal1,signal2, tapers, freq, mask)) def plot(signal1,signal2,freq,coh,imcoh,label): plot_tag = f"{tab_label}_plot" if not dpg.does_item_exist(f"{tab_label}_plot_group"): dpg.add_group(parent=tab_label, tag=f"{tab_label}_plot_group") dpg.add_text(f"Signal 1: {signal1['Channel']} - {signal1['Date']}", parent=f"{tab_label}_plot_group") dpg.add_text(f"Signal 2: {signal2['Channel']} - {signal2['Date']}", parent=f"{tab_label}_plot_group") with dpg.plot(label = 'Multitaper Coherence', width=-1, height=400, tag=plot_tag, parent=f"{tab_label}_plot_group"): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)") #tag=f"{tab_label}_x_axis" y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Coherence", tag= f"{tab_label}_y_axis") # freq, coh = freq.tolist(), coh.tolist() dpg.add_line_series(freq.tolist(), coh.tolist(), label=f'Multitaper Coherence | {label}', parent=y_axis) dpg.add_line_series(freq.tolist(), imcoh.tolist(), label=f'Imaginary Multitaper Coherence | {label}', parent=y_axis) if not dpg.does_item_exist(f"{tab_label}_header"): dpg.add_collapsing_header(tag=f"{tab_label}_header",label='Extracted Feautres',parent=f"{tab_label}_plot_group",default_open=False) for b,band in bands.items(): p = {'X': freq, 'Y':coh} info = ft.extract_psd_features(p,[b,band],bands=bands) create_table(f"{tab_label}_header",f"{tab_label}_table", info, trial_label=f"{b} | {signal1['Channel']}-{signal2['Channel']}") if not dpg.does_item_exist(f"{tab_label}_exp"): with dpg.group(tag=f"{tab_label}_exp", parent=tab_label): dpg.add_separator(label='Export') b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', plot_tag, y_axis,0),width=-1) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,plot_tag,'2D')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2) def spectrogram(sender,app_data,user_data): signal1,signal2, tapers, freqs, mask = user_data if dpg.does_item_exist(f"{tab_label}_progress_bar"): dpg.set_value(f"{tab_label}_progress_bar",0.0) else: dpg.add_progress_bar(parent=tab_label, default_value=0.0, overlay='Coherogram Loading', tag = f"{tab_label}_progress_bar",width=-1) if len(signal1['Y'])>len(signal2['Y']): raw=signal2['Y'] else: raw = signal1['Y'] window_len = parameter_values['M'] if 'step' not in parameter_values.keys(): step = 8 nfft = max(window_len, 512) step = window_len // step else: step = int(parameter_values['step']) nfft = int(parameter_values['nfft']) freqs = np.fft.rfftfreq(nfft, d=1/250) mask = freqs <= 100 freqs = freqs[mask] K = int(parameter_values['Kmax']) starts = np.arange(0, len(raw)-window_len+1, step) matrix = np.zeros((len(freqs), len(starts))) for idx, s in enumerate(starts): signal1w, signal2w = signal1['Y'][s:s+window_len], signal2['Y'][s:s+window_len] Sxx = Syy = Sxy = 0 for tap in tapers: X = np.fft.rfft(signal1w * tap, n=nfft) Y = np.fft.rfft(signal2w * tap, n=nfft) Sxx += np.abs(X)**2 Syy += np.abs(Y)**2 Sxy += X * np.conj(Y) total = len(starts) progress = (idx+1)/total dpg.set_value(f"{tab_label}_progress_bar",np.round(progress,2)) Sxx /= K Syy /= K Sxy /= K coh = np.abs(Sxy)**2 / (Sxx * Syy) matrix[:, idx] = coh[mask] matrix = np.flipud(np.array(matrix)) times = (starts + window_len/2) / 250 header_tag = f"{tab_label}_cot" group_tag = f"{tab_label}_spec_group" with dpg.group(parent=tab_label, tag=group_tag, horizontal=True): dpg.add_colormap_scale(tag=f"{tab_label}_colormap_scale", label="Density (dB)", colormap=dpg.mvPlotColormap_Viridis, min_scale = np.min(matrix), max_scale=np.max(matrix), height=400) subplot_tag = f"{tab_label}_spec" with dpg.plot(label='Coherogram', width=600, height=400, tag=subplot_tag): x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") heat = dpg.add_heat_series(matrix.flatten(), rows=len(freqs), cols=len(times), scale_min=np.min(matrix), scale_max=np.max(matrix), parent=y_axis, format="",bounds_min=[0,0], bounds_max=[int(len(signal1['Y'])/250),100]) dpg.bind_colormap(subplot_tag, dpg.mvPlotColormap_Viridis) if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag, children_only=True) colormap_buttons(group_tag, (np.min(matrix), np.max(matrix)), subplot_tag, [heat], not_coherogram=False, group_tag=tab_label) dpg.add_separator(label='Export',parent=tab_label) b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,parent=tab_label,callback = filename_window,user_data =('3DC', group_tag, y_axis,'viridis')) b2 = dpg.add_button(label='Export Signals',width=-1,parent=tab_label,callback=filename_window,user_data=(f"{tab_label}_spec",group_tag,'3DC')) add_exporting_tag(window_id,plot=b1,signal=b2) with dpg.collapsing_header(tag=header_tag, parent=tab_label, label='Coherence over time'): alpha_theme = create_alpha_theme() with dpg.plot(label='Coherence over time',height=400, width=-1,tag=f"{header_tag}_plot"): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Power (dB)") for b,v in bands.items(): mask = (freqs >= v[0]) & (freqs < v[1]) if np.any(mask): power = np.flipud(matrix)[mask, :] std = np.std(power, axis=0) power = np.mean(power,axis=0) min_std = power-std max_std = power+std c = dpg.add_shade_series(times,min_std,y2=max_std,label = f"{b} Std. Dev", parent=y_axis) dpg.bind_item_theme(c, alpha_theme) l = dpg.add_line_series(times,power,label = f"{b} Power", parent=y_axis) dpg.bind_item_theme(l, change_line_thickness(3)) dpg.add_separator(label='Export',parent=header_tag) b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,parent=header_tag,callback = filename_window,user_data =('2D', f"{header_tag}_plot", y_axis,0)) b2 = dpg.add_button(label='Export Signals',width=-1,parent=header_tag,callback=filename_window,user_data=(y_axis,f"{header_tag}_plot",'2D')) add_exporting_tag(window_id,plot=b1,signal=b2) combos = {} if flag == None: #Default with dpg.group(horizontal=True,parent=tab_label): for i,s in enumerate(['Signal 1','Signal 2']): input_id = dpg.add_combo( label=s, items=labels, # <-- customize your options default_value='---', callback=different_combo, user_data=(tab_label, combos), tag = f"{tab_label}_s{i}", width=200 ) combos[s] = input_id dpg.add_button(label='Plot Coherence', callback=calculate,width=200,tag=f"{tab_label}_coh") else: calculate()
[docs]def plot_cross_correlation(parameter_values,tab_tag,tags, mode = None): ''' Plots and computes cross-correlation between two signals, user-choosen, saved into keys 'a' and 'v' in parameter values. :param parameter_values: Description :param tab_tag: Description :param tags: Description :param mode: Description ''' if mode: tab_label = f"{tab_tag}_df_cross-correlation" else: tab_label = f"{tab_tag}_cross-correlation" labels = [dpg.get_item_label(tag) for tag in tags] window_id = extract_window_id(tab_tag) toggle_tags = [] def toggle(sender,app_data,user_data): labs = [dpg.get_item_label(tag) for tag in toggle_tags] name_band = dpg.get_item_label(sender) for idx,l in enumerate(labs): if name_band in l: dpg.configure_item(toggle_tags[idx], show=app_data) def plot(): nonlocal toggle_tags toggle_tags.clear() plot_mode = dpg.get_value(f"{tab_label}_band") signal1, signal2 = None, None if mode: for i,key in enumerate(['a','v']): lab = dpg.get_value(f"{tab_label}_s{i}") try: index = labels.index(lab) parameter_values[f'label_{key}'] = lab except Exception: index = labels.index(parameter_values[f'label_{key}']) signal = getSignalbyIdx(window_id,tab_tag,tags[index]) parameter_values[key] = signal['Y'] if key == 'a': signal1 = signal else: signal2 = signal else: for i,key in enumerate(['a','v']): lab = parameter_values[key] try: index = labels.index(lab) parameter_values[f'label_{key}'] = lab except Exception: index = labels.index(parameter_values[f'label_{key}']) signal = getSignalbyIdx(window_id,tab_tag,tags[index]) parameter_values[key] = signal['Y'] if key == 'a': signal1 = signal else: signal2 = signal if dpg.does_item_exist(f"{tab_label}_size_warning"): dpg.delete_item(f"{tab_label}_size_warning") # parameter_values['method'] = 'direct' parameter_values['a'], parameter_values['v'] = np.array(parameter_values['a']).astype(np.float64), np.array(parameter_values['v']).astype(np.float64) s1, s2 = parameter_values['a'], parameter_values['v'] if len(s1) == len(s2) and parameter_values['mode'] == 'valid': parameter_values['mode'] = 'full' dpg.add_text("Mode changed to 'full' because signals have the same length!", parent=tab_label, tag = f"{tab_label}_size_warning") if dpg.does_item_exist(f"{tab_label}_cc"): dpg.delete_item(f"{tab_label}_text",children_only=True) dpg.delete_item(f"{tab_label}_cc",children_only=True) else: dpg.add_group(tag=f"{tab_label}_cc", parent=tab_label, horizontal=True) dpg.add_group(tag=f"{tab_label}_text", parent=tab_label) dpg.add_text(f"Signal 1: {signal1['Channel']} - {signal1['Date']}",parent=f"{tab_label}_text") dpg.add_text(f"Signal 2: {signal2['Channel']} - {signal2['Date']}",parent=f"{tab_label}_text") plot_tag = f"{tab_label}_ccplot" with dpg.plot(label="Cross-correlation", height=300, width=800, tag = plot_tag, parent=f"{tab_label}_cc"): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend() cross_x = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") dpg.add_plot_axis(dpg.mvYAxis, label="Cross-Correlation Ratio", tag = f"{tab_label}_cc_y") cross_y = f"{tab_label}_cc_y" if dpg.does_item_exist(f"{tab_label}_cc_pb"): dpg.set_value(f"{tab_label}_cc_pb", 0) else: dpg.add_progress_bar(parent=tab_label, overlay='Loading',width=-1, tag = f"{tab_label}_cc_pb") if plot_mode: bands = getBandsByTab(tab_tag) else: bands = {'Bands': 'b'} d = parameter_values['dislocation'] if d == ' ' or d == '': d = 1 else: d = int(d) shape = parameter_values['mode'] normalize = parameter_values['normalization'] if normalize == ' ' or normalize == '': normalize = 'False' progress_value = 0 for b, band in bands.items(): for key in ['a','v']: if not isinstance(band,str): param_filter = {'N': '4', 'Wn': band,'btype':'band','fs': '250'} args = pre.call_function('butter', param_filter) parameter_values[key] = pre.call_function2('filtfilt', *args, parameter_values[key]) correlation =pre.call_function('correlate', parameter_values, module='np') N, D = len(signal1['Y']), len(signal2['Y']) match shape: case 'full': dislocation = np.arange(-(D-1), N) / 250 # dislocation = np.arange((-len(signal1['Y'])+d)/250,(len(signal1['Y'])/250),1/250) case 'same': dislocation = (np.arange(N) - D//2) / 250 case 'valid': dislocation = np.arange(N-D+1) / 250 if normalize == 'True': norm = np.linalg.norm(parameter_values['a']) * np.linalg.norm(parameter_values['v']) correlation = correlation/norm if plot_mode: label = f"{b} Cross-Correlation" info_mode = b if not dpg.does_item_exist(f"{tab_label}_cc_buttons"): dpg.add_group(tag=f"{tab_label}_cc_buttons", parent = f"{tab_label}_cc", horizontal=False) dpg.add_checkbox(label=b,parent=f"{tab_label}_cc_buttons",default_value=True,callback=toggle) else: label = "Cross-correlation" info_mode = None step = 1/(len(bands.keys())*2) dpg.add_line_series(dislocation.tolist(), correlation.tolist(),label=label, parent=cross_y,tag=f"{tab_label}_{label}") progress_value = progress_value + step dpg.set_value(f"{tab_label}_cc_pb", progress_value) toggle_tags.append(f"{tab_label}_{label}") p = {'Y': correlation.tolist(),'X':dislocation.tolist()} info, line_tags = extract_cc_features(p,cross_y,tab_label,info_mode) progress_value = progress_value + step dpg.set_value(f"{tab_label}_cc_pb", progress_value) label = f"{signal1['Channel']} - {signal2['Channel']}" toggle_tags = toggle_tags + line_tags if not dpg.does_item_exist(f"{tab_label}_header"): dpg.add_collapsing_header(tag = f"{tab_label}_header", parent=tab_label, label='Features') create_table(f"{tab_label}_header",f"{tab_label}_table",info,f"{b} | {label}") if not dpg.does_item_exist(f"{tab_label}_export_group"): with dpg.group(tag=f"{tab_label}_export_group",parent=tab_label): dpg.add_separator(label='Export',parent=tab_label) b1 = dpg.add_button(label = 'Export Plot Figure',parent=tab_label,width=-1,callback = filename_window,user_data =('CC', plot_tag, cross_y,0)) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,parent=tab_label,user_data=(cross_y,plot_tag,'CC')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2) if not dpg.does_item_exist(f"{tab_label}_band"): dpg.add_checkbox(label='Band Cross-Correlation', parent=tab_label, tag = f"{tab_label}_band") #, callback=plot) if mode==None: plot() else: #Default combos = {} with dpg.group(horizontal=True,parent=tab_label): parameter_values['mode'] = 'full' parameter_values['dislocation'] = '1' parameter_values['normalization'] = 'True' for i,s in enumerate(['Signal 1','Signal 2']): input_id = dpg.add_combo( label=s, items=labels, # <-- customize your options default_value='---', tag = f"{tab_label}_s{i}", width=200 # callback=different_combo, # user_data=(tab_label, combos), ) combos[s] = input_id dpg.add_button(label='Plot Cross-Correlation', callback=plot,width=200)
[docs]def set_colormap_range(sender,app_data,user_data): ''' Resets colormap range of spectrogram, of colormap and reconfigures spectrograms to same interval. tab_label: str/int, tab id spec_tags: list, spectrogram's series' id colormap_tag: str/int, colormap id :param sender: Description :param app_data: Description :param user_data: tab_label, spec_tags, colormap_tag ''' tab_label, spec_tags, colormap_tag = user_data min_value = dpg.get_value(f"{tab_label}_emin") max_value = dpg.get_value(f"{tab_label}_emax") dpg.configure_item(colormap_tag,min_scale=min_value,max_scale=max_value) for spec in spec_tags: dpg.configure_item(spec,scale_max=max_value,scale_min=min_value)
[docs]def reset_colormap(sender,app_data,user_data): mother, spec_tags, colormap_scale = user_data e_min, e_max = f"{mother}_emin", f"{mother}_emax" mother, min_value, max_value = dpg.get_item_user_data(e_min) dpg.set_value(e_min, min_value) dpg.set_value(e_max, max_value) dpg.configure_item(colormap_scale,min_scale=min_value,max_scale=max_value) for spec in spec_tags: dpg.configure_item(spec,scale_max=max_value,scale_min=min_value)
[docs]def colormap_buttons(mother, values, plot, spec_tags, group_tag = None, not_coherogram=True): #mother = group with the plots global_min, global_max = values if len(spec_tags)>1: y_axis = dpg.get_item_children(plot)[1][1] else: y_axis = dpg.get_item_children(plot)[1][0] if group_tag is None: group_tag=mother colormap_parent = mother else: colormap_parent = group_tag[:group_tag.find("_buttons")] if not not_coherogram: colormap_parent = group_tag group_tag = mother if not_coherogram: with dpg.group(horizontal=False, parent=group_tag): dpg.add_text('Adjust Colormap Scale:') dpg.add_input_int(label='Min: ', tag=f"{mother}_emin", width=90, default_value=global_min,callback=enforce_range, user_data=(mother,global_min,global_max), min_value=global_min,max_value=(global_max-5)) dpg.add_input_int(label='Max: ', tag=f"{mother}_emax", width=90, default_value=global_max,callback=enforce_range, user_data=(mother,global_min,global_max), min_value=global_min,max_value=(global_max-5)) with dpg.group(horizontal=True, tag=f"{mother}_range_cmp"): pass dpg.add_separator(label='Export') b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,callback = filename_window,user_data =('3D', mother, y_axis,'jet')) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(plot,mother,'3D')) add_exporting_tag(extract_window_id(mother),plot=b1,signal=b2) else: with dpg.group(horizontal=False, parent=group_tag): dpg.add_text('Adjust Colormap Scale:') dpg.add_input_float(label='Min: ', tag=f"{mother}_emin", width=110, default_value=global_min, user_data=(mother,global_min,global_max))#, min_value=global_min,max_value=(global_max-5)),callback=enforce_range, dpg.add_input_float(label='Max: ', tag=f"{mother}_emax", width=110, default_value=global_max, user_data=(mother,global_min,global_max))#, min_value=global_min,max_value=(global_max-5)),callback=enforce_range with dpg.group(horizontal=True, tag=f"{mother}_range_cmp"): pass dpg.add_button(label='Set Colormap Range',callback=set_colormap_range,user_data=(mother,spec_tags,f"{colormap_parent}_colormap_scale"),parent=f"{mother}_range_cmp") dpg.add_button(label='Reset Colormap', callback=reset_colormap, user_data = (mother, spec_tags,f"{colormap_parent}_colormap_scale"),parent=f"{mother}_range_cmp")
[docs]def plot_spectrogram_og(tags, tab_label, func, tab_tag, parameter_values, mode = 'segment'): ''' Computes and plots spectrograms individually, into subplots, under one colormap. :param tags: Description :param tab_label: Description :param func: Description :param tab_tag: Description :param parameter_values: Description :param mode: Description ''' window_id = extract_window_id(tab_tag) wt = extract_type_window(window_id) if wt == 'Individual': mode = None num_plots = len(tags) num_cols = 2 num_rows = (num_plots + 1) // num_cols global_min = 0 global_max = 1 Merge = [] per_file = {} per_tag = {} parameter_values['fs'] = 250 spectrogram_tags = [] for i, tag in enumerate(tags): stream = getSignalbyIdx(window_id, tab_tag, tag, mode=mode) file = extract_file_from_series_tag(tag) parameter_values['x'] = np.array(stream['Y']) signal = pre.call_function(func, parameter_values) frequencies, times, Sxx = signal Sxx_log = 10 * np.log10(Sxx + 1e-10) # Convert to dB scale Sxx_log = np.flipud(Sxx_log) global_min = min(global_min, Sxx_log.min()) global_max = max(global_max, Sxx_log.max()) signal = frequencies, times, Sxx_log if file in per_file.keys(): per_file[file][tag] = signal else: per_file[file] = {tag: signal} group_tag = f"{tab_label}_plot_group" with dpg.group(horizontal=True, parent=tab_label, tag=group_tag): dpg.add_colormap_scale(tag=f"{group_tag}_colormap_scale", label="Density (dB)", colormap=dpg.mvPlotColormap_Jet, min_scale = global_min, max_scale=global_max, height=400*num_rows) subplot_tag = f"{tab_label}_subplots" with dpg.subplots(rows=num_rows, columns=num_cols, label="Spectrograms", width=800, height=400*num_rows, tag=subplot_tag): for i,tag in enumerate(tags): stream = getSignalbyIdx(window_id, tab_tag, tag, mode=mode) stream_events = getEventsbyTag(tab_tag,tag) file = extract_file_from_series_tag(tag) y = stream['Y'] channel = getUpdatedElectrodeName(stream['Channel']) date = stream['Date'] signal = per_file[file][tag] Merge.append(signal) frequencies, times, Sxx_log = signal # Create a new subplot for each heat series if mode and wt == 'Combined': plot_label = f"{channel} | {stream['Type']} - {date}" plot_tag = f"{tab_label}_{channel}_{stream['Type']}_{i}" else: plot_label = f"{channel} | {date}" plot_tag = f"{tab_label}_{channel}_{date}" with dpg.plot(label=plot_label, width=-1, height=-1, tag = plot_tag): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") if mode and wt== 'Combined': emin = -int(dpg.get_value(f"{tab_tag}_emin")) emax = int(dpg.get_value(f"{tab_tag}_emax")) else: emin = 0 emax = len(y)/250 per_tag[tag] = [Sxx_log,plot_tag, plot_label, stream_events, (emin, emax)] t = dpg.add_heat_series(Sxx_log.flatten(), rows=Sxx_log.shape[0], cols=Sxx_log.shape[1], scale_min=global_min, scale_max=global_max, parent=y_axis, format="", bounds_min=[emin,0], bounds_max=[emax,125]) spectrogram_tags.append(t) dpg.bind_colormap(plot_tag, dpg.mvPlotColormap_Jet) if stream_events != None and wt == 'Individual': mode = 'segment' #why?? for colorIdx, event in enumerate(stream_events): # stamp = [event['Idx']/(len(times)*250)] #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample stamp = [event['Idx']/250] type = event['Type'] if stamp[0] <= Sxx_log.shape[1]: dpg.add_plot_legend() inf = dpg.add_inf_line_series(x=stamp,label=f'{type}',parent=y_axis) color = change_inf_line_color(colorIdx) dpg.bind_item_theme(inf, color) dpg.configure_item(item=f"{group_tag}_colormap_scale", min_scale=global_min, max_scale=global_max) def link(sender, app_data): dpg.configure_item(f"{tab_label}_subplots", link_all_x=app_data, link_all_y=app_data) def spectrograms_power_over_time(sender,app_data,user_data): bands = getBandsByTab(tab_tag) if dpg.does_item_exist(f"{tab_label}_merged"): dpg.delete_item(f"{tab_label}_merged") with dpg.group(parent=tab_label, tag = f"{tab_label}_merged"): alpha_theme = create_alpha_theme() dpg.add_text('Band Power over Time', parent=f"{tab_label}_merged") dpg.add_separator(parent=f"{tab_label}_merged") for _, value in per_tag.items(): sxx, plot_tag, plot_label, stream_events, time = value header_tag = f"{plot_tag}_header" new_plot_tag = f"{plot_tag}_overtime" if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag, children_only=True) with dpg.collapsing_header(tag=header_tag, parent=f"{tab_label}_merged", label=plot_label): with dpg.plot(label=plot_label, width=-1,tag=new_plot_tag): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Power (dB)") for b,v in bands.items(): mask = (frequencies >= v[0]) & (frequencies < v[1]) if np.any(mask): power = np.flipud(sxx)[mask, :] power = np.mean(power,axis=0) std = np.std(sxx[mask, :], axis=0) min_std = power-std max_std = power+std emin, emax = time # time_array = np.arange(emin,emax,1/len(power)) # time_array = np.linspace(emin,emax,int(-emin+emax+1)) time_array = np.linspace(emin,emax,len(power)) dpg.add_shade_series(time_array,min_std,y2=max_std,label = f"{b} Std. Deviation", tag=f"{plot_tag}_{b}_std", parent=y_axis) dpg.bind_item_theme(f"{plot_tag}_{b}_std", alpha_theme) l = dpg.add_line_series(time_array,power,label = f"{b} Power", tag=f"{plot_tag}_{b}_mean", parent=y_axis) dpg.bind_item_theme(l, change_line_thickness(3)) if stream_events != None and wt == 'Individual': for event in stream_events: # stamp = [event['Idx']/(len(times)*250)] #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample stamp = [event['Idx']/250] type = event['Type'] if stamp[0] <= Sxx_log.shape[1]: dpg.add_plot_legend() dpg.add_inf_line_series(x=stamp,label=f'{type}',parent=y_axis) b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', new_plot_tag, y_axis,0),width=-1) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,new_plot_tag,'2D')) add_exporting_tag(window_id,plot=b1,signal=b2) def mean_spectrogram(sender,app_data,user_data): if dpg.does_item_exist(f"{tab_label}_mean_spec"): dpg.delete_item(f"{tab_label}_mean_spec") all_Sxx = user_data all_Sxx = [s[2] for s in all_Sxx] shapes = [s.shape for s in all_Sxx] if not all(shape == shapes[0] for shape in shapes): e = "All spectrograms must have the same time length to compute the mean." t = dpg.add_text(e, parent=tab_label) dpg.bind_item_theme(t,change_text_color(1)) return # Compute the mean spectrogram (linear scale) mean_Sxx_log = np.nanmean(np.stack(all_Sxx), axis=0) title = "Mean Spectrogram" subplot_tag = f"{tab_label}_mean" mother = f"{tab_label}_mean_group" with dpg.collapsing_header(label='Mean Spectrogram', parent=tab_label, tag = f"{tab_label}_mean_spec"): with dpg.group(horizontal=True,tag=mother): dpg.add_colormap_scale(tag=f"{mother}_colormap_scale", label="Density (dB)", colormap=dpg.mvPlotColormap_Jet, min_scale = global_min, max_scale=global_max, height=400) with dpg.plot(label=title, width=500, height=400, tag=subplot_tag): x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") if mode and wt== 'Combined': emin = -int(dpg.get_value(f"{tab_tag}_emin")) emax = int(dpg.get_value(f"{tab_tag}_emax")) else: emin = 0 emax = len(y)/250 mean_s = dpg.add_heat_series(mean_Sxx_log.flatten(), rows=mean_Sxx_log.shape[0], cols=mean_Sxx_log.shape[1], scale_min=global_min, scale_max=global_max, parent=y_axis, format="", bounds_min=[emin,0], bounds_max=[emax,125]) dpg.bind_colormap(subplot_tag, dpg.mvPlotColormap_Jet) colormap_buttons(mother,(global_min,global_max),subplot_tag, [mean_s]) with dpg.group(horizontal=False, tag = f"{group_tag}_buttons"): dpg.add_checkbox(label='Synchronize Axis', callback=link) dpg.add_button(label='Band Power over Time', callback=spectrograms_power_over_time,width=-1) dpg.add_button(label='Mean Spectrogram', callback=mean_spectrogram,width=-1, user_data=Merge) colormap_buttons(group_tag, (global_min, global_max), subplot_tag, spectrogram_tags, group_tag=f"{group_tag}_buttons")
[docs]def choose(sender,app_data,user_data): ''' Buttons to choose between individual or joined spectrogram, or wavelet. Only appears in the case of there being more than 1 recording from the same channel tags: list, signals' id tab_label: str/id, tab id func: str, function name tab_tab: str/id, tab parent id parameter_values: dict, key: name of parameter, item: value :param sender: Description :param app_data: Description :param user_data: tags, tab_label, func, tab_tag, parameter_values, mode ''' tags, tab_label, func, tab_tag, parameter_values, mode = user_data window_id = extract_window_id(tab_tag) wt = extract_type_window(window_id) continue_flag = True #Mode to get signal - if event or full signal if wt == 'Individual': mode = None elif mode == None: # with dpg.child_window(parent=tab_label): # dpg.add_text(f"{func} only available in Combined Analysis for Event-lock based signals", show= True) continue_flag = True if continue_flag == True: events = [getEventsbyTag(tab_tag,serie) for serie in tags] if all(e is None for e in events): mode = None else: mode = 'segment' #Mode to plot: joined or separate plot_mode = app_data if plot_mode == 'Join Recordings': plot_mode = 'join' else: plot_mode = 'separate' #erase group if it already exists if dpg.does_item_exist(f"{tab_label}_plot_group"): dpg.delete_item(f"{tab_label}_plot_group") if dpg.does_item_exist(f"{tab_label}_merged"): dpg.delete_item(f"{tab_label}_merged") if plot_mode == 'join': #plot per recording + possibility of merge all if func == 'spectrogram': plot_spectrogram_joined(tags, tab_label, func, tab_tag, parameter_values, mode) else: plot_wavelet_joined(tags, tab_label, func, tab_tag, parameter_values, mode) else: #plot per channel if func == 'spectrogram': plot_spectrogram_og(tags, tab_label, func, tab_tag, parameter_values, mode) else: plot_wavelet(tags, tab_label, func, tab_tag, parameter_values, mode)
[docs]def plot_wavelet_joined(tags, tab_label, func, tab_tag, parameter_values, mode = None): ''' Plots wavelet, computed for all recordings joined by channel. :param tags: Description :param tab_label: Description :param func: Description :param tab_tag: Description :param parameter_values: Description :param mode: default to None ''' window_id = extract_window_id(tab_tag) wt = extract_type_window(window_id) new_signal, identifiers, event_names = {}, {}, [] '''Initializing new signal dictionary''' for tag in tags: signal = getSignalbyIdx(window_id,tab_tag,tag,mode) ch = signal['Channel'] if ch in electrodes_names.keys(): ch = electrodes_names[ch] else: electrodes_names[ch] = ch if func in signal.keys(): signal.pop(func) temp = {key: np.array([]) for key in signal.keys()} temp['dX'] = [] if ch not in new_signal.keys(): new_signal[ch] = temp else: new_signal[ch] = new_signal[ch] | temp '''Filling new signal dictionary''' prev_end_time = {} current_start = {} for tag in tags: signal = getSignalbyIdx(window_id,tab_tag,tag,mode) X = signal['X'] arr = np.array(X) if np.issubdtype(arr.dtype, np.number): valid = arr[~np.isnan(arr)] if len(valid) > 0:# and np.round(valid[0]) == 0: signal['dX'] = [datetime.fromtimestamp(val) for val in X] else: signal['dX'] = [pre.get_date_from_ts(val) for val in X] ch = getUpdatedElectrodeName(signal['Channel']) prev = len(new_signal[ch]['X']) current_start[ch] = signal['dX'][0] if ch in prev_end_time: gap = current_start[ch] - prev_end_time[ch] if gap.total_seconds() > 0: # Limit the gap to 10 min gap = min(gap, timedelta(seconds=15)) # Determine how many points to fill (based on sampling rate) sample_rate = 250 n_fill = int(gap.total_seconds() * sample_rate) if n_fill > 0: # Generate filler time and zero signal filler_dX = [prev_end_time[ch] + timedelta(seconds=i / sample_rate) for i in range(n_fill)] filler_Y = np.zeros(n_fill) # Append the filler new_signal[ch]['dX'] = np.append(new_signal[ch]['dX'], filler_dX) new_signal[ch]['Y'] = np.append(new_signal[ch]['Y'], filler_Y) else: prev_end_time[ch] = None for key in signal.keys(): if key == 'dX' or key == 'Y': new_signal[ch][key] = np.append(new_signal[ch][key], np.array(signal[key])) identifiers[ch] = len(signal[key]) elif key == 'Events': for e in signal[key]: event_names.append(e['Type']) e['sIdx'] = e['Idx'] + prev new_signal[ch][key] = np.append(new_signal[ch][key], signal[key]) else: new_signal[ch][key] = np.append(new_signal[ch][key], signal[key]) prev_end_time[ch] = signal['dX'][-1] num_plots = len(new_signal.keys()) num_cols = 1 num_rows = num_plots global_min, global_max = 0,0 per_tag, wavelet_tags = {}, [] bands = getBandsByTab(tab_tag) event_names = np.unique(event_names).tolist() group_tag = f"{tab_label}_plot_group" with dpg.group(horizontal=True, parent=tab_label, tag=group_tag): dpg.add_colormap_scale(tag=f"{group_tag}_colormap_scale", label="Density (dB)", colormap=dpg.mvPlotColormap_Jet, min_scale = global_min, max_scale=global_max, height=400*num_rows) with dpg.subplots(rows=num_rows, columns=num_cols, label="Wavelet", width=800, height=400*num_rows, tag = f"{tab_label}_subplots"): for channel in new_signal: title =f"Joined Wavelet - {channel}" subplot_tag = f"{tab_label}_joined_{channel}" with dpg.plot(label=title, width=800, height=400, tag=subplot_tag, parent=f"{tab_label}_subplots"): x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (DateTime)", scale= dpg.mvPlotScale_Time) y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") y = new_signal[channel]['Y'] if current_start == {}: x = new_signal[channel]['dX'] if isinstance(x[0], (int, float)): x = [pre.get_date_from_ts(val) for val in x] x = [(xx + timedelta(hours=1)).timestamp() for xx in x] else: x = np.arange(0,len(y)/250,1) dpg.configure_item(x_axis, scale = dpg.mvPlotScale_Linear) try: events = new_signal[channel]['Events'] except Exception as e: events = 'None' signal = None try: signal = apply_feat(func,parameter_values,y) updateSignalbyIdx(signal,tab_tag,tag,func) except Exception as e: print(e) tfr = 10 * np.log10(signal + 1e-8) tfr = np.flipud(tfr[0, 0, :, :]) downsampled_tfr = tfr[:, ::5] heatmap_data = downsampled_tfr.flatten().tolist() per_tag[channel] = [downsampled_tfr,subplot_tag, title, events, x] global_min = min(global_min, np.min(downsampled_tfr)) global_max = max(global_max, np.max(downsampled_tfr)) tag = dpg.add_heat_series(heatmap_data, rows=downsampled_tfr.shape[0], cols=downsampled_tfr.shape[1], scale_min=global_min, scale_max=global_max, parent=y_axis, format="",bounds_min=[x[0],0], bounds_max=[x[-1],100]) wavelet_tags.append(tag) dpg.bind_colormap(subplot_tag, dpg.mvPlotColormap_Jet) if not isinstance(events,str): dpg.add_plot_legend() for event in events: stamp = event['sIdx']/250 #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample name = event['Type'] if stamp <= x[-1]: ev = dpg.add_inf_line_series(x=[stamp],parent=y_axis,label = getUpdatedEventName(name)) dpg.bind_item_theme(ev,change_inf_line_color(event_names.index(name))) dpg.configure_item(f"{group_tag}_colormap_scale", min_scale = global_min, max_scale=global_max) def link(sender, app_data): dpg.configure_item(f"{tab_label}_subplots", link_all_x=app_data, link_all_y=app_data) def mean_wavelet(channels): # mean_Sxx = np.mean(np.stack(channels), axis=0) max_time = max(ch.shape[1] for ch in channels) num_freqs = channels[0].shape[0] for i,ch in enumerate(channels): pad_width = max_time - ch.shape[1] if pad_width > 0: pad_array = np.full((num_freqs, pad_width), np.nan) channels[i] = np.hstack([ch, pad_array]) stacked = np.stack(channels, axis=0) # shape: (channels, freqs, time) mean_Sxx = np.nanmean(stacked, axis=0) with dpg.plot(label='Mean Wavelet', width=-1, height=400, tag=f"{tab_label}_join_mean", parent=f"{tab_label}_merged"): x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") if mode and wt=='Combined': emin = -int(dpg.get_value(f"{tab_tag}_emin")) emax = int(dpg.get_value(f"{tab_tag}_emax")) else: emin, emax = 0, len(x)*5/250 #5 is downsampling factor, 250 is fs dpg.add_heat_series(mean_Sxx.flatten(), rows=mean_Sxx.shape[0], cols=mean_Sxx.shape[1], scale_min=global_min, scale_max=global_max, parent=y_axis, format="", bounds_min=[emin,0], bounds_max=[emax,num_freqs]) dpg.bind_colormap(f"{tab_label}_join_mean", dpg.mvPlotColormap_Jet) if not isinstance(events,str): for event in events: stamp = event['Idx']/250 #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample if stamp <= emax: type = event['Type'] dpg.add_plot_legend() dpg.add_inf_line_series(x=[stamp],label=f'{type}',parent=y_axis) def merge_wavelet(): if dpg.does_item_exist(f"{tab_label}_merged"): dpg.delete_item(f"{tab_label}_merged") with dpg.group(parent=tab_label, tag = f"{tab_label}_merged"): for _, value in per_tag.items(): sxx, plot_tag, plot_label, events, time = value header_tag = f"{plot_tag}_header" if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag, children_only=True) with dpg.collapsing_header(tag=header_tag, parent=f"{tab_label}_merged", label=plot_label): with dpg.plot(label= f"Bands Power over Time - {_}",width=-1,tag=f"{header_tag}_plot"): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Power (dB)") alpha_theme = create_alpha_theme() for b,v in bands.items(): frequencies = parameter_values['freqs'] mask = (frequencies >= v[0]) & (frequencies < v[1]) if np.any(mask): power = np.mean(sxx[mask, :],axis=0) # mean across freqs -> shape: (time,)std = np.std(sxx[mask, :], axis=0) std = np.std(sxx[mask, :],axis=0) min_std = power-std max_std = power+std x_time = np.linspace(0,max(time),len(power)) dpg.add_shade_series(x_time,min_std,y2=max_std,label = f"{b} Std. Deviation", tag=f"{plot_tag}_{b}_std", parent=y_axis) dpg.bind_item_theme(f"{plot_tag}_{b}_std", alpha_theme) dpg.add_line_series(x_time,power,label = f"{b} Mean", tag=f"{plot_tag}_{b}_mean", parent=y_axis) if not isinstance(events,str): dpg.add_plot_legend() for event in events: stamp = event['sIdx']/250 #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample name = event['Type'] if stamp <= time[-1]: ev = dpg.add_inf_line_series(x=[stamp],parent=y_axis,label = getUpdatedEventName(name)) dpg.bind_item_theme(ev,change_inf_line_color(event_names.index(name))) b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', f"{header_tag}_plot", y_axis,0),width=-1,parent=header_tag) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,f"{header_tag}_plot",'2D'),parent=header_tag) add_exporting_tag(window_id,plot=b1,signal=b2) with dpg.group(horizontal=False, tag = f"{group_tag}_buttons"): dpg.add_checkbox(label='Synchronize Axis', callback=link) dpg.add_button(label='Band Power over Time', callback=merge_wavelet) dpg.add_separator() colormap_buttons(group_tag, (global_min, global_max), subplot_tag, wavelet_tags, group_tag=f"{group_tag}_buttons")
[docs]def plot_wavelet(tags, tab_label, func, tab_tag, parameter_values, mode = None): ''' Plots wavelet, computed for all recordings individually. :param tags: Description :param tab_label: Description :param func: Description :param tab_tag: Description :param parameter_values: Description :param mode: default to None ''' window_id = extract_window_id(tab_tag) wt = extract_type_window(window_id) num_plots = len(tags) num_cols = 2 num_rows = (num_plots + 1) // num_cols events = None per_tag, wavelet_tags, event_names = {}, [], [] bands = getBandsByTab(tab_tag) global_min,global_max = 0,1 group_tag = f"{tab_label}_plot_group" with dpg.group(horizontal=True,tag = group_tag, parent=tab_label): dpg.add_colormap_scale(tag=f"{group_tag}_colormap_scale", label="Density (dB)", colormap=dpg.mvPlotColormap_Jet, min_scale=global_min, max_scale=global_max, height=400*num_rows) subplot_tag = f"{tab_label}_subplots" with dpg.subplots(rows=num_rows, columns=num_cols, label=f"Wavelet", width=800, height=400*num_rows, tag=subplot_tag): for idx, tag in enumerate(tags): stream = getSignalbyIdx(window_id,tab_tag,tag,mode=mode) if mode == None: #else the stream is the event try: events = getEventsbyTag(tab_tag, tag) except Exception as e: events = 'None' y = stream['Y'] channel = stream['Channel'] date = stream['Date'] signal = None try: signal = apply_feat(func,parameter_values,y) except Exception as e: print(e,': ',parameter_values['epoch_data']) if mode and wt=='Combined': #Plotting the events date = date + timedelta(seconds=int(stream['Idx']/250)) plot_label = f"{channel} - {stream['Type']} - {date}" plot_tag = f"{tab_label}_{channel}_{stream['Type']}_{idx}" else: plot_label = f"{channel} | {date}" plot_tag = f"{tab_label}_{channel}_{date}_{idx}" with dpg.plot(label = plot_label, width=-1, height=-1, tag =plot_tag): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Frequency (Hz)") freqs = parameter_values['freqs'] tfr = 10 * np.log10(signal) tfr = np.flipud(tfr[0, 0, :, :]) if mode and wt== 'Combined': emin = -int(dpg.get_value(f"{tab_tag}_emin")) emax = int(dpg.get_value(f"{tab_tag}_emax")) else: emin = 0 emax = len(y)/250 downsample = 2*int(emax/13) downsampled_tfr = tfr[:, ::downsample] per_tag[tag] = [downsampled_tfr,plot_tag, plot_label, events, (emin,emax)] heatmap_data = downsampled_tfr.flatten().tolist() global_min = min(global_min,np.min(downsampled_tfr)) global_max = max(global_max,np.max(downsampled_tfr)) # Add downsampled heatmap series tag = dpg.add_heat_series(heatmap_data, rows=downsampled_tfr.shape[0], cols=downsampled_tfr.shape[1], parent=y_axis, scale_min=global_min, scale_max=global_max, format="", bounds_min=[emin,freqs[0]], bounds_max=[emax,freqs[-1]]) wavelet_tags.append(tag) dpg.bind_colormap(plot_tag, dpg.mvPlotColormap_Jet) try: events = stream['Events'] except Exception as e: events = 'None' if not isinstance(events,str): for event in events: stamp = event['Idx']/250 #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample name = event['Type'] if name not in event_names: event_names.append(name) if stamp <= stream['X'][-1]: ev = dpg.add_inf_line_series(x=[stamp],parent=y_axis,label = getUpdatedEventName(name)) dpg.bind_item_theme(ev,change_inf_line_color(event_names.index(name))) dpg.configure_item(f"{group_tag}_colormap_scale",min_scale=global_min,max_scale=global_max) def link(sender, app_data): dpg.configure_item(f"{tab_label}_subplots", link_all_x=app_data, link_all_y=app_data) def merge_wavelet(): if dpg.does_item_exist(f"{tab_label}_merged"): dpg.delete_item(f"{tab_label}_merged") with dpg.group(parent=tab_label, tag = f"{tab_label}_merged"): for _, value in per_tag.items(): sxx, plot_tag, plot_label, events, time = value header_tag = f"{plot_tag}_header" if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag, children_only=True) with dpg.collapsing_header(tag=header_tag, parent=f"{tab_label}_merged", label=plot_label): with dpg.plot(label= f"Bands Power over Time - {_}",width=-1,tag=f"{header_tag}_plot"): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Power (dB)") alpha_theme = create_alpha_theme() sxx = np.flipud(sxx) for b,v in bands.items(): frequencies = parameter_values['freqs'] mask = (frequencies >= v[0]) & (frequencies < v[1]) if np.any(mask): power = np.mean(sxx[mask, :],axis=0) # mean across freqs -> shape: (time,)std = np.std(sxx[mask, :], axis=0) std = np.std(sxx[mask, :],axis=0) min_std = power-std max_std = power+std emin, emax = time time_array = np.linspace(emin,emax,len(power)) dpg.add_shade_series(time_array,min_std,y2=max_std,label = f"{b} Std. Deviation", tag=f"{plot_tag}_{b}_std", parent=y_axis) dpg.bind_item_theme(f"{plot_tag}_{b}_std", alpha_theme) dpg.add_line_series(time_array,power,label = f"{b} Mean", tag=f"{plot_tag}_{b}_mean", parent=y_axis) if isinstance(events,list): for event in events: stamp = event['Idx']/250 #spectrogram axis is in reality from 0 to 1, and the columns are time based, not sample # type = event['Type'] #--> for label if stamp <= emax: dpg.add_inf_line_series(x=[stamp],parent=y_axis) b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', f"{header_tag}_plot", y_axis,0),width=-1,parent=header_tag) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,f"{header_tag}_plot",'2D'),parent=header_tag) add_exporting_tag(window_id,plot=b1,signal=b2) with dpg.group(horizontal=False,tag=f"{group_tag}_buttons"): dpg.add_checkbox(label='Synchronize Axis', callback=link) dpg.add_button(label='Band Power over Time', callback=merge_wavelet) dpg.add_separator() colormap_buttons(group_tag, (global_min, global_max), subplot_tag, wavelet_tags, group_tag=f"{group_tag}_buttons")
[docs]def plot_pac(tags,tab_tag,parameter_values,mode=None): ''' Computes and plots Phase-Amplitude Coupling, for the selected streams and bands. :param tags: Description :param tab_tag: Description :param parameter_values: Description :param mode: Description ''' window_id = extract_window_id(tab_tag) labels = [dpg.get_item_label(tag) for tag in tags] signals = [dpg.get_value(tag)[:2] for tag in tags] bands = getBandsByTab(tab_tag) if mode: #if default tab_label = f"{tab_tag}_df_PAC" else: tab_label = f"{tab_tag}_PAC" def plot(): for b in ['s','b']: for s in ['1','2']: label = dpg.get_value(f"{tab_label}_{b}{s}") signal = {} if b == 's': idx = labels.index(label) signal = getSignalbyIdx(window_id,tab_tag,tags[idx]) temp_signal = signals[idx] signal['X'] , signal['Y'] = temp_signal parameter_values[f'{b}{s}'] = signal['Y'] if s == '1': signal1 = signal else: signal2 = signal else: parameter_values[f'{b}{s}'] = bands[label] #1st: Filter signals if len(parameter_values['s1']) != len(parameter_values['s2']): t = dpg.add_text('Signals with different lengths!',parent=tab_label) dpg.bind_item_theme(t,change_text_color(1)) return for s in ['1','2']: band = parameter_values[f'b{s}'] param_filter = {'N': '4', 'Wn': band,'btype':'band','fs': '250'} args = pre.call_function('butter', param_filter) filtered = pre.call_function2('filtfilt', *args, parameter_values[f's{s}']) param_hilbert = {'x': filtered} hilbert = pre.call_function('hilbert',param_hilbert) if s == '1': phase_data = np.angle(hilbert) else: amp_data = np.abs(hilbert) #4th: create bins if 'n_bins' in parameter_values.keys(): n_bins = parameter_values['n_bins'] if n_bins =='' or n_bins==' ': n_bins = 18 else: n_bins = int(n_bins) else: n_bins = 18 edges = np.linspace(-np.pi, np.pi, n_bins+1) centers = (edges[:-1] + edges[1:]) / 2 digitized = np.digitize(phase_data, edges) - 1 #assign each time point to each bin (phase) amp_means = np.zeros(n_bins) for i in range(n_bins): sel = amp_data[digitized==i] amp_means[i] = np.mean(sel) if sel.size>0 else 0 #get the mean amp from the binned values # Normaliza amp_dist = amp_means / np.sum(amp_means + 1e-16) entropy = -np.sum(amp_dist * np.log(amp_dist + 1e-16)) #shannon entropy mi = np.round((np.log(n_bins) - entropy) / np.log(n_bins),5) #modulation index if dpg.does_item_exist(f"{tab_label}_pac"): dpg.delete_item(f"{tab_label}_pac") with dpg.group(tag=f"{tab_label}_pac", parent=tab_label): dpg.add_separator() dpg.add_text(f"Phase Signal: {dpg.get_value(f'{tab_label}_s1')} | Phase Band: {parameter_values['b1']}") dpg.add_text(f"Amplitude Signal: {dpg.get_value(f'{tab_label}_s2')} | Phase Band: {parameter_values['b2']}") dpg.add_text('Modulation Index (MI): 0 = NO coupling | 1 = STRONG coupling ') plot_tag = f"{tab_label}_plot" with dpg.plot(label=f"Phase-Amplitude Coupling (MI = {mi})", height=300, width=-1,tag=plot_tag): dpg.add_plot_legend() x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Phase (rad)") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Amplitude (microV)") phase = centers.tolist() amp = amp_means.tolist() dpg.add_bar_series(phase,amp,parent=y_axis) dpg.add_separator(label='Export') b1 = dpg.add_button(label = 'Export Plot Figure', callback = filename_window,user_data =('Bar', plot_tag, y_axis,0),width=-1) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,plot_tag,'PAC')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2) peak_a = max(amp) peak_p = phase[amp.index(peak_a)] bandwidth = 360/n_bins peak_d = peak_p*180/np.pi peak_d = [np.round(peak_d-bandwidth/2,2),np.round(peak_d+bandwidth/2,2)] info = {'Modulation of Index': str(mi), 'Peak Phase (Rad)':peak_p, 'Peak Band (Degrees)': peak_d ,'Peak Amplitude': peak_a} if not dpg.does_item_exist(f"{tab_label}_header"): dpg.add_collapsing_header(label='Metrics', parent=tab_label, tag=f"{tab_label}_header") create_table(f"{tab_label}_header",f"{tab_label}_table",info,trial_label=f"{signal1['Channel']} - {signal2['Channel']}") if mode: combos = {} with dpg.group(horizontal=True,parent=tab_label): for i,s in enumerate(['Signal 1','Signal 2']): input_id = dpg.add_combo( label=s, items=labels, # <-- customize your options default_value='---', # callback=different_combo, # user_data=(tab_label, combos), tag = f"{tab_label}_s{i+1}", width=200 ) combos[s] = input_id with dpg.group(horizontal=True,parent=tab_label): bands_labels = list(getBandsByTab(tab_tag).keys()) for i,s in enumerate(['Band 1','Band 2']): input_id = dpg.add_combo( label=s, items=bands_labels, # <-- customize your options default_value='---', callback=different_combo, user_data=(tab_label, combos), tag = f"{tab_label}_b{i+1}", width=200 ) combos[s] = input_id dpg.add_button(label='Plot Phase-Amplitude Coupling', callback=plot,width=-1, parent=tab_label) else: plot()
[docs]def update_table(table_tag, unit): '''Update the table by replacing values with their dB equivalents. :param table_tag: :param type: str, 'dB' or else ''' if not dpg.does_item_exist(table_tag): print(f"Table {table_tag} does not exist!") return children = dpg.get_item_children(table_tag, slot=1) # Get table rows columns = [dpg.get_item_label(c) for c in dpg.get_item_children(table_tag)[0]] if not children: print(f"No rows found in {table_tag}.") return for row_id in children: # Iterate through rows row_children = dpg.get_item_children(row_id, slot=1) # Get row items for i in range(1, len(row_children)): # Skip first column (Trial) flag = False if '%' in columns[i]: continue elif 'freq' in columns[i]: flag=True tabled = dpg.get_value(row_children[i]) value = eval(tabled) if unit == 'dB': db_value = pre.convert_to_db(value) else: db_value = pre.convert_from_db(value) if isinstance(db_value, list) and not flag: # List case # db_value = [db_value[0], abs(db_value[1])] dpg.set_value(row_children[i], str(db_value)) elif isinstance(db_value, tuple) or flag: # Peak case (power, frequency) dpg.set_value(row_children[i], f"({db_value[0]}, {value[1]})") else: # Single number case dpg.set_value(row_children[i], f"{db_value:.2f}")
[docs]def before_vs_after(sender,app_data,user_data): ''' Callback plot_featured_signal, for previosly segmented recordings ''' signal_tags,tab_label,func,tab_tag,parameter_values = user_data plot_featured_signal(signal_tags,tab_label,func,tab_tag,parameter_values)
[docs]def plot_streaming_stimulation(sender,app_data,user_data): #toggle will get the child of the second y axis, if it exists tab_tag, stimulation_list, plot_tag = user_data axis = dpg.get_item_children(plot_tag)[1] #create arrays if app_data: mint, maxt = 0,0 x_axis, y_axis = axis[0], axis[1] # Groups = list(np.array([stim[0] for stim in stimulation_list if len(stim)>1]).flatten()) Right = {day: value for stim in stimulation_list if len(stim)>1 for day, value in stim[1].items()} Left = {day: value for stim in stimulation_list if len(stim)>1 for day, value in stim[2].items()} Time = {day: value for stim in stimulation_list if len(stim)>1 for day, value in stim[3].items()} days = [utl.extract_time(utl.parse_datetime(day)) for day in Right.keys()] if len(np.unique(days))>1: # convert to UTC for day, t in Time.items(): Time[day] = [(timedelta(seconds=timestamp) + utl.parse_datetime(day)).timestamp() for timestamp in t] dpg.configure_item(x_axis,scale=dpg.mvPlotScale_Time,label='Time (UTC)') for sdx, series in enumerate(dpg.get_item_children(y_axis)[1]): x = dpg.get_value(series)[0] label = dpg.get_item_label(series) dt = utl.parse_datetime(label[label.find('(')+1:label.find(')')]) x = [(timedelta(seconds=xx) + dt).timestamp() for xx in x] dpg.configure_item(series,x=x) if sdx==0: mint = min(x) else: mint = min(mint,min(x)) maxt = max(maxt,max(x)) if not dpg.does_item_exist(f"{tab_tag}_stim_axis"): dpg.add_plot_axis(dpg.mvYAxis2,opposite=True,parent=plot_tag,label='Stimulation Amplitude (mA)',tag=f"{tab_tag}_stim_axis") for rec, stim in Right.items(): r = dpg.add_line_series(Time[rec],stim,parent=f"{tab_tag}_stim_axis",tag=f"{tab_tag}_{rec}_right") l = dpg.add_line_series(Time[rec],Left[rec],parent=f"{tab_tag}_stim_axis",tag=f"{tab_tag}_{rec}_left") dpg.bind_item_theme(r,change_line_color_and_thickness(-1,4)) dpg.bind_item_theme(l,change_line_color_and_thickness(-3,4)) else: dpg.configure_item(f"{tab_tag}_stim_axis",show=True) elif not app_data and dpg.does_item_exist(f"{tab_tag}_stim_axis"): mint, maxt = 0,0 dpg.configure_item(f"{tab_tag}_stim_axis",show=False) x_axis, y_axis = axis[0], axis[1] for sdx, series in enumerate(dpg.get_item_children(y_axis)[1]): v = dpg.get_value(series) x,y=v[0],v[1] label = dpg.get_item_label(series) new_x = [xx-x[0] for xx in x] dpg.configure_item(series,x=new_x) if sdx==0: mint = min(x) else: mint = min(mint,min(x)) maxt = max(maxt,max(x)) dpg.configure_item(x_axis,scale=dpg.mvPlotScale_Linear,label='Time (s)') dpg.set_axis_limits(x_axis, mint, maxt) def reset_axis(): dpg.set_axis_limits_auto(x_axis) dpg.set_axis_limits_auto(y_axis) dpg.set_frame_callback(1, reset_axis)
#------------GUI PRETTY-----------------
[docs]def design(): ''' Defines theme of GUI ''' def add(parent): dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 8, parent=parent) dpg.add_theme_style(dpg.mvStyleVar_WindowRounding,10, parent=parent) dpg.add_theme_style(dpg.mvStyleVar_WindowTitleAlign,0.5, parent=parent) with dpg.theme() as theme: add(dpg.add_theme_component(dpg.mvAll)) with dpg.theme_component(dpg.mvCheckbox): dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 0) return theme
[docs]def change_button_color(color_id=4): ''' Creates and returns theme to be binded to button :param color_id: int/float, index of list colors ''' with dpg.theme() as theme: with dpg.theme_component(dpg.mvButton): dpg.add_theme_color(dpg.mvThemeCol_Button, colors[color_id]) return theme
[docs]def create_alpha_theme(alpha=0.5,color=None): ''' Create and return a DearPyGui plot alpha theme, to be binded to line series. :param alpha (float): Opacity value between 0.0 and 1.0 :param color (list): rbga values of line (for cases of dpg.add_line_series, that are not filled) Returns: int: The theme ID ''' if color: color[-1] = alpha color = [int(c*255) for c in color] with dpg.theme() as alpha_theme: with dpg.theme_component(dpg.mvAll): if color: dpg.add_theme_color(dpg.mvPlotCol_Line,color,category=dpg.mvThemeCat_Plots ) else: dpg.add_theme_style(dpg.mvPlotStyleVar_FillAlpha, alpha, category=dpg.mvThemeCat_Plots) return alpha_theme
[docs]def switch_theme(sender, app_data, user_data): ''' Binds selected theme to GUI :param user_data: theme ''' label = dpg.get_item_label(sender) if label=='Classic': dpg.bind_theme(design()) else: dpg.bind_theme(user_data)
[docs]def change_electrodes_names(sender, app_data, user_data): ''' Adds user-defined name into dictionary, so its used without app_state memory dictinary loss. Channels are to be renamed are hardcoded. ''' input_counter = [0] # Use a list to allow mutable reference this = "Change Channel Names Window" def handle_submit(sender, app_data, user_data): for i in range(user_data[0]): old_name = dpg.get_value(f"old_name_{this}_{i}") new_name = dpg.get_value(f"new_name_{this}_{i}") if new_name == '': new_name = old_name electrodes_names[old_name] = new_name for side in ['LEFT','RIGHT']: electrodes_names[f'{old_name}_{side}'] = f'{new_name}_{side}' dpg.delete_item(this) def reset(sender, app_data, user_data): electrodes_names.clear() if dpg.does_item_exist(this): dpg.delete_item(this) with dpg.window(tag=this, width=400, height=300,on_close=dpg.delete_item(this)): dpg.add_text("Change Channel Names") dpg.add_separator() channels = ['ONE_THREE','ZERO_THREE','ZERO_TWO'] for row_id, ch in enumerate(channels): input_counter[0]+=1 with dpg.group(tag=f"input_group_{row_id}", parent="input_container", horizontal=True): dpg.add_input_text(tag=f"old_name_{this}_{row_id}",default_value=ch, width=120) dpg.add_input_text(tag=f"new_name_{this}_{row_id}", label="New Name", width=120) with dpg.group(horizontal=True): dpg.add_button(label="Submit", callback=handle_submit, user_data=input_counter) dpg.add_button(label="Reset", callback=reset)
# dpg.configure_item(this,on_close=dpg.configure_item(this,show=False))
[docs]def change_window_name(sender,app_data,user_data): '''Renames window. window_id = user_data ''' window_id = user_data old_name = dpg.get_item_label(window_id) if dpg.does_item_exist(f"{window_id}_name_change"): dpg.delete_item(f"{window_id}_name_change") def handle_submit(sender,app_data,user_data): name = dpg.get_value(f"{window_id}_name") dpg.set_item_label(window_id,name) dpg.delete_item(f"{window_id}_name_change") with dpg.window(tag=f"{window_id}_name_change", autosize=True,no_collapse=True): dpg.add_text("Change Window Name") with dpg.group(horizontal=True): dpg.add_text('New name: ') dpg.add_input_text(default_value=old_name, width=120,tag=f"{window_id}_name") with dpg.group(horizontal=True): dpg.add_button(label="Submit", callback=handle_submit)
[docs]def change_event_name(sender,app_data,user_data): ''' Renames events. User must know the original events names. Only replaced in future created items. ''' this = "event_name_change" row_id = 0 if dpg.does_item_exist(this): dpg.delete_item(this) def add(sender,app_data,user_data): row_id=dpg.get_item_user_data('submit event name') +1 dpg.set_item_user_data('submit event name',row_id) with dpg.group(horizontal=True, tag=f"{this}_{row_id}",parent=f"{this}_group"): dpg.add_input_text(tag=f"old_name_{this}_{row_id}",default_value='Old Name Event', width=120) dpg.add_input_text(tag=f"new_name_{this}_{row_id}",default_value='New Name Event', width=120) def reset(sender,app_data,user_data): oldies = list(all_event_names.keys()) for o in oldies: all_event_names[o] = o dpg.delete_item(this) def handle_submit(sender,app_data,user_data): row_id=dpg.get_item_user_data(sender) for i in range(row_id+1): old_name = dpg.get_value(f"old_name_{this}_{i}") new_name = dpg.get_value(f"new_name_{this}_{i}") if new_name == '': new_name = old_name all_event_names[old_name] = new_name dpg.delete_item(this) with dpg.window(tag=this, autosize=True,no_collapse=True,on_close=dpg.delete_item(this)): dpg.add_text("Change Marked Events Name") with dpg.group(tag=f"{this}_group"): with dpg.group(horizontal=True, tag=f"{this}_{row_id}"): dpg.add_input_text(tag=f"old_name_{this}_{row_id}",default_value='Old Name Event', width=120) dpg.add_input_text(tag=f"new_name_{this}_{row_id}",default_value='New Name Event', width=120) dpg.add_button(label = '+',small=True,callback=add,tag='add') with dpg.group(): dpg.add_button(label='Submit',callback=handle_submit,user_data=row_id,tag='submit event name',small=True) dpg.add_button(label='Reset',callback=reset)
[docs]def change_inf_line_color(color_id): ''' Theme for mvInfLine :param color_id: int, index of color ''' with dpg.theme() as theme_id: with dpg.theme_component(dpg.mvInfLineSeries): dpg.add_theme_color(dpg.mvPlotCol_Line, colors[color_id], category=dpg.mvThemeCat_Plots) return theme_id
[docs]def change_line_color(color): ''' Theme for mvLineSeries :param color_id: int or list, index of color or rgba code ''' if isinstance(color,int): color = colors[color] elif isinstance(color, list): if sum(color)<3: color = [int(c*255) for c in color] with dpg.theme() as theme_id: with dpg.theme_component(dpg.mvLineSeries): dpg.add_theme_color(dpg.mvPlotCol_Line, color, category=dpg.mvThemeCat_Plots) return theme_id
[docs]def change_line_color_and_thickness(color_id,thickness): with dpg.theme() as theme_id: with dpg.theme_component(dpg.mvLineSeries): dpg.add_theme_color(dpg.mvPlotCol_Line, colors[color_id], category=dpg.mvThemeCat_Plots) dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, thickness, category=dpg.mvThemeCat_Plots) return theme_id
[docs]def change_plot_color(color_id): ''' Theme for mvScatterSeries :param color_id: int, index of color ''' if isinstance(color_id,int): color = colors[color_id] else: color = tuple([np.round(c*255) for c in color_id]) with dpg.theme() as theme_id: with dpg.theme_component(dpg.mvScatterSeries): dpg.add_theme_color(dpg.mvPlotCol_MarkerFill, color, category=dpg.mvThemeCat_Plots) dpg.add_theme_color(dpg.mvPlotCol_MarkerOutline, color, category=dpg.mvThemeCat_Plots) return theme_id
[docs]def classic_button(og=None): if og is None: og = (40, 141, 184,255) diff = (20,20,20,0) hovered = tuple(np.array(og)+np.array(diff)) active = tuple(np.array(og)-np.array(diff)) with dpg.theme() as button_theme: with dpg.theme_component(dpg.mvButton): dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 8) dpg.add_theme_color(dpg.mvThemeCol_Button, og) #(90, 110, 130) dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, hovered) dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, active) return button_theme
[docs]def change_line_thickness(thickness, component = dpg.mvLineSeries): ''' Theme for mvLineSeries thickness :param thickness: int, width of line ''' with dpg.theme() as theme_id: with dpg.theme_component(component): dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, thickness, category=dpg.mvThemeCat_Plots) return theme_id
[docs]def change_text_color(color_id=5): ''' Theme for text color :param color_id: int, index of color ''' with dpg.theme() as theme_id: with dpg.theme_component(dpg.mvText): dpg.add_theme_color(dpg.mvThemeCol_Text, colors[color_id], category=dpg.mvThemeCat_Core) return theme_id
[docs]def different_combo(sender,app_data,user_data): ''' Checks if selected recordings are different ''' if dpg.does_item_exist(f"{sender}_pac_w"):dpg.delete_item(f"{sender}_pac_w") doc_container, combos = user_data atual = dpg.get_value(sender) for key in combos.keys(): if sender != combos[key]: other = dpg.get_value(combos[key]) if atual == other: dpg.add_separator(parent=doc_container) e = dpg.add_text('Recordings cannot be the same!', parent=doc_container, show=True, tag = f"{sender}_pac_w") dpg.bind_item_theme(e,change_text_color(1)) return
[docs]def resize_font(sender,app_data,user_data): ''' Resizes font of all items ''' window_id = user_data this = f"{window_id}_font_size" if dpg.does_item_exist(this): dpg.delete_item(this) def set_size(sender,app_data,user_data): value = dpg.get_value(sender) set_font(size=value) def reset(sender,app_data,user_data): set_font() dpg.set_value(user_data,16) with dpg.window(label="Resize Font",tag=this, autosize=True,no_collapse=True): dpg.add_text("Resize Font") with dpg.group(horizontal=True,width=90): dv = int(dpg.get_item_configuration("CURRENT_FONT").get('size')) slider=dpg.add_slider_int(default_value=dv,min_value=8,max_value=25,callback=set_size,width=80) dpg.add_button(label='Reset',callback= reset,small=True,user_data=slider)
[docs]def set_font(size=16,just_theme=False): ''' Redefines actual theme to support function resize_font :param size: int ''' # delete previous font if it exists if dpg.does_item_exist("CURRENT_FONT"): dpg.delete_item("CURRENT_FONT") font_path = utl.resource_path("fonts", "SF-Pro-Display-Semibold.otf") exist = os.path.exists(font_path) if not exist: return # add new font to existing registry default_font = dpg.add_font( font_path, size, parent="FONT_REGISTRY", tag="CURRENT_FONT" ) if just_theme: default_font = dpg.add_font( font_path, size, parent="FONT_REGISTRY", ) return default_font try: dpg.bind_font(default_font) except Exception as e: print(e)
[docs]def horizontal_scroll(sender,app_data,user_data): wid = user_data if 'Activate' in dpg.get_item_label(sender): flag = True label = 'Deactivate Horizontal Scrollbar' else: flag = False label = 'Activate Horizontal Scrollbar' dpg.configure_item(wid,horizontal_scrollbar=flag) dpg.configure_item(sender,label=label)
#--- Extract signal/identifiers
[docs]def extract_idx_from_series_tag(series_tag: str) -> int: ''' Gets id of series/recording, from tag of series plot. :param series_tag: str, series id :return: int, number of series (in app_state memory dictinary) ''' if isinstance(series_tag,int):return series_tag match = re.search(r'_series_(\d+)(?:_|$)', series_tag) # Match _series_<number> with or without trailing underscore if match: return int(match.group(1)) # Extract and return the idx as an integer else: raise ValueError(f"Invalid series_tag format: {series_tag}")
[docs]def extract_side_from_series_tag(series_tag: str) -> int: ''' Gets hemisphere of series/recording, from tag of series plot. :param series_tag: str, series id :return: int, left=0, right=1 ''' match = re.search(r'_(left|right)$', series_tag.lower()) # Match 'left' or 'right' at the end if match: return match.group(1) else: raise ValueError("Invalid series_tag format or missing side")
[docs]def extract_file_from_series_tag(series_tag: str): ''' Gets file of series/recording, from tag of series plot. :param series_tag: str, series id :return: int, file id (in app_state memory dictinary) ''' match = re.search(r'_file_(\d+)_series_', series_tag) # Ensure only digits return int(match.group(1)) if match else None # Return integer or None
[docs]def extract_edx_from_series_tag(series_tag: str) -> str: #unused, past usages could be correctly done with extract_idx_from_series_tag ''' Gets id of event, from tag of series plot. :param series_tag: str, series id :return: int, event id (in app_state memory dictinary) ''' if series_tag.split('_')[-1] in ['left','right']: return series_tag.split('_')[-2] return series_tag.split('_')[-1]
[docs]def extract_event_from_series_tag(event_string): ''' Gets name of event, from tag of series plot. :param series_tag: str, series id (ex.: tab_Streaming_0_series_0) :return: str, event name (ex.: series_0) ''' try: parts = event_string.split('_') except: return None # Skip the first three (e.g., tab, Streaming, 0), then look for the next non-number for i in range(3, len(parts), 2): # step by 2 to skip the numbers if not parts[i].isdigit(): return parts[i] return None
[docs]def extract_channel_from_series_tag(series_tag: str) -> str: ''' Gets channel, without hemisphere, of series/recording, from channel (ex.: 'ONE_THREE_LEFT' --> 'ONE_THREE'). :param series_tag: str, series id :return: str, channel name ''' match = re.search(r'^(.*?)(?=_(left|right))', series_tag.lower()) # Look for '_left' or '_right' without including them if match: return match.group(1) # Return the part before '_left' or '_right' else: return series_tag
[docs]def extract_window_id(tab_tag: str) -> int: ''' Extracts window_id as an integer from a tab_tag formatted as 'tab_{mode}_{window_id}' or 'tab_{mode}_{window_id}_{suffix}'. :param tab_tag: Description ''' match = re.match(r"tab_[^_]+_(\d+)(?:_.+)?$", tab_tag) # Capture window_id, ignore optional suffix if match: return int(match.group(1)) # Convert extracted value to an integer else: try: cut = tab_tag.find('_')+1 extract_window_id(tab_tag[cut:]) except Exception as e: raise ValueError(f"Invalid tab_tag format: {tab_tag}")
[docs]def extract_type_window(window_id): ''' Gets type of window: 'Individual' or 'Combined' return str :param window_id: Description ''' if app_state['windows'][window_id]['Files'] == []: return None if 'Data' in app_state['windows'][window_id].keys(): return 'Individual' else: return 'Combined'
[docs]def extract_mode(tab_tag: str) -> str: ''' Extracts the mode from a tab_tag formatted as 'tab_{mode}_{window_id}' or 'tab_{mode}_{window_id}_{suffix}'. Return str :param tab_tag: Description ''' match = re.match(r"tab_([^_]+)_\d+(?:_.+)?$", tab_tag) # Capture mode, ignore optional suffix if match: return match.group(1) # Return the extracted mode else: raise ValueError(f"Invalid tab_tag format: {tab_tag}")
[docs]def extract_channel_hemisphere(label): ''' Parses a label like 'ZERO_THREE_LEFT' into channel and hemisphere. Returns: channel (str): The channel with underscores (e.g. 'zero_three') hemisphere (str): The hemisphere (e.g. 'left') :param label: str, channel_hemisphere = 'ZERO_THREE_LEFT' ''' parts = label.split('_') if len(parts) < 2: raise ValueError("Label format is invalid. Expected format: CHANNEL_HEMISPHERE") hemisphere = parts[-1] channel = '_'.join(parts[:-1]) return channel, hemisphere
[docs]def getActiveSeries(signal_tags): ''' From signal's id, returns only a list of the visible tags. :param signal_tags: dict/list, if dict the tags are keys ''' active_tags = [] if type(signal_tags) == dict: tags = signal_tags.keys() elif type(signal_tags) == list: tags = signal_tags.copy() for tag in tags: info = dpg.get_item_configuration(tag) if info['show'] == True: active_tags.append(tag) keywords = ['series', 'file', 'OFF', 'ON', 'ARTIFACTS', 'psd'] active_tags = [tag for tag in active_tags if extract_event_from_series_tag(tag) in keywords] return active_tags
[docs]def getActiveSignals(tab_tag,signal_tags): ''' From Active Series, returns the data of the series in app_state memory dictinary. :param tab_tag: Description :param signal_tags: Description ''' window_id = extract_window_id(tab_tag) active_series = getActiveSeries(signal_tags) signals = [] for serie in active_series: try: signal = getSignalbyIdx(window_id, tab_tag, serie) if signal is None: # If `getSignalbyIdx` returns None signal = getSignalbyIdxSide(window_id, tab_tag, serie) except KeyError as e: # Catch missing key issues signal = getSignalbyIdxSide(window_id, tab_tag, serie) except Exception as e: # Catch any other unexpected error print(f"Unexpected error for {tab_tag}, {serie}: {e}") signal = None # Handle gracefully signals.append(signal) return signals
[docs]def getSignalbyIdx(window_id,tab_tag,series_tag,mode=None): ''' Gets signal data, from app_state memory dictinary, from series_tag :param window_id: Description :param tab_tag: Description :param series_tag: Description :param mode: Description ''' idx = extract_idx_from_series_tag(series_tag) file = extract_file_from_series_tag(series_tag) edx = int(extract_edx_from_series_tag(series_tag)) # edx = extract_idx_from_series_tag(series_tag) wt = extract_type_window(window_id) if wt == 'Combined': #Combined window if mode == None: #signal and not events return app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Signals'][idx] else: #events return app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Events'][edx] else: #Individual - always signal return app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx]
[docs]def getSignalbyIdxSide(window_id,tab_tag,series_tag): ''' Gets signal data, for Events, from series_tag. :param window_id: Description :param tab_tag: Description :param series_tag: Description ''' idx = extract_idx_from_series_tag(series_tag) side = extract_side_from_series_tag(series_tag) wt = extract_type_window(window_id) file = extract_file_from_series_tag(series_tag) if wt == 'Individual': return app_state['windows'][window_id]['Data'][tab_tag]['Signals'][side][idx] else: return app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Signals'][idx][side]
[docs]def getEventsbyIdx(tab_tag,idx): ''' Gets Events in signal idx, by id of signal :param tab_tag: Description :param idx: int, signal id in app_state memory dictinary ''' window_id = extract_window_id(tab_tag) dictionary = {} if extract_type_window(window_id) == 'Combined': dictionary = app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx] if 'Events' in dictionary.keys(): return dictionary['Events'] else: return None
[docs]def getEventsbyTag(tab_tag,series_tag): ''' Gets Events in signal idx, by series tag :param tab_tag: Description :param series_tag: Description ''' window_id = extract_window_id(tab_tag) idx = extract_idx_from_series_tag(series_tag) file = extract_file_from_series_tag(series_tag) if extract_type_window(window_id) == 'Combined': dictionary= app_state['windows'][window_id]['Files'][file]['Data'][tab_tag] else: dictionary = app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx] if 'Events' in dictionary.keys(): if dictionary['Events'] == []: return None return dictionary['Events'] else: return None
[docs]def getBandsByTab(tab_tag): ''' Gets frequency bands (used for spectral analysis), associated to window or tab. :param tab_tag: Description ''' window_id = extract_window_id(tab_tag) return app_state['windows'][window_id]['Bands']
[docs]def getUpdatedEventName(event): ''' Gets user-defined event name, from original name defined in json file. :param event: str, original name ''' if event not in all_event_names.keys(): all_event_names[event] = event return all_event_names[event]
[docs]def getUpdatedElectrodeName(electrode): ''' Gets user-defined electrode name, from original electrode name defined in json file. :param electrode: str, original name ''' electrode = electrode.upper().replace('_AND_','_') if electrode not in electrodes_names.keys(): electrodes_names[electrode] = electrode return electrodes_names[electrode]
[docs]def getDatabyTag(series_tag): ''' Return Data dicitionary (from json file), from series tag :param series_tag: Description ''' window_id = extract_window_id(series_tag) wt = extract_type_window(window_id) if wt == 'Combined': file = extract_file_from_series_tag(series_tag) return app_state['windows'][window_id]['Files'][file]['Data'] else: return app_state['windows'][window_id]['Data']
[docs]def getDeviation(tab_tag): dev = 1 if dpg.does_item_exist(f"{tab_tag}_deviation"): name = dpg.get_value(f"{tab_tag}_deviation") if '95%' in name: dev = 2 return dev
[docs]def getDatabyFile(window_id, file): ''' Return Data dicitionary (from json file), from file id in app_state memory dictinary :param window_id: Description :param file: Description ''' wt = extract_type_window(window_id) if wt == 'Combined': return app_state['windows'][window_id]['Files'][file]['Data'] else: return app_state['windows'][window_id]['Data']
[docs]def get_limits(children,plot_tag): ''' Returns absolute axis limits, and indexes to create mask on signals and return only the visible parts of the signals. :param children: list, signals tags id (children of y axis) :param plot_tag: str/id, plot id (parent of axis) ''' axis = dpg.get_item_children(plot_tag)[1][0] limits = dpg.get_axis_limits(axis) xx = [dpg.get_value(tag)[0] for tag in children] lengths = [len(x) for x in xx] maxX = xx[lengths.index(max(lengths))] #get the longest array mini = np.abs(np.array(maxX) - limits[0]).argmin() # closest index maxi = np.abs(np.array(maxX) - limits[1]).argmin() tmin = min(mini,maxi) tmax = max(mini,maxi) return tmin,tmax,maxX
[docs]def updateKeySignalbyIdx(value,tab_tag,series_tag, key): #check need of this ''' Updates app_state memory dictionary with processed signal (cut or new events) :param value: list, new signal :param tab_tag: Description :param series_tag: Description :param key: str, key onto the value will be updated, must exist ''' window_id = extract_window_id(tab_tag) idx = extract_idx_from_series_tag(series_tag) file = extract_file_from_series_tag(series_tag) if file == None: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx][key] = value else: app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Signals'][idx][key] = value
[docs]def updateSignalbyIdx(value,tab_tag,series_tag, func = None, mode = None, new_tab_tag = None): #check need of this ''' Updates signal after filtering and saves wavelet into app_state memory. :param value: list, new signal :param tab_tag: Description :param series_tag: Description :param func: Description :param mode: Description :param new_tab_tag: Description ''' window_id = extract_window_id(tab_tag) idx = extract_idx_from_series_tag(series_tag) file = extract_file_from_series_tag(series_tag) edx = int(extract_edx_from_series_tag(series_tag)) # edx = extract_idx_from_series_tag(series_tag) wt = extract_type_window(window_id) if new_tab_tag: tab_tag=new_tab_tag if mode == None: if file == None: if func != None: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx][func] = value else: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx]['Y'] = value else: if func != None: app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Signals'][idx][func] = value else: app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Signals'][idx]['Y'] = value else: if wt == 'Combined': if func != None: app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Events'][edx][func] = value else: app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Events'][edx]['Y'] = value else: if func != None: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx]['Events'][edx][func] = value else: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx]['Events'][edx]['Y'] = value
[docs]def enforce_min_max(sender, app_data, user_data): '''Ensure max is always at least diff units greater than min, and min is never below 0. sender: min_tag or max_tag min_tag, max_tag, diff = user_data ''' min_tag, max_tag, diff = user_data # Extract tag references min_value = max(0, dpg.get_value(min_tag)) # Ensure min is at least 0 max_value = absolute_max = dpg.get_value(max_tag) if sender == min_tag: if min_value >= max_value - diff: # If min is too close to max max_value = min_value + diff dpg.set_value(max_tag, max_value) # Ensure max stays 10+ units above dpg.set_value(min_tag, min_value) # Set min (ensuring it's 0 or higher) elif sender == max_tag: if max_value < diff: # Prevent max from being below 10 min_value = 0 max_value = diff elif max_value <= min_value + diff: # Ensure min stays 10 below max min_value = max_value - diff elif max_value > absolute_max: max_value = absolute_max dpg.set_value(min_tag, min_value) dpg.set_value(max_tag, max_value)
[docs]def enforce_range(sender, app_data, user_data): ''' Enforces range of colormap, diff of 5 units. :param sender: Description :param app_data: Description :param user_data: tab_label (parent of sender), absolute_min (int/float), absolute_max (int/float) ''' tab_label, absolute_min, absolute_max = user_data min_tag, max_tag = f"{tab_label}_emin", f"{tab_label}_emax" diff = 5 min_value = dpg.get_value(min_tag) max_value = dpg.get_value(max_tag) min_value = max(min_value, absolute_min) max_value = min(max_value, absolute_max) if max_value - min_value < diff: if sender == min_tag: max_value = min(min_value + diff, absolute_max) min_value = max(max_value - diff, absolute_min) else: # sender == max_tag min_value = max(max_value - diff, absolute_min) max_value = min(min_value + diff, absolute_max) # Set adjusted values back dpg.set_value(min_tag, min_value) dpg.set_value(max_tag, max_value)
[docs]def extract_cc_features(p, y_axis, tab_label ,mode = None) -> dict: ''' Extracts features of computed cross-correlation, plots respective features and returns features in a dictionary: info. info = { 'RMS Correlation': rms, 'Max Correlation': max_value, 'Lag til Max (s)': lag, 'Half Max Width (s)': width, 'Peak Frequency (mean, std)': [freq_mean,freq_std] } :param p: dict, computed signal dictionary :param y_axis: str/id, parent for plot :param tab_label: str/id, parent of plot, adding table of features :param mode: srt, default to None --> performs general cc; band name str --> performs band-specific cc :return: [info, tags], info --> features dictionary; tags --> list of id's of added feature plots :rtype: dict, list ''' tags = [] max_value = max(p['Y']) idx = p['Y'].index(max_value) lag = p['X'][idx] half = np.round(max_value/2,2) temp = [np.round(y,2) for y in p['Y']] start, inf, hlf = None, None, None Y, X = p['Y'], p['X'] for i in range(idx, 0, -1): if Y[i] >= half and Y[i-1] < half: # Linear interpolation to find exact crossing point x1, x2 = X[i-1], X[i] y1, y2 = Y[i-1], Y[i] start = x1 + (half - y1) * (x2 - x1) / (y2 - y1) break # 3. Search to the right of the peak for first crossing below half max for i in range(idx, len(Y)-1): if Y[i] >= half and Y[i+1] < half: x1, x2 = X[i], X[i+1] y1, y2 = Y[i], Y[i+1] end = x1 + (half - y1) * (x2 - x1) / (y2 - y1) break if mode: ext = mode + ' ' else: ext = '' if start: inf = dpg.add_inf_line_series([start,end],parent=y_axis, label = ext + 'Half Max Width', tag = f"{tab_label}_{ext}_HMW") tags.append(f"{tab_label}_{ext}_HMW") if half: hlf = dpg.add_inf_line_series([half],parent=y_axis,horizontal=True,label= ext +'Half Max', tag = f"{tab_label}_{ext}_HW") tags.append(f"{tab_label}_{ext}_HW") # dpg.add_line_series(p['X'], envelope.tolist(),label=f"{ext} Envelope", parent=y_axis,tag=f"{tab_label}_{ext}_env") red = change_inf_line_color(0) green = change_inf_line_color(3) if inf: dpg.bind_item_theme(inf,red) if hlf: dpg.bind_item_theme(hlf,green) if start: width = np.round((end-start)/250,2) else: width = 0 parameters, _ = fill_params_features('find_peaks') parameters['x'] = p['Y'] parameters['distance'] = 125 peaks = pre.call_function('find_peaks',parameters) true_peaks = [(x-(len(p['Y'])+1)/2)/250 for x in peaks[0]] above_peaks = [p['Y'][x] for x in peaks[0]] rms = np.sqrt(np.mean(np.array(p['Y'])**2)) # mask = np.array(above_peaks) >= 3*rms mask = np.array(above_peaks) >= np.max(p['Y'])/2 true_peaks = np.array(true_peaks)[mask] if len(true_peaks)>0: dpg.add_scatter_series(true_peaks.tolist() ,np.zeros(len(true_peaks)).tolist(),parent=y_axis,label = ext +'Peaks', tag = f"{tab_label}_{ext}_peaks") tags.append(f"{tab_label}_{ext}_peaks") diff = np.diff(true_peaks) if diff.size == 0: diff = [1] freq_mean = np.round(1/np.mean(diff),2) freq_std = np.std(diff) info = { 'RMS Correlation': rms, 'Lag til Max (s)': lag, 'Half Max Width (s)': width, 'Frequency of Peaks (mean, std)': [freq_mean,freq_std] } return info, tags
[docs]def extract_artifact_type(s: str): ''' Extracts type of artifact from string. :param s: str, ex.: CT_ARTIFACT_PRESENT ''' if s == "ARTIFACT_NOT_PRESENT": return 'Not' if s.endswith("_ARTIFACT_PRESENT"): return s.replace("_ARTIFACT_PRESENT", "") return None # fallback for unexpected format
#--- Process with signal
[docs]def Volt2dB(sender,app_data,user_data): ''' Callback of checkbox - changes PSD plot between original units to dB. y_axis: str/id, parent of plots (axis y) plot_tag: str/id, parent of axis signal_tags: list, list of tags to be converted, if they're shown tables: list, list of tags of tables to be converted :param sender: Description :param app_data: boolean, type of units (original/dB) :param user_data: y_axis, plot_tag, signal_tags, tables ''' unit = app_data if unit == True: unit = 'dB' label = unit else: unit = 'Y' label = 'µV^2/Hz' y_axis, plot_tag, signal_tags, tables = user_data dpg.set_item_label(y_axis,label=label) all_values, visible = [], 0 for tag in signal_tags: label = dpg.get_item_label(tag) if dpg.get_item_configuration(tag)['show']==True: visible = +1 _,y = dpg.get_value(tag)[:2] if app_data: y = pre.convert_to_db(y) else: y = pre.convert_from_db(y) dpg.configure_item(tag,y=y) all_values.append(y) if visible == 0: std_tag,mean_tag = dpg.get_item_children(y_axis)[1][-2:] mean = dpg.get_value(mean_tag)[1] std = dpg.get_value(std_tag)[1] std = np.array(std) + np.array(mean) if app_data: mean = pre.convert_to_db(mean) std = pre.convert_to_db(std.tolist()) else: mean = pre.convert_from_db(mean) std = pre.convert_from_db(std.tolist()) mean = np.array(mean) std = np.array(std) else: mean = np.nanmean(all_values,axis=0) std = np.nanstd(all_values,axis=0) dev = getDeviation(plot_tag) minus, plus = mean-dev*std, mean+dev*std dpg.configure_item(f"{plot_tag}_std", y1=minus,y2=plus) dpg.bind_item_theme(f"{plot_tag}_std",create_alpha_theme()) dpg.configure_item(f"{plot_tag}_mean", y=mean) dpg.bind_item_theme(f"{plot_tag}_mean", change_line_thickness(5)) time.sleep(0.1) for table in np.unique(tables).tolist(): update_table(table,unit)
[docs]def getMeanActiveSeries(window_id,tab_tag,signal_tags,key='Y'): #try to use it in more places ''' Calculates mean and standard deviation from the active signals in plot. :param window_id: str/id :param tab_tag: Description :param signal_tags: Description :param dev: default to None, standar deviation*1, else gets value of radio button to get type of standard deviation ''' if 'psd' in tab_tag: key = 'LFPMagnitude' active_tags = getActiveSeries(signal_tags) active_signals = [] mode = extract_mode(tab_tag) for tag in active_tags: if mode =='Events': active = getSignalbyIdxSide(window_id,tab_tag,tag) else: active = getSignalbyIdx(window_id,tab_tag,tag) active_signals.append(active) if len(active_signals)>0: mean_power = pre.MeanSignal(active_signals,key) std_power = pre.StdDevSignal(active_signals,key) dev = getDeviation(tab_tag) std_power = dev*std_power minus, plus = mean_power-std_power, mean_power+std_power if dpg.does_item_exist(f"{tab_tag}_2db") and dpg.get_value(f"{tab_tag}_2db"): mean_power = np.array([pre.convert_to_db(m) for m in mean_power]) minus = np.array([pre.convert_to_db(m) for m in minus]) plus = np.array([pre.convert_to_db(m) for m in plus]) else: return 0,0 return mean_power, minus, plus
[docs]def segment_signal(tab_tag, signal_tags,plot_tag): ''' Adds buttons to perform segmentation of time-domain signals :param tab_tag: Description :param signal_tags: Description :param plot_tag: Description ''' with dpg.theme() as theme: with dpg.theme_component(dpg.mvAll): dpg.add_theme_style(dpg.mvStyleVar_SeparatorTextAlign,1) with dpg.group(tag = f"{tab_tag}_segmentation"): time.sleep(0.1) s = dpg.add_separator(label='SEGMENTATION: minimum of 10s') dpg.bind_item_theme(s,theme) mmax = max([dpg.get_value(tag)[0][-1] for tag in signal_tags]) with dpg.group(horizontal=True): dpg.add_input_int(label='Start', tag=f"{tab_tag}_min", width=90, min_value=0,default_value=0, callback=enforce_min_max, user_data=(f"{tab_tag}_min", f"{tab_tag}_max", 10)) dpg.add_input_int(label='End', tag=f"{tab_tag}_max", width=90,max_value=mmax, default_value=mmax, callback=enforce_min_max, user_data=(f"{tab_tag}_min", f"{tab_tag}_max", 10)) dpg.add_button(label='Segment Signal', show=True, callback=cut_signals,user_data=(tab_tag, signal_tags))
[docs]def cut_signals(sender,app_data,user_data): ''' Cuts and saves time-domain signals. tab_tag: Description signal_tags: Description :param sender: Description :param app_data: Description :param user_data: tab_tag, signal_tags ''' tab_tag, signal_tags = user_data window_id = extract_window_id(tab_tag) active_series = getActiveSeries(signal_tags) min_value = dpg.get_value(f"{tab_tag}_min") max_value = dpg.get_value(f"{tab_tag}_max") min_idx = min_value*250 max_idx = max_value*250 dpg.set_axis_limits(axis=f"x_axis_{tab_tag}", ymin=min_value, ymax=max_value) for serie in active_series: signal = getSignalbyIdx(window_id,tab_tag,serie) new_x = signal['X'][min_idx:max_idx] new_y = signal['Y'][min_idx:max_idx] updateKeySignalbyIdx(new_x,tab_tag,serie,'X') updateKeySignalbyIdx(new_y,tab_tag,serie,'Y') time.sleep(0.1) update_plot_td(tab_tag,signal_tags,' Segment')
[docs]def interval_events(tab_tag, label1, label2,mini=15,maxi=15): ''' Adds buttons to get event-locked segmentation interval. :param tab_tag: Description :param label1: str, label of input 1 :param label2: str, label of input 2 :param mini: default to 15s, value of before interval (s) :param maxi: default to 15s, value of after interval (s) ''' min_dif = 2 with dpg.group(horizontal=True): dpg.add_input_int(label=label1, tag=f"{tab_tag}_emin", width=90, default_value=mini, callback=enforce_min_max, user_data=(f"{tab_tag}_emin", f"{tab_tag}_emax", min_dif)) dpg.add_input_int(label=label2, tag=f"{tab_tag}_emax", width=90, default_value=maxi, callback=enforce_min_max, user_data=(f"{tab_tag}_emin", f"{tab_tag}_emax", min_dif))
[docs]def DefaultFilter(sender,app_data,user_data): ''' Standard filtration = 5th order Butterworth bandpass filter 1-100 Hz + Removal of cardiac component Remove cardiac component - Fixed peak finder method - BRAVO adapted tab_tag, signal_tags = user_data tab_tag: Description signal_tags: Description ''' tab_tag, signal_tags = user_data active_series = getActiveSeries(signal_tags) window_id = extract_window_id(tab_tag) signal_mode, plot_mode = None, None wt = extract_type_window(window_id) if wt == 'Combined': config = dpg.get_item_configuration(f"x_axis_{tab_tag}") for j in app_state['windows'][window_id]['Files']: if tab_tag in j['Data'].keys(): if isinstance(j['Data'][tab_tag], dict) and 'Events' in j['Data'][tab_tag].keys(): signal_mode = plot_mode = 'segment' if config['scale'] == 1: #if were plotting in datetime (signal w marked events) and not seconds (cutted events) signal_mode = plot_mode = None break else: signals = app_state['windows'][window_id]['Data'][tab_tag]['Signals'] for i,_ in enumerate(signals): events = getEventsbyIdx(tab_tag,i) if events != None: plot_mode = 'segment' band = dpg.get_value(item=f"{tab_tag}_default_wn")[:2] if band[0] == 0: band = [0.1,band[1]] dpg.delete_item(f"{tab_tag}_default_filter",children_only=True) dpg.add_text("3rd Order Butterworth Filter and Removal of Cardiac Component", parent=f"{tab_tag}_default_filter") else: band = list(band) dpg.delete_item(f"{tab_tag}_default_filter",children_only=True) dpg.add_text("5th Order Butterworth Filter and Removal of Cardiac Component", parent=f"{tab_tag}_default_filter") for series in active_series: signal = {} signal=getSignalbyIdx(window_id,tab_tag,series,mode=signal_mode) # signal['X'], signal['Y']=dpg.get_value(series)[:2] clean, _ = pre.removeCardiacComponent(signal, band) clean = np.ascontiguousarray(clean) updateSignalbyIdx(clean,tab_tag,series,mode=signal_mode) title = 'Default Filter' update_plot_td(tab_tag,signal_tags, title,signal_mode = plot_mode)
[docs]def CustomFilter(sender,app_data,user_data): ''' Function that handles all the process from button/input creation to filter application on the signals. tab_tag: Description signal_tags: Description :param sender: Description :param app_data: Description :param user_data: tab_tag, signal_tags ''' tab_tag, signal_tags = user_data window_id = extract_window_id(tab_tag) if not 'Data' in app_state['windows'][window_id]: if 'Events' in app_state['windows'][window_id]['Files'][-1]['Data'][tab_tag].keys(): mode = 'segment' else: mode = None else: mode = None param_container = f"{tab_tag}_filters_container" doc_container = f"{tab_tag}_filters_doc" if dpg.does_item_exist(param_container): dpg.delete_item(param_container,children_only=True) dpg.delete_item(doc_container,children_only=True) else: ch = dpg.add_tree_node(label='Custom Parameters',parent=f"{tab_tag}_filters",default_open=True, tag = f"{tab_tag}_tree_filters") with dpg.group(parent=ch,horizontal=True): dpg.add_child_window(width=200, height=300, tag = param_container) # Space to add inputs dynamically dpg.add_child_window(width=600, height=300, tag = doc_container) parameter_values = {} input_fields = {} def create_input_fields(sender, app_data): '''Dynamically create input fields for selected function.''' #global parameter_values, input_fields dpg.delete_item(param_container, children_only=True) # Clear previous inputs params = pre.get_required_params(app_data) if not params: dpg.add_text("No parameters found", parent=param_container) return dpg.add_text("Required parameters: ", parent=param_container) parameter_values.clear() # Reset parameter storage input_fields.clear() # Reset input field storage _, documentation = fill_params_features(app_data) documentation = pre.extract_params_and_returns(documentation) dpg.add_text(documentation, parent=doc_container, tag = f"{doc_container}_doc") for param, default in params.items(): if param == 'fs': default = 250 input_id = dpg.add_input_text( label=param, default_value=str(default) if default is not None else "", parent=param_container ) input_fields[param] = input_id # Store input field reference # Add save button dpg.add_button(label="Save Parameters", callback=save_parameters, parent=param_container, user_data = app_data) def save_parameters(sender, app_data, user_data): '''Saves input values to the dictionary and prints them.''' parameter_values.clear() # Reset before saving for param, input_id in input_fields.items(): values = dpg.get_value(input_id) if values is str and ',' in values: try: # Split the string at the comma and convert each part to a float values = np.array([float(val) for val in values.split(',')]) except ValueError: print(f"Invalid input for parameter {param}. Please enter valid numbers separated by commas.") return # If parsing fails, exit early parameter_values[param] = values if not dpg.does_item_exist(f"{tab_tag}_plot_filtered"): dpg.add_button(label="Plot filtered signal", callback=apply_filt, parent=param_container, user_data=user_data, tag=f"{tab_tag}_plot_filtered") def apply_filt(sender, app_data, user_data): func = user_data tags = getActiveSeries(signal_tags) args = pre.call_function(func, parameter_values) #gets the parameters of the filter for tag in tags: # if mode: # stream=getSignalbyIdx(window_id,tab_tag,tag,mode='segment') # else: stream = getSignalbyIdx(window_id,tab_tag,tag) stream=getSignalbyIdx(window_id,tab_tag,tag,mode) y = stream['Y'] filtered = pre.call_function2('filtfilt', *args, y) updateSignalbyIdx(np.ascontiguousarray(filtered),tab_tag,tag, mode=mode) snr = (10*np.log10(np.sum(np.array(y)**2)/np.sum(y-filtered)**2)) title = f'Custom Filter: {func}' update_plot_td(tab_tag,signal_tags, title,signal_mode=mode) create_input_fields(sender,app_data)
[docs]def DefaultFeatures(sender, app_data, user_data): tab_tag, signal_tags = user_data window_id = extract_window_id(tab_tag) dpg.configure_item(f"window_{window_id}", autosize=True) wt = extract_type_window(window_id) parameter_values = {} if app_data== 'multitaper coherence': parameter_values, documentation = fill_params_features('dpss') elif app_data == 'cross-correlation': parameter_values, documentation = fill_params_features('correlate') parameter_values['method'] = 'auto' elif app_data == 'PAC': parameter_values, documentation = fill_params_features('hilbert') else: parameter_values, documentation = fill_params_features(app_data) func = app_data tags = getActiveSeries(signal_tags) events = [getEventsbyTag(tab_tag,serie) for serie in tags] if all(e is None for e in events): events = None if events: mode = 'segment' else: mode = None tab_label = f"{tab_tag}_df_{func}" if not dpg.does_item_exist(f"{tab_tag}_features_bar"): dpg.add_tab_bar(reorderable=True,parent=f"{tab_tag}_features", tag = f"{tab_tag}_features_bar") if dpg.does_item_exist(tab_label): dpg.delete_item(tab_label, children_only=True) dpg.configure_item(tab_label,show=True) else: dpg.add_tab(label=f"Default {func}", parent=f"{tab_tag}_features_bar", tag=tab_label, closable=True) dates = [] for tag in tags: signal = getSignalbyIdx(window_id,tab_tag,tag,mode) dates.append(signal['Date']) dates = np.unique(dates) if len(dates)>1: plot_modes = ['Join Recordings', 'Separate Recordings'] else: plot_modes = [None] continue_flag = True if wt == 'Individual': mode = None elif mode == None and (func == 'spectrogram' or func == 'wavelet'): dpg.add_text(default_value=f"{func} only available in Combined Analysis for Event-lock based signals", show= True, parent=tab_label) continue_flag = False # dpg.add_group(parent=f"{tab_tag}_features_group", tag = f"{tab_tag}_df_group") if continue_flag == True: match func: case 'spectrogram': if plot_modes[0] != None: if not dpg.does_item_exist(f"{tab_label}_deviation"): dpg.add_radio_button(items=plot_modes,label='Plot Mode', parent=tab_label, callback=choose, user_data=(tags, tab_label, func, tab_tag, parameter_values, mode), tag = f"{tab_label}_deviation") else: plot_spectrogram_og(tags, tab_label, func, tab_tag, parameter_values, mode) case 'wavelet': if plot_modes[0] != None: if not dpg.does_item_exist(f"{tab_label}_deviation"): dpg.add_radio_button(items=plot_modes,label='Plot Mode', parent=tab_label, callback=choose, user_data=(tags, tab_label, func, tab_tag, parameter_values, mode), tag = f"{tab_label}_deviation") else: plot_wavelet(tags, tab_label, func, tab_tag, parameter_values, mode=mode) case 'multitaper coherence': plot_multitaper_coherence(parameter_values,tab_tag,tags) case 'cross-correlation': plot_cross_correlation(parameter_values,tab_tag,tags,mode='df') case 'PAC': plot_pac(tags,tab_tag,parameter_values,mode='df') case 'coherence': plot_df_coherence(parameter_values,tab_tag,tags) case _: plot_featured_signal(signal_tags, tab_label, func, tab_tag, parameter_values,mode) dpg.configure_item(f"window_{window_id}", autosize=False)
[docs]def CustomFeatures(sender,app_data,user_data): tab_tag, signal_tags= user_data window_id = extract_window_id(tab_tag) dpg.configure_item(f"window_{window_id}", autosize=True) parameter_values = {} input_fields = {} # Store input field IDs tags = getActiveSeries(signal_tags) events = [getEventsbyTag(tab_tag,serie) for serie in tags] if all(e is None for e in events):events = None if events: mode = 'segment' else: mode = None tab_label = f"{tab_tag}_{app_data}" dates = [] for tag in tags: signal = getSignalbyIdx(window_id,tab_tag,tag,mode) dates.append(signal['Date']) labels = [[dpg.get_item_label(tag), tag] for tag in tags] bands = list(getBandsByTab(tab_tag).keys()) dates = np.unique(dates) if len(dates)>1: plot_modes = ['Join Recordings', 'Separate Recordings'] else: plot_modes = [None] if dpg.does_item_exist(f"{tab_tag}_ft_input"): dpg.delete_item(f"{tab_tag}_ft_input", children_only=True) param_container = f"{tab_tag}_ft_input" dpg.delete_item(f"{tab_tag}_ft_doc", children_only=True) doc_container = f"{tab_tag}_ft_doc" else: ch = dpg.add_tree_node(label='Custom Parameters',parent=f"{tab_tag}_custom_features",default_open=True, tag = f"{tab_tag}_tree_features") group = dpg.add_group(parent=ch, tag = f"{tab_tag}_cf_group", horizontal=True) param_container = dpg.add_child_window(width=200, height=300, tag = f"{tab_tag}_ft_input", parent=group) # Space to add inputs dynamically doc_container = dpg.add_child_window(width=600, height=300, tag = f"{tab_tag}_ft_doc", parent=group) def create_input_fields(sender, app_data): '''Dynamically create input fields for selected function.''' if app_data == 'multitaper coherence': params, documentation = fill_params_features('dpss') elif app_data == 'cross-correlation': params, documentation = fill_params_features('correlate') elif app_data == 'PAC': params, documentation = fill_params_features('hilbert') else: params, documentation = fill_params_features(app_data) documentation = pre.extract_params_and_returns(documentation) if app_data == 'multitaper coherence': for k in ['step', 'nfft','y']: params[k] = None elif app_data == 'cross-correlation': params['method'] = 'auto' params['mode'] = 'full' params['dislocation'] = None params['normalization'] = None elif app_data == 'PAC': extra = "\nInput: \n ________\n s1: signal 1\n s2: signal 2\n b1: Phase Band\n b2: Amplitude Band\n" documentation = extra + documentation for k in ['s1', 's2','b1', 'b2','n_bins']: params[k] = 'choose' dpg.add_text(documentation, parent=doc_container, tag = f"{doc_container}_doc") if not params: dpg.add_text("No parameters found", parent=param_container) return dpg.add_text("Required parameters: ", parent=param_container) parameter_values.clear() # Reset parameter storage input_fields.clear() # Reset input field storage combos = {} params = {k: params[k] for k in sorted(params)} for param, default in params.items(): # Set defaults if param == 'x' or param == 'data': default = '---' if param == 'y' and (app_data == 'coherence' or app_data=='multitaper coherence'): default = '---' if param == 'fs' or param == 'sfreq': default = 250 if param == 'output': default = 'power' # wavelet if param == 'freqs': default = 100 # wavelet if param != 'window': if ((param == 'x' or param == 'y') and (app_data == 'coherence' or app_data=='multitaper coherence')) or ( (param=='a' or param=='v') and app_data=='cross-correlation') or ( app_data == 'PAC' and (param=='s1' or param=='s2') ): # Use a combo box (select box) for this special case input_id = dpg.add_combo( label=param, items=[l[0] for l in labels], default_value=default, parent=param_container, callback=different_combo, tag = f"{tab_label}_{param}", user_data=(doc_container, combos) ) combos[param] = input_id elif app_data=='PAC' and (param=='b1' or param=='b2'): input_id = dpg.add_combo( label=param, items=bands, # <-- customize your options default_value=default, callback=different_combo, user_data=(doc_container, combos), tag = f"{tab_label}_{param}", parent=param_container, ) combos[param] = input_id elif (param=='x' and app_data in ['dpss', 'spectrogram','welch','periodogram']): combos[param] = input_id elif (param == 'data' and app_data=='wavelet'): # or (param=='x' and app_data in ['dpss', 'spectrogram','welch','periodogram']): continue else: # Default to input text input_id = dpg.add_input_text( label=param, default_value=default if default is not None else " ", parent=param_container ) input_fields[param] = input_id # Store input field reference # Add save button dpg.add_button(label="Save Parameters", callback=save_parameters, parent=param_container, user_data = app_data) def save_parameters(sender, app_data, user_data): '''Saves input values to the dictionary and prints them.''' parameter_values.clear() # Reset before saving for param, input_id in input_fields.items(): value = dpg.get_value(input_id) if (param == 'x' or param == 'y') and user_data == 'coherence': ls = [l[0] for l in labels] index = ls.index(value) tag = labels[index][1] signal = getSignalbyIdx(window_id,tab_tag,tag) channel = extract_channel_from_series_tag(tag) extra = {'Channel': channel} signal = signal | extra parameter_values[param] = signal else: parameter_values[param] = value if not dpg.does_item_exist(f"{tab_tag}_plot_features"): dpg.add_button(label="Plot filtered signal", callback=apply_filt, parent=param_container, user_data=user_data, tag=f"{tab_tag}_plot_features") def apply_filt(sender, app_data, user_data): func = user_data tags = getActiveSeries(signal_tags) tab_label = f"{tab_tag}_{func}" if not dpg.does_item_exist(f"{tab_tag}_features_bar"): dpg.add_tab_bar(reorderable=True,parent=f"{tab_tag}_features", tag = f"{tab_tag}_features_bar") if dpg.does_item_exist(tab_label): dpg.delete_item(tab_label, children_only=True) dpg.configure_item(tab_label,show=True) else: dpg.add_tab(label=f"{func}", parent=f"{tab_tag}_features_bar", tag=tab_label, closable=True) match func: case 'spectrogram': if plot_modes[0] != None: if not dpg.does_item_exist(f"{tab_label}_deviation"): dpg.add_radio_button(items=plot_modes,label='Plot Mode', parent=tab_label, callback=choose, user_data=(tags, tab_label, func, tab_tag, parameter_values, mode), tag = f"{tab_label}_deviation") else: plot_spectrogram_og(tags, tab_label, func, tab_tag, parameter_values, mode) case 'wavelet': if plot_modes[0] != None: if not dpg.does_item_exist(f"{tab_label}_deviation"): dpg.add_radio_button(items=plot_modes,label='Plot Mode', parent=tab_label, callback=choose, user_data=(tags, tab_label, func, tab_tag, parameter_values, mode), tag = f"{tab_label}_deviation") else: plot_wavelet(tags, tab_label, func, tab_tag, parameter_values) case 'multitaper coherence': plot_multitaper_coherence(parameter_values,tab_tag,tags,flag='flag') case 'PAC': plot_pac(tags,tab_tag,parameter_values) case 'coherence': plot_coherence(parameter_values,tab_label) case 'cross-correlation': plot_cross_correlation(parameter_values,tab_tag,tags) case _: plot_featured_signal(signal_tags, tab_label, func, tab_tag, parameter_values) create_input_fields(sender,app_data) dpg.configure_item(f"window_{window_id}", autosize=False)
[docs]def insert_events(sender, app_data, user_data): window_id, tab_tag, signal_tags= user_data if dpg.does_item_exist(f"{tab_tag}_emin"): min = int(dpg.get_value(f"{tab_tag}_emin"))*250 max = int(dpg.get_value(f"{tab_tag}_emax"))*250 window_type = extract_type_window(window_id) def read_txt(sender, app_data, user_data): window_id, tab_tag, signal_tags = user_data if dpg.does_item_exist(f'{tab_tag}_events_present'): dpg.delete_item(f'{tab_tag}_events_present') streaming_file = app_data['file_path_name'] events = utl.read_event_file(streaming_file) scatter, dates = [], [] series = getActiveSeries(signal_tags) window_id = extract_window_id(tab_tag) scale = dpg.get_item_configuration(f"x_axis_{tab_tag}")['scale'] for i,tag in enumerate(series): signal = getSignalbyIdx(window_id,tab_tag,tag) if window_type == 'Combined': file = extract_file_from_series_tag(tag) if 'Events' not in app_state['windows'][window_id]['Files'][file]['Data'][tab_tag].keys(): app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Events'] = [] else: idx = extract_idx_from_series_tag(tag) if 'Events' not in app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx].keys(): app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx]['Events'] = [] date = signal['Date'] channel = signal['Channel'] channel = getUpdatedElectrodeName(channel) dates.append(f"{date.year}-{date.month}-{date.day}") if scale == 0: #in linear offset = DH.extract_time_offset(getDatabyTag(tag)) #already in timedelta format new_time = [date + timedelta(seconds=val) for val in signal['X'] if isinstance(val,(int,float))] new_x = [(d+offset).timestamp() for d in new_time] updateKeySignalbyIdx(new_x,tab_tag,series[i],'X') dpg.configure_item(f"x_axis_{tab_tag}",scale=dpg.mvPlotScale_Time) dpg.configure_item(series[i], x=new_x) else: new_x = signal['X'] new_time = [pre.get_date_from_ts(val) for val in new_x] for _, event in enumerate(events): # hour, minute, second, type = event dtt, type = event if (dtt.year != 1970) and (utl.extract_date(date) != utl.extract_date(dtt)): #confirms that theres a real date, not buffer, and checks with the string to minimize verify idx if its not correspondant to the session continue hour, minute, second = dtt.hour, dtt.minute, dtt.second tidx = utl.find_closest_index(new_time,hour,minute, second) if tidx != None: tidx = int(tidx) scatter.append([new_x[tidx], type, new_time[tidx]]) date_event = new_time[tidx] if dpg.does_item_exist(f"{tab_tag}_emin"): imin = tidx-min imax = tidx+max if imin<0: imin = 0 imax = tidx+max+min if imax>len(new_x)-1: imax = len(new_x)-1 imin = imax-(max+min) segment_x = new_x[imin:imax] new_y = signal['Y'][imin:imax] x_shift = segment_x[min] #mid_index aligned_x = [xi - x_shift for xi in segment_x] segment = {'X': aligned_x,'Y':new_y,'Channel':channel,'Type':type, 'Date': date_event, 'Idx': tidx, 'Serie': i} else: segment = {'Channel':channel,'Type':type, 'Date': date_event, 'Idx': tidx, 'Serie': i} if window_type == 'Combined': app_state['windows'][window_id]['Files'][file]['Data'][tab_tag]['Events'].append(segment) else: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][idx]['Events'].append(segment) if scatter: if window_type == 'Combined': align_events(sender,app_data,(tab_tag,signal_tags)) else: event_types = [s[1] for s in scatter] event_types = np.unique(event_types).tolist() for i, point in enumerate(scatter): idx, etype, time = point tag = f"{tab_tag}_{etype}_{time}" if not dpg.does_item_exist(tag): m = dpg.add_inf_line_series(parent=f"y_axis_{tab_tag}", x=[idx], label=f"{etype} - {time}", tag=tag) dpg.bind_item_theme(m, change_inf_line_color(event_types.index(etype))) signal_tags[tag] = True dpg.configure_item(f"x_axis_{tab_tag}",label='Time (MM/DD/YYYY UTC)') scatter = np.array(scatter, dtype=object) types = np.unique(scatter[:,1]).tolist() if len(types)>1: if dpg.does_item_exist(f"{tab_tag}_By Recording:_buttons"): dpg.delete_item(f"{tab_tag}_By Recording:_buttons") new_parent = f"{tab_tag}_types" if dpg.does_item_exist(f"{new_parent}_events"): children = dpg.get_item_children(f"{new_parent}_events") for _, child in children.items(): for kid in child: label = dpg.get_item_label(kid) if label not in types and label != '': types.append(label) dpg.delete_item(f"{new_parent}_events") with dpg.child_window(parent=new_parent, tag = f"{new_parent}_events",height=cwh): dpg.add_text('By Event:') for day in types: toggleType(signal_tags,parent=f"{new_parent}_events",user_data=(day, window_id, tab_tag)) dpg.configure_item(f"{new_parent}_events", width=cww,horizontal_scrollbar=True) else: if not dpg.does_item_exist(f'{tab_tag}_events_present'): dpg.add_text("No Events in the present recordings!", parent=f"{tab_tag}_add_events", tag=f'{tab_tag}_events_present') dpg.bind_item_theme(f'{tab_tag}_events_present',change_text_color(1)) dpg.fit_axis_data(f"x_axis_{tab_tag}") def create_file_dialog(window_id,tab_tag, signal_tags): with dpg.file_dialog( directory_selector=False, show=True, callback=read_txt, width=700, height=400, modal=True, default_path=directories['events'], user_data=(window_id,tab_tag, signal_tags) ) as dialog_id: dpg.add_file_extension(".txt", color=(0, 255, 0, 255), custom_text="[TEXT File]") create_file_dialog(window_id,tab_tag, signal_tags)
[docs]def align_events(sender,app_data, user_data): tab_tag, signal_tags = user_data window_id = extract_window_id(tab_tag) window_type = extract_type_window(window_id) parent = f"y_axis_{tab_tag}" all_events, edx, fdx = [], [], [] if window_type == 'Combined': files = app_state['windows'][window_id]['Files'] for idx,_ in enumerate(files): count = 0 info = files[idx]['Data'][tab_tag] if isinstance(info, list): continue #if the file has this type of recording if 'Events' in info.keys(): events = info['Events'] if len(events)>1: #why only 1? for event in events: all_events.append(event) edx.append(count) count+=1 fdx.append(idx) dpg.delete_item(parent,children_only=True) signal_tags.clear() dpg.configure_item(f"x_axis_{tab_tag}", scale=dpg.mvPlotScale_Linear) signal_tags = plot_td(all_events,parent,tab_tag, signal_tags,files=fdx,events=True, count=edx) # else: dpg.add_text('To compare events, needs at least 2 events', parent=parent) dpg.configure_item(f"{tab_tag}_add_events", show = False) # dpg.configure_item(f"{tab_tag}_all_streams", show = False) dpg.delete_item(f"{tab_tag}_tav") toggleVisibility(signal_tags, tab_tag, group=f"{tab_tag}_all_streams", user_data=window_id) dpg.configure_item(f"{tab_tag}_segmentation", show = False) else: signals = app_state['windows'][window_id]['Data'][tab_tag]['Signals'] events = [] for idx,_ in enumerate(signals): temp = getEventsbyIdx(tab_tag,idx) for t in temp: events.append(t) if len(events)>1: dpg.delete_item(parent,children_only=True) signal_tags.clear() dpg.configure_item(f"x_axis_{tab_tag}", scale=dpg.mvPlotScale_Linear) plot_td(events,parent,tab_tag, signal_tags,events=True)
# else: dpg.add_text('To compare events, needs at least 2 events', parent=parent)
[docs]def changeTimelinePlot(sender,app_data,user_data): signal_tags, signals, data, plot_tag = user_data # signals = getActiveSignals(tab_tag,signal_tags) old = list(dpg.get_item_user_data(f"{plot_tag}_export_plot")) ploted_colors = old[-1] for idx, signal in enumerate(signals): for tag in signal_tags.keys(): if isinstance(tag,int) or 'series' not in tag: continue i = extract_idx_from_series_tag(tag) if i == idx and signal_tags[tag]==True: if dpg.does_item_exist(f"{tag}_Left_added"): dpg.configure_item(f"{tag}_Left_added",show=app_data) dpg.configure_item(f"{tag}_Right_added",show=app_data) else: if isinstance(data,list): dictionary = data[idx] else: dictionary = data if dictionary != None: extra = DH.correctMissingTimeline(dictionary, signal,'X_missing')[0] for side in ['Left','Right']: tagg = f"{tag}_{side}_added" extra_x, extra_y = extra[side]['X_missing'], extra[side]['Y_missing'] extra_x = [x.timestamp() for x in extra_x] if extra_x != []: if side=='Left': color_id=-2 else: color_id=-6 ploted_colors.append(colors[color_id]) s = dpg.add_scatter_series(extra_x,extra_y,tag = tagg, parent=plot_tag) dpg.bind_item_theme(s,change_plot_color(color_id)) old[-1] = ploted_colors dpg.set_item_user_data(f"{plot_tag}_export_plot",old)
[docs]def circadian_rhythm(sender, app_data, user_data): tab_tag, signal_tags, data = user_data tab_label = tab_tag+'_circadian' if not dpg.does_item_exist(f"{tab_tag}_features_bar"): dpg.add_tab_bar(reorderable=True,parent=f"{tab_tag}_features", tag = f"{tab_tag}_features_bar") if dpg.does_item_exist(tab_label): if not dpg.get_item_configuration(tab_label)['show']: dpg.delete_item(tab_label) return dpg.add_tab(label=f"Circadian Rhythm", parent=f"{tab_tag}_features_bar", tag=tab_label,closable=True) tags = getActiveSeries(signal_tags) if dpg.does_item_exist(f"{tab_tag}_missing_points"): missing=dpg.get_value(f"{tab_tag}_missing_points") else: missing=False days = {} mean, dates, circadian_tags = [], [], [] #preparing streams for idx,tag in enumerate(tags): signal = getSignalbyIdx(extract_window_id(tab_tag),tab_tag,tag) old_x,old_y = dpg.get_value(tag)[:2] if missing: dictionary = getDatabyTag(tag) side = extract_side_from_series_tag(tag).capitalize() filtered = {side:{'X': [datetime.fromtimestamp(xx) for xx in old_x], 'Y':old_y},'Date':signal['Date']} if dictionary != None: # extra = DH.correctMissingTimeline(dictionary, signal)[0][side] extra = DH.correctMissingTimeline(dictionary, filtered,days=filtered['Date'],filtered=filtered)[0][side] old_x = [temp_x.timestamp() for temp_x in extra['X']] old_y = extra['Y'] label = dpg.get_item_label(tag) stim_info = DH.info_electrodes(getDatabyTag(tag))['Sense_frequency'] dates.append(label[-10:]) x = sorted(old_x) if x[0] > old_x[0]: old_y = list(reversed(old_y)) old_x = list(reversed(old_x)) if isinstance(old_x[0],(int,float)): old_x = [utl.get_timestamp_from_dt(str(pre.get_date_from_ts(xx).time())) for xx in x] elif isinstance(old_x[0], str): old_x = [utl.get_timestamp_from_dt(xx) for xx in x] else: #datetime old_x = [utl.get_timestamp_from_dt(utl.full_date2str(xx)) for xx in x] x,y = [], [] for n in range(len(old_x)-1): x.append(old_x[n]) y.append(old_y[n]) if old_x[n+1]-old_x[n]>660: gap_times = np.arange(old_x[n]+600, old_x[n+1], 600) x.extend(gap_times) y.extend([np.nan]*len(gap_times)) x.append(old_x[-1]) y.append(old_y[-1]) last = utl.get_timestamp_from_dt("23:59:00") if x[0]>600: #day starts after theoric first point (00h10) if x[0]>last: x[0] = x[0]-last diff = int((x[0]-600)//600)+1 nan_array = [np.nan]*diff y = nan_array + y x = list(np.arange(x[0]-(diff*600),x[0],600)) + x if x[-1]<last: #day ends before theoric first point (23h50) diff = int((last - x[-1])//600)+1 nan_array = [np.nan]*diff y = y + nan_array x = x + list(np.arange(x[-1],last,600)) x=x[:144] #6timestamps every 24hours y=y[:144] days[tag] = [x,y,stim_info] mean.append(y) std = np.nanstd(mean,axis=0) mean = np.nanmean(mean,axis=0) minus = mean - std plus = mean + std def toggle(sender,app_data,user_data): label = dpg.get_item_label(sender) new_mean = [] if dpg.get_value(f"{tab_label}_hide") == True: callback = dpg.get_item_callback(f"{tab_label}_hide") dpg.set_value(f"{tab_label}_hide",False) if callback: callback(f"{tab_label}_hide", None, dpg.get_item_user_data(f"{tab_label}_hide")) toggle_tags = [f"{tag}_{tab_label}" for tag in getActiveSeries(tags)] for t in toggle_tags: taglabel = dpg.get_item_label(t) if label in taglabel: dpg.configure_item(t,show=app_data) show = dpg.get_item_configuration(t)['show'] if show: new_mean.append(dpg.get_value(t)[1]) if len(toggle_tags)<1: length = len(dpg.get_value(f"{tab_label}_mean")[0]) new_mean = np.zeros(length) dpg.configure_item(f"{tab_label}_mean",y=new_mean) dpg.configure_item(f"{tab_label}_std",y1=new_mean,y2=new_mean) return new_std = np.nanstd(new_mean,axis=0) new_mean = np.nanmean(new_mean,axis=0) new_minus = new_mean - new_std new_plus = new_mean + new_std dpg.configure_item(f"{tab_label}_mean",y=new_mean) dpg.configure_item(f"{tab_label}_std",y1=new_minus,y2=new_plus) new_x = dpg.get_value(f"{tab_label}_mean")[0] sign = {'X':new_x,'Y':new_mean} info = ft.extract_timeline_features(sign) trial = 'Mean' if not app_data: trial = trial + ' wo/ ' + label if not isinstance(user_data,str): create_table(f"{tab_label}_header",f"{tab_label}_table", info, trial_label=trial) with dpg.group(parent=tab_label, horizontal=True, tag=f"{tab_label}_CR"): plot_tag = f"{tab_label}_plot" with dpg.plot(label = "Circadian Rhythm", tag = plot_tag, width=800): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label='Time (MM/DD/YYYY UTC)', scale = dpg.mvPlotScale_Time) #tag=f"{tab_label}_x_axis" y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="LFP Power (µVp)", tag= f"{tab_label}_y_axis") for tag, val in days.items(): label = dpg.get_item_label(tag) circadian_tags.append(f"{tag}_{tab_label}") dpg.add_line_series(val[0],val[1],label=label,parent=y_axis, tag = f"{tag}_{tab_label}",skip_nan=False) rgba = dpg.get_colormap_color(0,list(days.keys()).index(tag)) dpg.bind_item_theme(f"{tag}_{tab_label}",create_alpha_theme(alpha=0.8,color=rgba)) new_x = [pre.get_date_from_ts(xx) for xx in val[0]] sign = {'X':new_x,'Y':val[1],'Sense':val[2]} if not dpg.does_item_exist(f"{tab_label}_header"): dpg.add_collapsing_header(tag=f"{tab_label}_header", parent=tab_label, label = 'Circadian Metrics') s = dpg.add_shade_series(val[0],y1=minus,y2=plus,parent=y_axis,label='Std. Dev',tag=f"{tab_label}_std") dpg.bind_item_theme(s,create_alpha_theme()) m = dpg.add_line_series(val[0],mean,parent=y_axis,label='Mean',tag=f"{tab_label}_mean",skip_nan=False) dpg.bind_item_theme(m,change_line_thickness(4)) sign = {'X':new_x,'Y':mean} info = ft.extract_timeline_features(sign) create_table(f"{tab_label}_header",f"{tab_label}_table", info, trial_label='Mean') dates = sorted(np.unique(dates).tolist()) checkboxes = [] with dpg.group(): dpg.add_checkbox(label='Hide Days', tag = f"{tab_label}_hide", callback=hide,parent = f"{tab_label}_vbuttons", user_data=(circadian_tags,checkboxes)) with dpg.child_window(height=cwh, auto_resize_x = True): a = dpg.add_text('By Day:') for d in dates: c = dpg.add_checkbox(label=d, default_value=True, callback=toggle) checkboxes.append(c) dpg.configure_item(dpg.get_item_parent(a),auto_resize_x=False,resizable_x=True) with dpg.child_window(height=cwh,width=cww,horizontal_scrollbar=True): a = dpg.add_text('By Hemisphere:') for side in ['Left', 'Right']: dpg.add_checkbox(label=side, default_value=True, callback=toggle) dpg.configure_item(dpg.get_item_parent(a),auto_resize_x=False,resizable_x=True) dpg.add_separator(label='Export',parent=tab_label) b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,callback = filename_window,user_data =('2D', plot_tag, y_axis,0),parent=tab_label) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,parent=tab_label,user_data=(y_axis,plot_tag,'2D')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2)
[docs]def stimulation_timeline(sender,app_data,user_data): tab_tag, signal_tags, data = user_data tab_label = tab_tag+'_stimulation' window_id = extract_window_id(tab_tag) if not dpg.does_item_exist(f"{tab_tag}_features_bar"): dpg.add_tab_bar(reorderable=True,parent=f"{tab_tag}_features", tag = f"{tab_tag}_features_bar") if dpg.does_item_exist(tab_label): if not dpg.get_item_configuration(tab_label)['show']: dpg.delete_item(tab_label) return dpg.add_tab(label=f"Stimulation", parent=f"{tab_tag}_features_bar", tag=tab_label,closable=True) if extract_type_window(window_id) == 'Combined': files = [i for i in range(len(app_state['windows'][window_id]['Files']))] files = sorted(files,key=lambda f: getDatabyFile(window_id, f)['Device']['SessionDate']) else: files = [None] copy = {'X': [], 'Y': [], 'Info':[]} stimulation = {side: {'X': [], 'Y': [], 'Info':[]} for side in ['Left','Right']} for side in ['Left','Right']: temp = stimulation[side] for file in files: stimus = DH.getStimulation(getDatabyFile(window_id,file),side=side) for x in stimus['X']: temp['X'].append(x) for x in stimus['Y']: temp['Y'].append(x) for x in stimus['Info']: if temp['Info']!= [] and x['Date'] == temp['Info'][-1]['Date']: temp['Info'][-1] = x continue temp['Info'].append(x) with dpg.group(parent=tab_label, horizontal=True): plot_tag = f"{tab_label}_plot" with dpg.plot(label = "Stimulation", tag = plot_tag, width=500, height=400): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label='Time (MM/DD/YYYY UTC)', scale = dpg.mvPlotScale_Time) #tag=f"{tab_label}_x_axis" y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Stimulation Amplitude (mA)", tag= f"{tab_label}_y_axis") for side in ['Left','Right']: temp = stimulation[side] x = [utl.parse_datetime(x).timestamp() for x in temp['X']] y = temp['Y'] l = dpg.add_line_series(x,y,parent=y_axis,label=f'{side} Hemisphere') dpg.bind_item_theme(l,change_line_thickness(4)) with dpg.group(): dpg.add_text('Stimulation Information') for side in ['Left','Right']: with dpg.collapsing_header(tag=f"{tab_label}_{side}_information", label=f'{side} Hemisphere'): for info in stimulation[side]['Info']: trial = info['Date'] info.pop('Date') create_table(f"{tab_label}_{side}_information",f"{tab_label}_{side}_information_table",info,trial_label=trial) dpg.add_separator(label='Export',parent=tab_label) b1 = dpg.add_button(label = 'Export Plot Figure',parent=tab_label,width=-1,callback = filename_window,user_data =('2D', plot_tag, y_axis,0)) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,parent=tab_label,user_data=(y_axis,plot_tag,'Stimulation')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2)
[docs]def AnalyzeTimelineEvents(sender,app_data,user_data): tab_tag, events_tags, signal_tags = user_data event_ids = [dpg.get_value(e[0])[0] for e in events_tags] event_labels = [dpg.get_item_label(e[0]) for e in events_tags] timelines = [dpg.get_value(t)[:2] for t in signal_tags.keys()] timeline_labels = [dpg.get_item_label(t) for t in signal_tags.keys()] if dpg.does_item_exist(f"{tab_tag}_missing_points") and dpg.get_value(f"{tab_tag}_missing_points"): timelines = [] for tag in signal_tags.keys(): dictionary = getDatabyTag(tag) signal = getSignalbyIdx(extract_window_id(tab_tag),tab_tag,tag) side = extract_side_from_series_tag(tag).capitalize() if dictionary != None: extra = DH.correctMissingTimeline(dictionary, signal)[0][side] corrected_x = [temp_x.timestamp() for temp_x in extra['X']] timeline.append([corrected_x,extra['Y']]) segments, event_names, checkboxes, days, temp_tags = [], [], [], [], [] def cut_segment(x,y,event): diff = [abs(xx-event) for xx in x] ev_id = diff.index(min(diff)) half_window = 6 if ev_id >= half_window and ev_id <= len(x) - half_window - 1: start = ev_id - half_window end = ev_id + half_window + 1 segment_x = x[start:end] segment_y = y[start:end] elif ev_id < half_window: # Pad at the start pad = np.full(half_window - ev_id, np.nan) segment_x = np.concatenate([pad, x[0:ev_id + half_window + 1]]) segment_y = np.concatenate([pad, y[0:ev_id + half_window + 1]]) else: # Pad at the end pad = np.full(ev_id + half_window + 1 - len(x), np.nan) segment_x = np.concatenate([x[ev_id - half_window:], pad]) segment_y = np.concatenate([y[ev_id - half_window:], pad]) onex = segment_x[0] segment_x = [x - onex for x in segment_x] if y == []: return None return segment_x,segment_y for ex, event in enumerate(event_ids): event = event[0] for timeline in timelines: x, y = timeline[0], timeline[1] first, last = min(x), max(x) label = f"{event_labels[ex]} - {timeline_labels[timelines.index(timeline)]}" if not (first <= event <= last): continue segment = cut_segment(x,y,event) if segment is None: continue segments.append([segment,label]) event_names.append(label) date = utl.extract_date(pre.get_date_from_ts(event)) days.append(date) values = [segment[0][1] for segment in segments] mean_value = np.nanmean(values, axis = 0) std_dev = np.nanstd(values, axis = 0) tab_label = tab_tag+'_events' plot_tag = f'{tab_label}_plot' def normal(segments, plot=True, bva=False): for s, seg in enumerate(segments): segment, label = seg name = label[:label.find('-')] name = getUpdatedEventName(name) x,y=segment[0],segment[1] if plot: dpg.add_line_series(x,y,label=label,parent=y_axis,tag=f"{tab_label}_{label}_{s}") temp_tags.append(f"{tab_label}_{label}_{s}") else: name = label[label.find(' | ')+3:] name = getUpdatedEventName(name[:name.find('-')]) sign = {'X':x,'Y':y} if 'Before' in label: header = f"{tab_label}_{name}_before" table = f"{tab_label}_table_before" elif 'After' in label: header = f"{tab_label}_{name}_after" table = f"{tab_label}_table_after" else: header = f"{tab_label}_{name}_full" table = f"{tab_label}_table_full" parent = f"{tab_label}_{name}" if not dpg.does_item_exist(parent): dpg.add_tree_node(tag=parent, parent=tab_label,label=f"{name} Metrics") for moment in ['Full','Before','After']: this = f"{tab_label}_{name}_{moment.lower()}" if not dpg.does_item_exist(this): dpg.add_tree_node(tag=this, parent=parent,label=f'{moment}') for side in ['Left','Right']: if not dpg.does_item_exist(f"{this}_{side}"): dpg.add_collapsing_header(label=f"{name} | {moment} | {side}",tag=f"{this}_{side}",parent=this) if not isinstance(y,(list,np.ndarray)) or (np.all(y) == np.nan): continue info = ft.extract_timeline_features(sign) if info != None: equal = f"{tab_label}_{name}" if 'Before' in label: moment = '_before' elif 'After' in label: moment = '_after' else: moment = '_full' if 'Right' in label: side = '_Right' else: side = '_Left' header = equal + moment + side table = header + '_table' label = label[-14:] create_table(header,table, info, trial_label=label) minus = mean_value - std_dev plus = mean_value + std_dev if plot and len(segments)>0: std = dpg.add_shade_series(segment[0],minus, y2 = plus,label='Std. Dev', parent=y_axis, tag = f"{tab_label}_std") dpg.bind_item_theme(std,create_alpha_theme()) mean = dpg.add_line_series(segment[0],mean_value,label='Mean',parent=y_axis, tag = f"{tab_label}_mean") dpg.bind_item_theme(mean, change_line_thickness(5)) def bva(segments): bva_segments = [] for segment, label in segments: x,y = segment before = [x[:6],y[:6]] bva_segments.append([before,f"Before | {label}"]) after = [x[6:],y[6:]] bva_segments.append([after,f"After | {label}"]) normal(bva_segments, plot=False, bva=True) def toggle(sender,app_data,user_data): name = dpg.get_item_label(sender) mean_temp = [] c = f"{tab_label}_hide" if dpg.get_value(c)==True: dpg.set_value(c,False) for s, tag in enumerate(segments): t, label = tag dpg.configure_item(f"{tab_label}_{label}_{s}",show=True) for s, tag in enumerate(segments): t, label = tag if name in label: dpg.configure_item(f"{tab_label}_{label}_{s}",show=app_data) show = dpg.get_item_configuration(f"{tab_label}_{label}_{s}")['show'] if show: mean_temp.append(t[1]) if len(segments)<1: length = len(dpg.get_value(f"{tab_label}_mean")[0]) new_mean = np.zeros(length) dpg.configure_item(f"{tab_label}_mean",y=new_mean) dpg.configure_item(f"{tab_label}_std",y1=new_mean,y2=new_mean) return new_mean = np.nanmean(mean_temp,axis=0) new_std = np.nanstd(mean_temp,axis=0) minus = new_mean - new_std plus = new_mean + new_std dpg.configure_item(f"{tab_label}_std",y1=minus, y2=plus) dpg.configure_item(f"{tab_label}_mean",y=new_mean) mean_info = {'X': t[0], 'Y':new_mean} mean_info = ft.extract_timeline_features(mean_info) if not dpg.does_item_exist(f"{tab_label}_header"): dpg.add_collapsing_header(tag=f"{tab_label}_header",label='Mean',parent=tab_label) create_table(f"{tab_label}_header",f"{tab_label}_table", mean_info, trial_label=f'New Mean ~({name})') if not dpg.does_item_exist(f"{tab_tag}_features_bar"): dpg.add_tab_bar(reorderable=True,parent=tab_tag, tag = f"{tab_tag}_features_bar") if dpg.does_item_exist(tab_label): dpg.delete_item(tab_label,children_only=True) else: dpg.add_tab(label=f"Timeline Events", parent=f"{tab_tag}_features_bar", tag=tab_label) e_names = np.unique(event_labels).tolist() with dpg.group(parent=tab_label, tag=f"{plot_tag}_group", horizontal=True): with dpg.plot(label = 'Event-locked Segments',tag = plot_tag, width=600, height=400): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (MM/DD/YYYY UTC)", tag=f"x_axis_{plot_tag}", scale=dpg.mvPlotScale_Time) y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µVP)", tag=f"y_axis_{plot_tag}") max_timestamp = 2*60*60 dpg.set_axis_limits(x_axis, 0, max_timestamp) normal(segments) bva(segments) with dpg.group(parent=f"{plot_tag}_group"): with dpg.group(horizontal=True): with dpg.child_window(width=cww,horizontal_scrollbar=True,height=cwh): dpg.add_text('Hemisphere: ') for side in ['Left', 'Right']: c = dpg.add_checkbox(label=side,callback = toggle,default_value=True) checkboxes.append(c) with dpg.child_window(width=cww,horizontal_scrollbar=True,height=cwh): dpg.add_text('Events: ') for side in e_names: c = dpg.add_checkbox(label=side,callback = toggle,default_value=True) checkboxes.append(c) with dpg.child_window(width=150,height=cwh): dpg.add_text('Days: ') days = sorted(np.unique(days).tolist()) for side in days: c = dpg.add_checkbox(label=side,callback = toggle,default_value=True) checkboxes.append(c) dpg.add_separator() dpg.add_checkbox(label='Show Mean Only', tag = f"{tab_label}_hide", callback=hide, user_data=(temp_tags,checkboxes)) b1 = dpg.add_button(label = 'Export Plot Figure',callback = filename_window,user_data =('2D', plot_tag, y_axis,0),width=-1) b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,plot_tag,'2D')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2)
[docs]def RemoveTimelineOutliers(sender,app_data,user_data): tab_tag,signal_tags = user_data window_id = extract_window_id(tab_tag) tags = getActiveSeries(signal_tags) factor = app_data if dpg.does_item_exist(f"{tab_tag}_plot_progress"): dpg.delete_item(f"{tab_tag}_plot_progress") # create_table(f"{tab_tag}_header",f"{tab_tag}_table", info, trial_label=label_left) # dpg.delete_item(f"{tab_tag}_header", children_only=True) dpg.add_progress_bar(parent=f"{tab_tag}_plot_group",overlay='Removing', default_value=0.0, width = 400, height = 50, tag = f"{tab_tag}_plot_progress") for i, tag in enumerate(tags): values = dpg.get_value(tag) x,_= values[0], values[1] data = getDatabyTag(tag) info = dpg.get_item_configuration(tag) series_label = info['label'] side = series_label.split(' ')[0] stream = getSignalbyIdx(window_id,tab_tag,tag) stream = DH.correctMissingTimeline(data,stream)[0] new_x,y = stream[side]['X'],stream[side]['Y'] new_x = [utl.convert_to_timestamp(n) for n in new_x] y = np.array(y) copy = np.copy(y) '''USING RMS''' # threshold = np.sqrt(np.mean(np.array(y)**2)) # '''USING ITERATIVE RMS''' rms = np.sqrt(np.mean(y**2)) for _ in range(3): # Iterate a few times y = y[np.abs(y) <= factor * rms] if len(y)>=(len(copy)/10)+1: rms = np.sqrt(np.nanmean(y**2)) # threshold =rms '''USING MEDIAN''' mad = np.median(np.abs(y - np.median(y))) # threshold = mad # Or some scaled version threshold = rms mask = copy > factor*threshold copy[mask] = rms dpg.set_value(tag,[new_x,copy.tolist()]) updateSignalbyIdx(copy.tolist(),tab_tag,tag,f'Filtered_{side}') progress = np.round((i+1)/len(tags),2) dpg.set_value(f"{tab_tag}_plot_progress", progress) if progress == 1: dpg.configure_item(f"{tab_tag}_plot_progress", overlay = 'Removed')
[docs]def recalculate_timeline(sender,app_data,user_data): tab_tag, tags, tables = user_data if dpg.does_item_exist(f"{tab_tag}_rebar"): dpg.delete_item(f"{tab_tag}_rebar") for table in tables: dpg.delete_item(table,children_only=True) mean = [] for tag in tags: if not isinstance(tag,str): continue #means its a marked event if not dpg.get_item_configuration(tag)['show']: continue info, _ = calculate_timeline_features(tab_tag,tag) mean.append(info) mean_dict = {} for key in mean[0].keys(): values = [m[key] for m in mean] if isinstance(values[0], str): values=[utl.get_timestamp_from_dt(t) for t in values] mean_value = utl.extract_time(pre.get_date_from_ts(np.mean(values))) mean_value = {key: mean_value} else: mean_value = {key: np.mean(values,axis=0)} mean_dict = mean_dict | mean_value create_table(f"{tab_tag}_header",f"{tab_tag}_table", mean_dict, trial_label='Mean')
[docs]def calculate_timeline_features(tab_tag,series_tag): label = dpg.get_item_label(series_tag) datestr = label[-10:] side = label[:label.find(' ')].capitalize() x,y = dpg.get_value(series_tag)[:2] sign = {'X':x,'Y':y} wt = extract_type_window(extract_window_id(tab_tag)) nodes = {f"{tab_tag}_hemisphere": "By Hemisphere"} headers = {f"{tab_tag}_{side}": [f'{side} Hemisphere',f"{tab_tag}_hemisphere"], f"{tab_tag}_header": ['All Individual Streams', tab_tag]} if wt=='Combined': channel, sensing_band = dpg.get_item_user_data(series_tag) if channel is not None: nodes[f"{tab_tag}_channel"] = 'By Channel' headers[f"{tab_tag}_{channel}"] = [channel,f"{tab_tag}_channel"] if sensing_band is not None: nodes[f"{tab_tag}_sensing"] = 'By Sensing Band' headers[f"{tab_tag}_{sensing_band}"] = [sensing_band,f"{tab_tag}_sensing"] for tag, name in nodes.items(): if not dpg.does_item_exist(tag): dpg.add_tree_node(label=name,parent=tab_tag,tag=tag,default_open=False) for tag, data in headers.items(): name,parent = data if not dpg.does_item_exist(tag): dpg.add_collapsing_header(tag=tag,label=name,parent=parent,default_open=False) '''if not dpg.does_item_exist(f"{tab_tag}_hemisphere"): dpg.add_tree_node(tag=f"{tab_tag}_hemisphere", label="By Hemisphere", parent=tab_tag, default_open=False ) if not dpg.does_item_exist(f"{tab_tag}_{side}"): dpg.add_collapsing_header(label = f'{side} Hemisphere', tag =f"{tab_tag}_{side}", parent = f"{tab_tag}_hemisphere") if not dpg.does_item_exist(f"{tab_tag}_header"): dpg.add_collapsing_header(tag=f"{tab_tag}_header", parent=tab_tag, label = 'All Streams Metrics')''' info = ft.extract_timeline_features(sign) create_table(f"{tab_tag}_header",f"{tab_tag}_table", info, trial_label=label) create_table(f"{tab_tag}_{side}",f"{tab_tag}_{side}_table", info, trial_label=datestr) if wt=='Combined': if channel is not None: create_table(f"{tab_tag}_{channel}",f"{tab_tag}_{channel}_table",info, trial_label=datestr) if sensing_band is not None: create_table(f"{tab_tag}_{sensing_band}",f"{tab_tag}_{sensing_band}_table",info, trial_label=label) tables = list(headers.keys()).__add__(list(nodes.keys())) return info, tables
#-------------------------------------GENERAL TABS-------------------------------------------
[docs]def device_window(Data, window_id, file_id = None): wt = extract_type_window(window_id) device = DH.getDeviceInfo(Data) battery = DH.getBatteryInfo(Data) battery_reminder = Data['Device']['BatteryReminder'] date = DH.getSessionDate(Data) info_parent = f"device_parameteres_{window_id}" dpg.configure_item(f"{info_parent}_title", show=True) if wt == 'Individual': dpg.delete_item(item=info_parent,children_only=True) else: dpg.configure_item(info_parent, show=True) header_tag = info_parent + f'_{file_id}' if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag) dpg.add_collapsing_header(tag=header_tag, parent=info_parent, label = date) info_parent = header_tag #WARNINGS if battery_reminder==True: w = dpg.add_text("WARNING: Battery needs recharge!", parent=f"window_{window_id}", show=True) color = change_text_color(0) dpg.bind_item_theme(w,color) if int(device['OverdischargeCount'])>0: w = dpg.add_text(f"Warning! Overdischarge count above 0", parent=f"window_{window_id}") color = change_text_color(0) dpg.bind_item_theme(w,color) #INFO dpg.add_text(f"Battery Charge: {battery['BatteryPercentage']}%", parent=info_parent, show=True) dpg.add_text(f"Battery Status: {utl.after_point(battery['BatteryStatus'])}", parent=info_parent, show=True) dpg.add_separator(parent=info_parent) dpg.add_text(f"Neurostimulator: {device['Neurostimulator']} {device['NeurostimulatorModel']}", parent=info_parent, show=True) dpg.add_text(f"Location: {utl.after_point(device['NeurostimulatorLocation'])}", parent=info_parent, show=True) dpg.add_text(f"Overdischarge Count: {device['OverdischargeCount']}", parent=info_parent, show=True) dpg.add_separator(parent=info_parent) dpg.add_text('Versions', parent=info_parent) dpg.add_text(f"Device Version: {device['DeviceVersion']}", parent=info_parent) dpg.add_text(f"Product Version: {device['ProductVersion']}", parent=info_parent) dpg.configure_item(info_parent, show=True)
[docs]def stimulation_window(Data, window_id, file_id = None): wt = extract_type_window(window_id) info_parent = f"stimulation_parameteres_{window_id}" try: stimulation = DH.info_electrodes(Data,version='Initial') except Exception as e: d = dpg.add_text('Stimulation OFF/not set up!', parent=info_parent) dpg.bind_item_theme(d,change_text_color(1)) dpg.configure_item(f"{info_parent}_title", show=True) dpg.configure_item(info_parent, show=True) return date = DH.getSessionDate(Data) dpg.configure_item(f"{info_parent}_title", show=True) if wt == 'Individual': dpg.delete_item(item=info_parent,children_only=True) else: dpg.configure_item(info_parent, show=True) header_tag = info_parent + f'_{file_id}' if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag) dpg.add_collapsing_header(tag=header_tag, parent=info_parent, label = date) info_parent = header_tag try: ch = stimulation['Sense_electrodes'] if ch in electrodes_names.keys(): ch = electrodes_names[ch] else: electrodes_names[ch] = ch except Exception: pass dpg.add_text(f"Active Group: {stimulation['Group_id']}", parent=info_parent) dpg.add_text(f"Frequency: {stimulation['Frequency']} Hz", parent=info_parent) dpg.add_text(f"Amplitude: {stimulation['Amplitude']} mA", parent=info_parent) dpg.add_text(f"Pulse Width: {stimulation['Pulse']} µs", parent=info_parent) dpg.add_text(f"Type: {stimulation['Type']}", parent=info_parent) dpg.add_text(f"Electrodes: {stimulation['Stim_electrodes']}", parent=info_parent) if 'PSD' in stimulation.keys(): dpg.add_separator(parent=info_parent) d = dpg.add_text('At-home BrainSense: ON', parent=info_parent) dpg.bind_item_theme(d,change_text_color(-1)) dpg.add_text(f"Sensing Channel: {getUpdatedElectrodeName(ch)}", parent=info_parent) f = stimulation['Sense_frequency'] f1, f2 = np.round(f-2.5,2),np.round(f+2.5,2) dpg.add_text(f"Sensing Band: [{f1}, {f2}] Hz", parent=info_parent) else: d = dpg.add_text('At-home BrainSense: OFF', parent=info_parent) dpg.bind_item_theme(d,change_text_color(1)) dpg.configure_item(info_parent, show=True)
[docs]def session_window(Data, window_id, file_id = None): wt = extract_type_window(window_id) date = DH.getSessionDate(Data) session_duration = DH.getSessionDuration(Data) initial_stim_status, final_stim_status = DH.getStimStatus(Data) impedance = DH.getImpedance(Data) patient_info = DH.getPatientInfo(Data) info_parent = f"session_parameteres_{window_id}" dpg.configure_item(f"{info_parent}_title", show=True) if not isinstance(impedance, str): impedance_status, l, r = impedance else: impedance_status = impedance l=False if wt == 'Individual': dpg.delete_item(item=f"session_parameteres_{window_id}",children_only=True) else: header_tag = info_parent + f'_{file_id}' if dpg.does_item_exist(header_tag): dpg.delete_item(header_tag) dpg.add_collapsing_header(tag=header_tag, parent=info_parent, label = date) info_parent = header_tag dpg.add_text(f"Session date: {date}", parent=info_parent, show=True) dpg.add_text(f"Session duration: {session_duration}", parent=info_parent, show=True) dpg.add_text(f"Initial Stimulation: {initial_stim_status}", parent=info_parent, show=True) dpg.add_text(f"Final Stimulation: {final_stim_status}", parent=info_parent, show=True) dpg.add_separator(parent=info_parent) dpg.add_text(f"Patient Name: {patient_info['PatientFirstName']} {patient_info['PatientLastName']}", parent=info_parent, show=True) dpg.add_text(f"Patient Date of Birth: {patient_info['PatientDateOfBirth']}", parent=info_parent, show=True) dpg.add_text(f"Diagnosis: {utl.after_point(patient_info['Diagnosis'])}", parent=info_parent, show=True) if patient_info['ClinicianNotes'] != "": dpg.add_text(f"Notes: {patient_info['ClinicianNotes']}", parent=info_parent, show=True) dpg.add_separator(parent=info_parent) dpg.add_text(f"Impedance Status: {impedance_status}", parent=info_parent, show=True) dpg.configure_item(info_parent, show=True) return l
[docs]def window_menu(window_id): with dpg.menu_bar(label='Settings'): with dpg.menu(label='Edit',tag=f"{window_id}_change"): dpg.add_menu_item(label='Edit Window Name',callback=change_window_name,user_data=f"window_{window_id}") dpg.add_menu_item(label='Activate Horizontal Scrollbar', callback = horizontal_scroll,user_data=f"window_{window_id}") with dpg.menu(label='Save...'): p = dpg.add_menu_item(label="All Window - Plots", callback=save_all, user_data = ('Plots', window_id)) with dpg.tooltip(parent=p): dpg.add_text(f"Plots must be previously opened for correct exportation.") dpg.add_menu_item(label="All Window - Tables", callback=save_all, user_data = ('Tables', window_id)) dpg.add_menu_item(label="All Window - Signals", callback=save_all, user_data = ('Signals', window_id))
#--------------------------------------INDIVIDUAL TABS-----------------------------------------
[docs]def show_event_summary(mode,tab_tag,plot_tag,files=None): window_id = extract_window_id(tab_tag) wt = extract_type_window(window_id) if wt == 'Individual': Data = getDatabyTag(tab_tag) daylight_savings = DH.extract_time_offset(Data) temp = DH.getEventLog(Data) names = [event['EventName'] for event in temp] names = np.unique(names).tolist() elif wt == 'Combined': temp, names = [], [] for file in files: Data = file['Data'] daylight_savings = DH.extract_time_offset(Data) temp_file = DH.getEventLog(Data) names_file = [event['EventName'] for event in temp_file] for t in temp_file: temp.append(t) for n in names_file: names.append(n) names = np.unique(names).tolist() names = [getUpdatedEventName(name) for name in names] event_summary = {name: {} for name in names} for event in temp: eventname = getUpdatedEventName(event['EventName']) # if utl.parse_datetime(previous_session).date()>utl.parse_datetime(event['DateTime']).date(): continue if event['DateTime'] in event_summary[eventname].keys(): event_summary[eventname][event['DateTime']] = event_summary[eventname][event['DateTime']] +1 else: event_summary[eventname][event['DateTime']] = 1 def toggle_plot(sender,app_data,user_data): if app_data == user_data: return if app_data=='Days': values=[len(event_summary[key]) for key in event_summary.keys()] # numerical values labels=list(event_summary.keys()) labels = [getUpdatedEventName(l) for l in labels] title = 'Event Count' dpg.bind_colormap(f"{tab_tag}_pie_plot", 0) else: new = {} for _,events in event_summary.items(): for day in events.keys(): dt = int(utl.parse_datetime(day).hour) if dt in new.keys(): new[dt] = new[dt] + events[day] else: new[dt] = events[day] sorted_items = sorted(new.items()) labels, values = zip(*sorted_items) labels = list(labels) labels = [getUpdatedEventName(l) for l in labels] values = list(values) title = 'Event Circadian Rhythm' dpg.bind_colormap(f"{tab_tag}_pie_plot", 3) dpg.configure_item(f"{tab_tag}_pie",values=values,labels=labels) dpg.configure_item(f"{tab_tag}_pie_plot",label=title) for name, events in event_summary.items(): if app_data == 'Days': x = [(utl.parse_datetime(key)+daylight_savings).timestamp() for key in events.keys()] weight = 200 else: x = [utl.get_timestamp_from_dt(key)+(daylight_savings.seconds) for key in events.keys()] weight = 75 dpg.configure_item(f"{name}_{tab_tag}",x=x, weight=weight) dpg.configure_item(f"{tab_tag}_xaxis",label=app_data) dpg.set_item_user_data(f"axis_{tab_tag}",app_data) with dpg.group(label = f"{tab_tag}_group", parent=tab_tag, horizontal=True ): with dpg.group(width=600): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis,scale=dpg.mvPlotScale_Time,label='Days',tag=f"{tab_tag}_xaxis") y_axis = dpg.add_plot_axis(dpg.mvYAxis,label='Number of Events',tag = f"{tab_tag}_yaxis") for name, events in event_summary.items(): name = getUpdatedEventName(name) # x = [utl.get_timestamp_from_dt(key) for key in events.keys()] x = [(utl.parse_datetime(key)+daylight_savings).timestamp() for key in events.keys()] y = [events[key] for key in events.keys()] dpg.add_bar_series(x,y,label=name,parent=y_axis,weight=200,tag=f"{name}_{tab_tag}") b1 = dpg.add_button(label = 'Export Plot Figure',width=600,callback = filename_window,user_data =('Events', plot_tag, y_axis,0)) b2 = dpg.add_button(label='Export Signals',width=600,callback=filename_window,user_data=(y_axis,plot_tag,'Events')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2) with dpg.group(width=300): dpg.add_text('Type of Plot:') dpg.add_radio_button(items=['Days','Hours'],tag=f"axis_{tab_tag}",callback=toggle_plot,user_data='Days') plot_tag = f"{tab_tag}_pie_plot" with dpg.plot(label = 'Event Count',height=300,width=300,tag = plot_tag): dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) x_axis = dpg.add_plot_axis(dpg.mvXAxis,no_gridlines=True,no_tick_labels=True,no_tick_marks=True) y_axis = dpg.add_plot_axis(dpg.mvYAxis,no_gridlines=True,no_tick_labels=True,no_tick_marks=True) labels = [getUpdatedEventName(e) for e in list(event_summary.keys())] dpg.add_pie_series( x=0.0, y=0.0, # (x, y) center coordinates radius=3.0, # relative radius of the pie values=[len(event_summary[key]) for key in event_summary.keys()], # numerical values labels=labels, # corresponding labels normalize=True, # ensures the values sum to 1 parent=y_axis, label='Events', tag=f"{tab_tag}_pie" ) b1 = dpg.add_button(label = 'Export Plot Figure',width=300,callback = filename_window,user_data =('Pie', plot_tag, y_axis,0)) b2 = dpg.add_button(label='Export Signals',width=300,callback=filename_window,user_data=(y_axis,plot_tag,'Pie')) add_exporting_tag(extract_window_id(tab_tag),plot=b1,signal=b2)
[docs]def event_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags): app_state['windows'][window_id]['Data'][tab_tag]['Signals'] = {'left': [], 'right': []} dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) if not isinstance(signals, dict) and len(signals)==0: return if isinstance(signals, dict): dpg.add_text('Event Summary', parent=tab_tag) show_event_summary(mode,tab_tag,plot_tag) return else: dpg.add_text(f"Total events: {len(signals)}", parent=tab_tag, tag = f"{tab_tag}_text_recordings") initial, final = DH.getEventsElectrodes(app_state['windows'][window_id]['Data']) power, tables, all_mean = [], [], {} bands = getBandsByTab(tab_tag) max_p = 0 if not isinstance(initial, str): for ch in initial: if ch in electrodes_names.keys(): ch = electrodes_names[ch] else: electrodes_names[ch] = ch dpg.add_text(f"Electrodes | Left: {electrodes_names[initial[0]]} | Right: {electrodes_names[initial[1]]}", parent=tab_tag) else: og = utl.after_point(signals[0]['Left']['Channel']) electrodes_names[og] = og dpg.add_text(f'{electrodes_names[og]}', parent=tab_tag) with dpg.group(label = f"{tab_tag}_group", parent=tab_tag, horizontal=True ): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP Power (µVp)", tag=f"y_axis_{tab_tag}") for idx, signal in enumerate(signals): date = utl.parse_datetime(signal['DateInitial']) for side in ['Left', 'Right']: sign = signal[side] name = getUpdatedEventName(signal['EventName']) label = f"day {date.day:02d}/{date.month:02d}/{date.year} | {date.hour:02d}:{date.minute} | {side} | {name}" x = sign['X'] y = sign['Y'] # Create a unique series tag for each side series_tag = f"{tab_tag}_series_{idx}_{side.lower()}" while len(app_state['windows'][window_id]['Data'][tab_tag]['Signals'][side.lower()]) <= idx: app_state['windows'][window_id]['Data'][tab_tag]['Signals'][side.lower()].append(None) # Fill with placeholders app_state['windows'][window_id]['Data'][tab_tag]['Signals'][side.lower()][idx] = sign # Add the line series for each side l = dpg.add_line_series(x, y, label=label, parent=f"y_axis_{tab_tag}", tag=series_tag)#, fill=[1.0, 0, 0, 0.5]) --> define transparency? # dpg.bind_item_theme(l, 'default_line_thickness_theme') signal_tags[series_tag] = True # Track visibility tab_label = tab_tag p = sign power.append(y) max_p = max(max_p,np.max(p['Y'])) series_label = series_tag if not dpg.does_item_exist(f"{tab_label}_grouped"): dpg.add_tree_node(tag=f"{tab_label}_grouped", label="Grouped Streams", parent=tab_label, default_open=False ) if not dpg.does_item_exist(f"{tab_label}_{side}_all_mean"): dpg.add_collapsing_header(label = f'All Mean Power - {side}', tag =f"{tab_label}_{side}_all_mean", parent = f"{tab_label}_grouped") tables.append(f"{tab_label}_{side}_all_mean_table") if not dpg.does_item_exist(f"{tab_label}_{side}_{name}"): dpg.add_collapsing_header(label = f"All {name}s - {side}", tag =f"{tab_label}_{side}_{name}", parent = f"{tab_label}_grouped") tables.append(f"{tab_label}_{side}_{name}_table") if not dpg.does_item_exist(f"{tab_label}_individuals"): dpg.add_tree_node(tag=f"{tab_label}_individuals", label="Individual Streams", parent=tab_label, default_open=False ) if not dpg.does_item_exist(f"{tab_label}_{series_label}"): header = dpg.get_item_label(series_label) dpg.add_collapsing_header(label = header, tag =f"{tab_label}_{series_label}", parent = f"{tab_label}_individuals") else: dpg.delete_item(f"{tab_label}_{series_label}", children_only=True) #TABLE CONTENT for band in bands.items(): info = ft.extract_psd_features(p, band,bands=bands) temp = info['Mean Power (mean, std. dev)'] # temp = info['Peak Power (power, frequency)'] if not isinstance(temp,(int,float)): all_mean[band[0]] = temp[-1] else: all_mean[band[0]]=temp create_table(f"{tab_label}_{series_label}",f"{tab_label}_{series_label}_table", info, trial_label=band[0]) create_table(f"{tab_label}_{side}_all_mean",f"{tab_label}_{side}_all_mean_table", all_mean, trial_label=header) create_table(f"{tab_label}_{side}_{name}",f"{tab_label}_{side}_{name}_table", all_mean, trial_label=header) tables.append(f"{tab_label}_{series_label}_table") if not dpg.does_item_exist(f"{tab_label}_mean_metric"): dpg.add_collapsing_header(label = 'MEAN POWER', tag =f"{tab_label}_mean_metric", parent = tab_label) else: dpg.delete_item(f"{tab_label}_mean_metric", children_only=True) mean_power, minus, plus = getMeanActiveSeries(window_id,tab_tag,signal_tags) s = dpg.add_shade_series(x,y1=minus,y2=plus,label = 'Std. Dev', parent=f"y_axis_{tab_tag}",tag=f"{tab_tag}_std") dpg.bind_item_theme(s,create_alpha_theme()) m = dpg.add_line_series(x, mean_power, label="Mean Power", parent=f"y_axis_{tab_tag}", tag=f"{tab_tag}_mean") dpg.bind_item_theme(m, change_line_thickness(5)) mean_power = {'Y': mean_power, 'X': p['X']} for band in bands.items(): info = ft.extract_psd_features(mean_power, band,bands=bands) create_table(f"{tab_label}_mean_metric",f"{tab_label}_mean_metric_table", info, trial_label=band[0]) tables.append(f"{tab_label}_mean_metric_table") toggleDisplay2(signals,signal_tags,window_id,tab_tag,tables,plot_tag)
[docs]def timeline_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) data = app_state['windows'][window_id]['Data'] app_state['windows'][window_id]['Data'][tab_tag] = {'Signals': []} flag, mean = False, [] evt = DH.getEventSummary(data) events = DH.getSignals(data,'Events') ocd_events = np.unique([event['EventName'] for event in events]) ocd_events = [getUpdatedEventName(o) for o in ocd_events.tolist()] if 'Eventos' in evt.keys(): with dpg.group(parent=tab_tag, horizontal=True, tag = f"{tab_tag}_ec"): pass dpg.add_text(f"Total days: {len(signals)}", parent=f"{tab_tag}_ec") for e in evt['Eventos']: name = getUpdatedEventName(e['EventName']) event = f"| {name} Count: {e['EventCount']}" dpg.add_text(event, parent= f"{tab_tag}_ec") days = [] with dpg.group(parent=tab_tag, horizontal=True): dpg.add_text('Events Legend: ') for oc in ocd_events: color_id = list(ocd_events).index(oc) ev = dpg.add_text(f"{oc} ") dpg.add_text("|") theme = change_text_color(color_id) dpg.bind_item_theme(ev, theme) with dpg.group(parent=tab_tag, horizontal=True): dpg.add_text('Signals Legend: ') left = dpg.add_text("Left ") dpg.add_text("|") right = dpg.add_text(" Right") lef = change_text_color(-1) dpg.bind_item_theme(left, lef) ri = change_text_color(-3) dpg.bind_item_theme(right, ri) initial, _ = DH.getEventsElectrodes(data) left, right = DH.info_electrodes(data,version='Initial'), DH.info_electrodes(data,version='Initial',side=1) if not isinstance(initial, str): e1,e2 = getUpdatedElectrodeName(initial[0]), getUpdatedElectrodeName(initial[1]) dpg.add_text(f"Sensing Electrodes | Left: {e1} | Right: {e2}", parent=tab_tag) if 'PSD' in left.keys(): f = [] for stim in [left,right]: freq = stim['Sense_frequency'] f1, f2 = float(np.round(freq-2.5,2)),float(np.round(freq+2.5,2)) f.append([f1,f2]) dpg.add_separator(parent=tab_tag) with dpg.group(horizontal=True, parent=tab_tag): l = dpg.add_text("Left") dpg.bind_item_theme(l, change_text_color(-1)) dpg.add_text(f"Sensing Band: {f[0]} Hz |") l = dpg.add_text("Right") dpg.bind_item_theme(l, change_text_color(-3)) dpg.add_text(f"Sensing Band: {f[1]} Hz") else: e = getUpdatedElectrodeName(initial) dpg.add_text(f'{e}', parent=tab_tag) ploted_colors = [] with dpg.group(label = f"{tab_tag}_group" , parent=tab_tag, horizontal=True): dpg.add_group(width=600, tag=f"{tab_tag}_plot_group", horizontal=False) with dpg.plot(label=f"{mode} Signals", height=400, width=800, tag=plot_tag, parent=f"{tab_tag}_plot_group"): # dpg.bind_colormap(plot_tag, 0) legend = dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label='Time (MM/DD/YYYY UTC)', tag=f"x_axis_{tab_tag}", scale = dpg.mvPlotScale_Time) y_left = dpg.add_plot_axis(dpg.mvYAxis, label="Mean LFP Power (µVp)", tag=f"y_axis_{tab_tag}_left") #y_right = dpg.add_plot_axis(dpg.mvYAxis, label="Stimulation Amplitude (mA)", tag=f"y_axis_{tab_tag}_right") dpg.set_axis_limits_auto(y_left) for idx, signal in enumerate(signals): app_state['windows'][window_id]['Data'][tab_tag]['Signals'].append(signal) stim_info = DH.info_electrodes(app_state['windows'][window_id]['Data'])['Sense_frequency'] # date = utl.parse_datetime(signal['Date']) date = signal['Date'] extra = DH.correctMissingTimeline(data, signal,'X_missing')[0] if extra['Right']['X_missing'] != [] and flag == False: flag = True for side in ['Left','Right']: sign = signal[side] | {'Sense': stim_info} date = signal['Date'] days.append(date) label_left = f"{side} - day {date}" try: x = [utl.convert_to_timestamp(val) for val in sign['X']] y = [float(val) for val in sign['Y']] except ValueError as e: print(f"Error converting data for {label_left}: {e}") continue # LFP power series_tag = f"{tab_tag}_series_{idx}_{side.lower()}" l=dpg.add_line_series(x, y, parent=y_left, tag=series_tag ,label=label_left) if side=='Left': color_id=-1 else: color_id=-3 ploted_colors.append(colors[color_id]) th = change_line_color(color_id) dpg.bind_item_theme(l, th) signal_tags[series_tag] = True # Track visibility info, tables = calculate_timeline_features(tab_tag,series_tag) mean.append(info) mean_dict = {} for key in mean[0].keys(): values = [m[key] for m in mean] if isinstance(values[0], str): #its datetime in string values=[utl.get_timestamp_from_dt(t) for t in values] mean_value = utl.extract_time(pre.get_date_from_ts(np.mean(values))) mean_value = {key: mean_value} else: mean_value = {key: np.mean(values,axis=0)} mean_dict = mean_dict | mean_value create_table(f"{tab_tag}_header",f"{tab_tag}_table", mean_dict, trial_label='Mean') days = sorted(np.unique(days).tolist()) sides = ['Left','Right'] for event in events: x_event = utl.convert_to_timestamp(event['DateInitial']) if event['DateInitial'][:10] < signals[0][side]['X'][0][:10]: continue label = getUpdatedEventName(event['EventName']) color = list(ocd_events).index(label) e = dpg.add_inf_line_series(x=[x_event], label=label, parent = y_left) theme = change_inf_line_color(color) ploted_colors.append(colors[color]) dpg.bind_item_theme(e, theme) signal_tags[e] = True dpg.configure_item(legend, show=False) with dpg.group(label = f"{tab_tag}_buttons", parent=f"{tab_tag}_group",): toggleVisibility(signal_tags,parent=tab_tag, group=f"{tab_tag}_buttons", user_data = window_id) with dpg.group(horizontal=True): with dpg.child_window(height=cwh, width=cww,horizontal_scrollbar=True): dpg.add_text('By Day: ') for day in days: toggleType(signal_tags,parent=f"{tab_tag}_buttons", user_data=(day, window_id, tab_tag)) #check day with dpg.child_window(height=cwh, width=cww,horizontal_scrollbar=True): dpg.add_text('By Hemisphere: ') for day in sides: toggleType(signal_tags,parent=f"{tab_tag}_buttons", user_data=(day, window_id, tab_tag)) #check hemisphere if len(ocd_events)>1: with dpg.child_window(height=cwh, width=cww,horizontal_scrollbar=True): dpg.add_text('By Event: ') for ocd in ocd_events: toggleType(signal_tags,parent=f"{tab_tag}_buttons", user_data=(ocd, window_id, tab_tag)) #check hemisphere dpg.add_separator() b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,callback = filename_window,user_data =('2D', plot_tag, y_left,ploted_colors),tag=f"{y_left}_export_plot") b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_left,plot_tag,'Timeline')) add_exporting_tag(window_id,plot=b1,signal=b2) dpg.add_separator() with dpg.group(horizontal=True): dpg.add_slider_float(label = 'Remove Outlier', callback=RemoveTimelineOutliers,default_value=3.0,user_data=(tab_tag,signal_tags), width=100, min_value=0,max_value=10) dpg.add_button(label='Recalculate Metrics', callback = recalculate_timeline,user_data=(tab_tag,list(signal_tags.keys()),tables)) if flag == True: dpg.add_separator() a = dpg.add_text('Warning: There are missing points in Timeline', tag=f"{tab_tag}_wmp", parent=f"{tab_tag}_buttons") dpg.bind_item_theme(a,change_text_color(1)) dpg.add_checkbox(label='Show missing points',parent=f"{tab_tag}_buttons", default_value=False, callback=changeTimelinePlot, user_data=(signal_tags, signals, data, y_left), tag=f"{tab_tag}_missing_points") add_timeline_features_buttons(tab_tag, signal_tags)
[docs]def setup_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags): sense, calibration, artifacts = signals def setup_off_tab(): #SENSE CHANNEL TEST - STIMULATION OFF tab_tag_off = tab_tag + "_OFF" app_state['windows'][window_id]['Data'][tab_tag_off]= {'Signals': []} if dpg.does_item_exist(tab_tag_off): dpg.delete_item(tab_tag_off) dpg.add_tab(label='Setup OFF', tag=tab_tag_off, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag_off, horizontal=True): # STIM OFF signal_tags = {} plot_tag = f"plot_{tab_tag_off}" with dpg.group(): # First plot (Stimulation OFF) with dpg.plot(label=f"{mode} SETUP - Stimulation OFF", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast, no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag_off}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag_off}") parent = f"y_axis_{tab_tag_off}" plot_td(sense, parent, tab_tag_off, signal_tags, plot_tag) toggleDisplay(sense, signal_tags, window_id, tab_tag_off,plot_tag) add_filters_buttons(tab_tag_off,signal_tags) add_features_buttons(tab_tag_off, signal_tags) def setup_on_tab(): #CALIBRATION TEST - STIMULATION OFF tab_tag_on = tab_tag + "_ON" if dpg.does_item_exist(tab_tag_on): dpg.delete_item(tab_tag_on) app_state['windows'][window_id]['Data'][tab_tag_on]= {'Signals': []} dpg.add_tab(label='Setup ON', tag=tab_tag_on, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag_on, horizontal=True): # STIM ON signal_tags = {} plot_tag = f"plot_{tab_tag_on}" with dpg.group(): # Second plot (Stimulation ON) with dpg.plot(label=f"{mode} SETUP - Stimulation ON", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag_on}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag_on}") parent = f"y_axis_{tab_tag_on}" plot_td(calibration, parent, tab_tag_on, signal_tags) toggleDisplay(calibration, signal_tags, window_id, tab_tag_on,plot_tag) add_filters_buttons(tab_tag_on,signal_tags) add_features_buttons(tab_tag_on, signal_tags) def setup_artifacts(): types_artifacts, channels = [], [] tab_tag_art = tab_tag + "_ARTIFACTS" if dpg.does_item_exist(tab_tag_art): dpg.delete_item(tab_tag_art) app_state['windows'][window_id]['Data'][tab_tag_art]= {'Signals': []} bands = getBandsByTab(tab_tag_art) dpg.add_tab(label='Setup ARTIFACTS', tag=tab_tag_art, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag_art, horizontal=True): # STIM ON signal_tags, tables = {}, [] plot_tag = f"plot_{tab_tag_art}" with dpg.plot(label=f"{mode} SETUP - Artifacts", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)") #tag=f"{tab_label}_x_axis" y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="LFP Power (µVp)", tag=f"y_axis_{tab_tag_art}") for idx, signal in enumerate(artifacts): artifact = signal['Artifact'] label = f"{electrodes_names[signal['Channel']]} | {artifact}" if isinstance(artifact,str): types_artifacts.append(f"{extract_artifact_type(artifact).upper()} Artifact") channel,side = extract_channel_hemisphere(electrodes_names[signal['Channel']].upper()) channels.append(channel) x = signal['X'] y = signal['Y'] series_tag = f"{tab_tag_art}_series_{idx}" while len(app_state['windows'][window_id]['Data'][tab_tag_art]['Signals']) <= idx: app_state['windows'][window_id]['Data'][tab_tag_art]['Signals'].append(None) # Fill with placeholders app_state['windows'][window_id]['Data'][tab_tag_art]['Signals'][idx] = signal # Add the line series for each side dpg.add_line_series(x, y, label=label, parent=f"y_axis_{tab_tag_art}", tag=series_tag)#, fill=[1.0, 0, 0, 0.5]) --> define transparency? signal_tags[series_tag] = True # Track visibility if not dpg.does_item_exist(f"{tab_tag_art}_hemisphere"): dpg.add_tree_node(tag=f"{tab_tag_art}_hemisphere", label="By Hemisphere", parent=tab_tag_art, default_open=False ) if not dpg.does_item_exist(f"{tab_tag_art}_channel"): dpg.add_tree_node(tag=f"{tab_tag_art}_channel", label="By Channel", parent=tab_tag_art, default_open=False ) if not dpg.does_item_exist(f"{tab_tag_art}_individual"): dpg.add_tree_node(tag=f"{tab_tag_art}_individual", label="Individual Streams", parent=tab_tag_art, default_open=False ) if not dpg.does_item_exist(f"{tab_tag_art}_{side}"): dpg.add_collapsing_header(label = f'{side} HEMISPHERE', tag =f"{tab_tag_art}_{side}", parent = f"{tab_tag_art}_hemisphere") if not dpg.does_item_exist(f"{tab_tag_art}_{channel}_{side}"): dpg.add_collapsing_header(label = f"{channel}_{side}", tag =f"{tab_tag_art}_{channel}_{side}", parent = f"{tab_tag_art}_channel") if not dpg.does_item_exist(f"{series_tag}_header"): dpg.add_collapsing_header(tag=f"{series_tag}_header", parent=f"{tab_tag_art}_individual",label=label) for band in bands.items(): info = ft.extract_psd_features(signal, band,bands=bands) create_table(f"{series_tag}_header",f"{series_tag}_table", info, trial_label=band[0]) create_table(f"{tab_tag_art}_{side}",f"{tab_tag_art}_{side}_table", info, trial_label=f"{band[0]} | label") create_table(f"{tab_tag_art}_{channel}_{side}",f"{tab_tag_art}_{channel}_{side}_table", info, trial_label=band[0]) for table in [f"{series_tag}_table",f"{tab_tag_art}_{side}_table",f"{tab_tag_art}_{channel}_{side}_table"]: tables.append(table) mean_power = pre.MeanSignal(artifacts) std = pre.StdDevSignal(artifacts) minus,plus = mean_power-std,mean_power+std s = dpg.add_shade_series(x,y1=minus,y2=plus,label='Std. Dev', parent=y_axis, tag=f"{tab_tag_art}_std") dpg.bind_item_theme(s,create_alpha_theme()) m = dpg.add_line_series(x, mean_power, label="Mean Power", parent=y_axis, tag=f"{tab_tag_art}_mean") dpg.bind_item_theme(m, change_line_thickness(5)) mean_power = {'Y': mean_power, 'X':x} if not dpg.does_item_exist(f"{tab_tag_art}_mean_metric"): dpg.add_collapsing_header(tag=f"{tab_tag_art}_mean_metric",parent=tab_tag_art,label = 'Mean') for band in bands.items(): info = ft.extract_psd_features(mean_power, band,bands=bands) create_table(f"{tab_tag_art}_mean_metric",f"{tab_tag_art}_mean_metric_table", info, trial_label=band[0]) tables.append(f"{tab_tag_art}_mean_metric_table") tables = np.unique(tables).tolist() toggleDisplay(artifacts,signal_tags,window_id,tab_tag_art,plot_tag,tables=tables) if sense: setup_off_tab() if calibration: setup_on_tab() if artifacts: setup_artifacts()
[docs]def survey_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags): # 'Recordings Time Domain' dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) dpg.add_text(f"Total recordings: {len(signals)}", parent=tab_tag) with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" plot_td(signals,parent,tab_tag,signal_tags) toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags) # 'Recordings PSDs' new_tab_tag = f"{tab_tag}_psd" new_signals = getDatabyTag(tab_tag)[mode]['LFPMontage'] new_plot_tag = f"{plot_tag}_psd" bands = getBandsByTab(tab_tag) app_state['windows'][window_id]['Data'][new_tab_tag]= {'Signals': []} dpg.add_tab(label=f"{mode} PSD", tag=new_tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) dpg.add_text(f"Total recordings: {len(signals)}", parent=new_tab_tag) line_tags, channels = [], [] child = dpg.add_child_window(height=100, width=-1, parent=new_tab_tag) with dpg.group(parent=new_tab_tag, horizontal=True): signal_tags, colors = {}, [] with dpg.plot(label=f"{mode} PSD Signals", height=400, width=600, tag=new_plot_tag): dpg.bind_colormap(new_plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)", tag=f"x_axis_{new_tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µVp)", tag=f"y_axis_{new_tab_tag}") parent = f"y_axis_{new_tab_tag}" # plot_td(new_signals,parent,new_tab_tag,signal_tags) for _,signal in enumerate(new_signals): side = utl.after_point(signal['Hemisphere']) f = signal['LFPFrequency'] m = signal['LFPMagnitude'] channel = utl.after_point(signal['SensingElectrodes']) line_label = f"{channel} | {side}" line_tag = f"{new_tab_tag}_series_{_}" line_tags.append(line_tag) channels.append(channel) app_state['windows'][window_id]['Data'][new_tab_tag]['Signals'].append(signal) artifact = utl.after_point(signal['ArtifactStatus']) dpg.add_line_series(f,m,label=line_label,parent=parent, tag = line_tag) colors.append(dpg.get_colormap_color(0,_)) tt = dpg.add_text(f"{channel}_{side}: {artifact}", parent=child) if artifact != 'ARTIFACT_NOT_PRESENT': dpg.bind_item_theme(tt, change_text_color(1)) signal_tags[line_tag] = True if 'PeakFrequencyInHertz' in signal: peak = signal['PeakFrequencyInHertz'] peak_m = m[f.index(peak)] dpg.add_scatter_series([peak],[peak_m], parent=parent, tag =f"{line_tag}_peak") dpg.bind_item_theme(f"{line_tag}_peak",change_plot_color(dpg.get_colormap_color(0,_))) colors.append(dpg.get_colormap_color(0,_)) line_tags.append(f"{line_tag}_peak") if not dpg.does_item_exist(f"side_{new_tab_tag}"): dpg.add_tree_node(label = f"By Hemisphere", tag =f"side_{new_tab_tag}", parent = new_tab_tag,default_open=False) if not dpg.does_item_exist(f"ind_{new_tab_tag}"): dpg.add_tree_node(label = f"Individual Streams", tag =f"ind_{new_tab_tag}", parent = new_tab_tag,default_open=False) if not dpg.does_item_exist(f"{side}_{new_tab_tag}"): dpg.add_collapsing_header(label = f"{side} Hemisphere", tag =f"{side}_{new_tab_tag}", parent = f"side_{new_tab_tag}") if not dpg.does_item_exist(f"{line_tag}_header"): header = dpg.get_item_label(line_tag) dpg.add_collapsing_header(label = header, tag =f"{line_tag}_header", parent = f"ind_{new_tab_tag}") else: dpg.delete_item(f"{line_tag}_header", children_only=True) #TABLE CONTENT p = {'X': f, 'Y': m} for band in bands.items(): info = ft.extract_psd_features(p, band,bands=bands) create_table(f"{line_tag}_header",f"{line_tag}_header_table", info, trial_label=band[0]) #individual create_table(f"{side}_{new_tab_tag}",f"{side}_{new_tab_tag}_table", info, trial_label=f"{band[0]} | {channel}") #hemisphere # mean, std = getMeanActiveSeries(window_id,new_tab_tag,signal_tags) mean, std = pre.MeanSignal(new_signals,'LFPMagnitude'), pre.StdDevSignal(new_signals,'LFPMagnitude') dev = getDeviation(new_tab_tag) minus,plus = mean-(dev*std),mean+(dev*std) dpg.add_shade_series(f,y1=minus,y2=plus,label='Std. Dev',tag=f"{new_tab_tag}_std",parent=parent) dpg.bind_item_theme(f"{new_tab_tag}_std",create_alpha_theme()) colors.append(dpg.get_colormap_color(0,_+1)) dpg.add_line_series(f,mean,label='Mean Power',parent=parent, tag = f"{new_tab_tag}_mean") dpg.bind_item_theme(f"{new_tab_tag}_mean",change_line_thickness(5)) colors.append(dpg.get_colormap_color(0,_+2)) toggleDisplay(new_signals,signal_tags,window_id,new_tab_tag,new_plot_tag,colors=colors)
[docs]def streaming_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) dpg.add_text(f"Total recordings: {len(signals)}", parent=tab_tag) rdate = signals[0]['Date'] dpg.add_text(f"Day: {rdate.day}/{rdate.month}/{rdate.year}", parent=tab_tag) Channel = signals[0]['Channel'] stimulation = DH.getStreamingStimulation(getDatabyTag(tab_tag),signals[0]['Date']) stim_text = '' if len(stimulation)==1: stim_text=stimulation[0] Channel = getUpdatedElectrodeName(Channel) dpg.add_text(f"Channel: {extract_channel_from_series_tag(Channel).upper()}", parent=tab_tag) dpg.add_text(stim_text, parent=tab_tag) with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" plot_td(signals,parent,tab_tag,signal_tags) #plot stimulation values '''if right!=[]: #there is stimulation values! stim_axis = dpg.add_plot_axis(dpg.mvYAxis2,label='Stimulation Amplitude (mA)', tag = f"y_axis_stim_{tab_tag}",opposite=True) for r, stim in right.items(): left_stim = left[r] s1 = dpg.add_line_series(time[r],stim,parent=stim_axis,tag=f"right_stimulation_{r}_{tab_tag}") s2 = dpg.add_line_series(time[r],left_stim,parent=stim_axis,tag=f"left_stimulation_{r}_{tab_tag}") dpg.bind_item_theme(s1,change_line_color(-1)) dpg.bind_item_theme(s2,change_line_color(-2)) # dpg.bind_item_theme(s1,change_line_thickness(3)) # dpg.bind_item_theme(s2,change_line_thickness(3))''' toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) dpg.add_button(label='Insert Streaming Events', parent=f"{tab_tag}_buttons", callback = insert_events, user_data=(window_id, tab_tag, signal_tags),width=-1) # if len(stimulation)>1: # dpg.add_checkbox(label='Plot Stimulation Amplitude', callback=plot_streaming_stimulation,user_data=(tab_tag, [stimulation], plot_tag),parent=f"{tab_tag}_buttons") add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags)
[docs]def indefinite_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) dpg.add_text(f"Total recordings: {len(signals)}", parent=tab_tag, tag = f'{tab_tag}_info') with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" plot_td(signals,parent,tab_tag,signal_tags) toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) with dpg.group(parent=tab_tag, tag = f"{tab_tag}_add_events", horizontal=True): dpg.add_button(label='Insert Streaming Events', parent=f"{tab_tag}_add_events", callback = insert_events, user_data=(window_id, tab_tag, signal_tags)) add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags)
[docs]def impedance_tab(Data, tab_tag): window_id = extract_window_id(tab_tag) mode = extract_mode(tab_tag) status, l, r = DH.getImpedance(Data) impedance = DH.getImpedance(Data) if not isinstance(impedance, str): impedance_status, l, r = impedance else: impedance_status = impedance l=False def build_result_table(parent, session_data): # Collect unique electrodes electrodes1 = sorted(set(entry["Electrode1"] for entry in session_data)) electrodes2 = sorted(set(entry["Electrode2"] for entry in session_data)) # Build a lookup dictionary result_dict = { (entry["Electrode2"], entry["Electrode1"]): entry["ResultValue"] for entry in session_data } table_tag = f"{parent}_table" with dpg.table(tag=table_tag,header_row=True, borders_innerH=True, borders_innerV=True, borders_outerH=True, borders_outerV=True, resizable=True, policy=dpg.mvTable_SizingStretchProp): # Header row dpg.add_table_column(label="SenSight") for e1 in electrodes1: new_label = utl.after_underscore(e1) if new_label==e1: new_label=utl.after_point(e1) dpg.add_table_column(label=f"{new_label} (Ohm)") # Rows for e2 in electrodes2: with dpg.table_row(): dpg.add_text(utl.after_underscore(e2)) for e1 in electrodes1: val = result_dict.get((e2, e1), "") if e2==e1: val = '-' if val == "": val = result_dict.get((e1, e2), "") dpg.add_text(str(val)) if not dpg.does_item_exist(f"{parent}_save"): b1 = dpg.add_button(label='Save Table', tag = f"{parent}_save", callback=filename_window, user_data=(table_tag, 'csv'), parent=parent) add_exporting_tag(window_id,table=b1) def build_result_bar_graph(title, session_data, parent): unique_results = {} for entry in session_data: e1 = utl.after_underscore(utl.after_point(entry["Electrode1"])) e2 = utl.after_underscore(utl.after_point(entry["Electrode2"])) value = entry["ResultValue"] key = tuple(sorted([e1, e2])) if key not in unique_results and e1 != e2: try: unique_results[key] = float(value) except (ValueError, TypeError): unique_results[key] = 0.0 # Fallback if non-numeric labels = [f"{e1}-\n{e2}" for (e1, e2) in unique_results.keys()] if 'Monopolar' in title: labels = [f"{_}" for (_, e2) in unique_results.keys()] values = list(unique_results.values()) # Create the plot plot_tag = f"{parent}_plot" with dpg.group(horizontal=True, parent=parent): with dpg.plot(label=title, height=400, width=700, tag = plot_tag): if title=='Monopolar': x_label = "Case - Electrode Pair" else: x_label = "Electrode Pair" x_axis = dpg.add_plot_axis(dpg.mvXAxis, label=x_label) y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Impedance (Ohm)") ticks = tuple((label, float(i)) for i, label in enumerate(labels)) dpg.set_axis_ticks(x_axis, ticks) dpg.set_item_user_data(x_axis,ticks) #had to manually set ticks to user_data cause dpg doesnt properly save info to memory, only GUI for i, val in enumerate(values): dpg.add_bar_series([float(i)], [val], parent=y_axis) dpg.bind_colormap(plot_tag, dpg.mvPlotColormap_Viridis) b1 = dpg.add_button(label = 'Export Plot Figure', callback = filename_window,user_data =('Bar', plot_tag, y_axis,dpg.mvPlotColormap_Viridis),width=-1) add_exporting_tag(extract_window_id(tab_tag),plot=b1) with dpg.tab(tag = tab_tag, label = mode, parent=f"tab_bar_window_{window_id}", closable=True): dpg.add_text(f"Impedance Status: {impedance_status}", parent=tab_tag, show=True) with dpg.collapsing_header(label='Monopolar Left',tag = f"{tab_tag}_ml"): build_result_table(f"{tab_tag}_ml", l["Monopolar"]) build_result_bar_graph("Monopolar Left", l["Monopolar"],f"{tab_tag}_ml") with dpg.collapsing_header(label='Monopolar Right',tag = f"{tab_tag}_mr"): build_result_table(f"{tab_tag}_mr", r["Monopolar"]) build_result_bar_graph("Monopolar Right", r["Monopolar"],f"{tab_tag}_mr") with dpg.collapsing_header(label='Bipolar Left',tag = f"{tab_tag}_bl"): build_result_table(f"{tab_tag}_bl", l["Bipolar"]) build_result_bar_graph("Bipolar Left", l["Bipolar"],f"{tab_tag}_bl") with dpg.collapsing_header(label='Bipolar Right',tag = f"{tab_tag}_br"): build_result_table(f"{tab_tag}_br", r["Bipolar"]) build_result_bar_graph("Bipolar Right", r["Bipolar"],f"{tab_tag}_br")
[docs]def toggle(signals, tab_tag, group_parent): channels, days = [], [] for signal in signals: channels.append(signal['Channel']) days.append(str(signal['Date'])[:10]) days = sorted(np.unique(days).tolist()) channels = np.unique(channels).tolist() def tog(sender,app_data,user_data): children = dpg.get_item_children(f"y_axis_{tab_tag}")[1][:-2] active = [dpg.get_item_label(child) for child in children] all_mean = [] label = dpg.get_item_label(sender) for tag in active: stream = children[active.index(tag)] if label in tag: dpg.configure_item(stream,show=app_data) if dpg.get_item_configuration(stream)['show']==True: all_mean.append(dpg.get_value(stream)[1]) mean = np.nanmean(all_mean,axis=0) std = np.nanstd(all_mean,axis=0) minus,plus = mean-std,mean+std dpg.configure_item(f"{tab_tag}_std",y1=minus,y2=plus) dpg.configure_item(f"{tab_tag}_mean",y=mean) with dpg.group(parent=group_parent): sides = ['LEFT', 'RIGHT'] groups = { 'Days': days, 'Channels': channels, 'Hemisphere': sides } for name,group in groups.items(): if len(group)>1: with dpg.child_window(width=150, height=120): dpg.add_text(f'By {name}:') for item in group: dpg.add_checkbox(label=item,callback=tog,default_value=True)
[docs]def electrode_tab(Data,tab_tag): window_id = extract_window_id(tab_tag) try: alpha = DH.info_electrodes(Data) alpha['PSD'] except Exception as e: print(e) return plot_tag = f"{tab_tag}_plot" bands = getBandsByTab(tab_tag) signal_tags, all_mean, mean, tables, signals = [], {}, [], [], [] if dpg.does_item_exist(tab_tag): return dpg.add_tab(label="Sensing PSD", tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag, horizontal=True,tag=f"{tab_tag}_group"): with dpg.plot(label=f"Sensing PSD - {alpha['PSDdate']}", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP Power (µVp)", tag=f"y_axis_{tab_tag}") y_axis = f"y_axis_{tab_tag}" for side in [0,1]: values = DH.info_electrodes(Data,side=side)['PSD'] freq,psd = values['SignalFrequencies'], values['SignalPsdValues'] channel = utl.after_point(values['Channel']) tag = dpg.add_line_series(freq,psd,label=channel,parent=y_axis) signal_tags.append(tag) mean.append(psd) if not dpg.does_item_exist(f"{tab_tag}_{channel}"): dpg.add_collapsing_header(label = channel, tag =f"{tab_tag}_{channel}", parent = tab_tag) else: dpg.delete_item(f"{tab_tag}_{tag}") #TABLE CONTENT sign = { 'X': values['SignalFrequencies'], 'Y': values['SignalPsdValues'], 'Channel': channel, 'Date': DH.getSessionDate(Data) } signals.append(sign) for band in bands.items(): info = ft.extract_psd_features(sign, band,bands=bands) temp = info['Mean Power (mean, std. dev)'] if not isinstance(temp,(int,float)): all_mean[band[0]] = temp[-1] else: all_mean[band[0]]=temp create_table(f"{tab_tag}_{channel}",f"{tab_tag}_{channel}_table", info, trial_label=band[0]) tables.append(f"{tab_tag}_{channel}_table") mean_power, std_power = np.nanmean(mean,axis=0), np.nanstd(mean,axis=0) minus, plus = mean_power-std_power, mean_power+std_power mean_dict = {'X': freq, 'Y':mean_power} s = dpg.add_shade_series(freq,y1=minus,y2=plus,label = 'Std. Dev', parent=y_axis,tag=f"{tab_tag}_std") dpg.bind_item_theme(s,create_alpha_theme()) m = dpg.add_line_series(freq, mean_power, label="Mean Power", parent=y_axis, tag=f"{tab_tag}_mean") dpg.bind_item_theme(m, change_line_thickness(5)) if not dpg.does_item_exist(f"{tab_tag}_mean_metric"): dpg.add_collapsing_header(label = 'MEAN POWER', tag =f"{tab_tag}_mean_metric", parent = tab_tag) else: dpg.delete_item(f"{tab_tag}_mean_metric", children_only=True) for band in bands.items(): info = ft.extract_psd_features(mean_dict, band,bands=bands) create_table(f"{tab_tag}_mean_metric",f"{tab_tag}_mean_metric_table", info, trial_label=band[0]) tables.append(f"{tab_tag}_mean_metric_table") # toggleDisplay(signals,signal_tags,window_id,tab_tag) toggle(signals,tab_tag,f"{tab_tag}_group")
#--------------------------------------COMBINED TABS--------------------------------------------
[docs]def comb_event_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) power, tables = [], [] max_p = 0 bands = getBandsByTab(tab_tag) files = app_state['windows'][window_id]['Files'] all_signals, all_mean, summary = [], {}, True for file in files: data = file['Data'] signals = DH.getSignals(data,mode) if not isinstance(signals,dict): summary = False else: summary = True initial, _ = DH.getEventsElectrodes(data) date = utl.extract_date(DH.getSessionDate(data)) header = f"{date}_{tab_tag}" dpg.add_collapsing_header(tag=header,label=date,parent=tab_tag) if not isinstance(initial, str) and not summary: file_stim = {'Left': DH.info_electrodes(data,version='Initial'), 'Right': DH.info_electrodes(data,version='Initial',side=1)} dpg.add_text(f"Sensing Electrodes until {DH.getSessionDate(data)} | Left: {getUpdatedElectrodeName(initial[0])} | Right: {getUpdatedElectrodeName(initial[1])}", parent=header) a = {} for key, info in file_stim.items(): freq = info['Sense_frequency'] f1, f2 = float(np.round(freq-2.5,2)),float(np.round(freq+2.5,2)) a[key] = [f1,f2] if summary: show_event_summary(mode,tab_tag,plot_tag,files) return plotted = [] with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µVp)", tag=f"y_axis_{tab_tag}") ids, channels = [], [] tab_label = tab_tag for idx, file in enumerate(files): app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag] = {} if mode in file['Modes']: app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag]['Signals'] = [] signals = DH.getSignals(file['Data'],mode) file['Data'][tab_tag]= {'Signals': []} if len(signals) == 0: ids.append(idx) if len(ids) == len(files): return #No file has a single event for idx, file in enumerate(files): app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag] = {} if idx in ids: continue if mode in file['Modes']: app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag]['Signals'] = [] signals = DH.getSignals(file['Data'],mode) file['Data'][tab_tag]= {'Signals': []} rdate = signals[0]['DateInitial'] rdate = utl.parse_datetime(rdate) dpg.add_text(f"Day: {rdate.day}/{rdate.month}/{rdate.year}", parent=tab_tag) for sdx, signal in enumerate(signals): all_signals.append(signal) date = utl.parse_datetime(signal['DateInitial']) if plotted != []: if date in plotted: continue else: plotted.append(date) app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag]['Signals'].append({'left': [], 'right': []}) for side in ['Left', 'Right']: sign = signal[side] channel = getUpdatedElectrodeName(utl.after_point(sign['Channel'])) channels.append(channel) name = getUpdatedEventName(signal['EventName']) label = f"day {date.day:02d}/{date.month:02d}/{date.year} | {date.hour:02d}:{date.minute} | {side} | {name}" x = sign['X'] y = sign['Y'] # Create a unique series tag for each side series_tag = f"{tab_tag}_file_{idx}_series_{sdx}_{side.lower()}" app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag]['Signals'][sdx][side.lower()] = sign # Add the line series for each side dpg.add_line_series(x, y, label=label, parent=y_axis, tag=series_tag)#, fill=[1.0, 0, 0, 0.5]) --> define transparency? signal_tags[series_tag] = True # Track visibility p = sign power.append(y) max_p = max(max_p,np.max(p['Y'])) series_label = series_tag if not dpg.does_item_exist(f"{tab_label}_grouped"): dpg.add_tree_node(tag=f"{tab_label}_grouped", label="Grouped Streams", parent=tab_label, default_open=False ) if not dpg.does_item_exist(f"{tab_label}_{side}_all_mean"): dpg.add_collapsing_header(label = f'All Mean Power - {side}', tag =f"{tab_label}_{side}_all_mean", parent = f"{tab_label}_grouped") tables.append(f"{tab_label}_{side}_all_mean_table") if not dpg.does_item_exist(f"{tab_label}_{side}_{name}"): dpg.add_collapsing_header(label = f"All {name}s - {side}", tag =f"{tab_label}_{side}_{name}", parent = f"{tab_label}_grouped") tables.append(f"{tab_label}_{side}_{name}_table") if not dpg.does_item_exist(f"{tab_label}_{channel}_{name}"): dpg.add_collapsing_header(label = f"All {name}s - {channel}", tag =f"{tab_label}_{channel}_{name}", parent = f"{tab_label}_grouped",show=False) tables.append(f"{tab_label}_{channel}_{name}_table") if not dpg.does_item_exist(f"{tab_label}_individuals"): dpg.add_tree_node(tag=f"{tab_label}_individuals", label="Individual Streams", parent=tab_label, default_open=False ) if not dpg.does_item_exist(f"{tab_label}_{series_label}"): header = dpg.get_item_label(series_label) dpg.add_collapsing_header(label = header, tag =f"{tab_label}_{series_label}", parent = f"{tab_label}_individuals") else: dpg.delete_item(f"{tab_label}_{series_label}", children_only=True) #TABLE CONTENT for band in bands.items(): info = ft.extract_psd_features(p, band,bands=bands) temp = info['Mean Power (mean, std. dev)'] # temp = info['Peak Power (power, frequency)'] if not isinstance(temp,(int,float)): all_mean[band[0]] = temp[-1] else: all_mean[band[0]]=temp create_table(f"{tab_label}_{series_label}",f"{tab_label}_{series_label}_table", info, trial_label=band[0]) create_table(f"{tab_label}_{side}_all_mean",f"{tab_label}_{side}_all_mean_table", all_mean, trial_label=header) create_table(f"{tab_label}_{side}_{name}",f"{tab_label}_{side}_{name}_table", all_mean, trial_label=header) create_table(f"{tab_label}_{channel}_{name}",f"{tab_label}_{channel}_{name}_table", all_mean, trial_label=header) tables.append(f"{tab_label}_{series_label}_table") channels = np.unique(channels).tolist() if len(channels)==1: dpg.delete_item(f"{tab_label}_{channel}_{name}") else: dpg.configure_item(f"{tab_label}_{channel}_{name}",show=True) if not dpg.does_item_exist(f"{tab_label}_mean_metric"): dpg.add_collapsing_header(label = 'MEAN POWER', tag =f"{tab_label}_mean_metric", parent = tab_label) else: dpg.delete_item(f"{tab_label}_mean_metric", children_only=True) mean_power, minus, plus = getMeanActiveSeries(window_id,tab_tag,signal_tags) s = dpg.add_shade_series(x,y1=minus,y2=plus, parent=f"y_axis_{tab_tag}",tag=f"{tab_tag}_std", label='Std. Dev') dpg.bind_item_theme(s,create_alpha_theme()) m = dpg.add_line_series(x, mean_power, label="Mean Power", parent=f"y_axis_{tab_tag}", tag=f"{tab_tag}_mean") dpg.bind_item_theme(m, change_line_thickness(5)) mean_power = {'Y': mean_power, 'X': p['X']} for band in bands.items(): info = ft.extract_psd_features(mean_power, band,bands=bands) create_table(f"{tab_label}_mean_metric",f"{tab_label}_mean_metric_table", info, trial_label=band[0]) tables.append(f"{tab_label}_mean_metric_table") toggleDisplay2(all_signals,signal_tags,window_id,tab_tag,tables,plot_tag)
[docs]def comb_streaming_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) stimulation_list = [] for file in files: if file['Data'][mode]=={}: continue stimulation = DH.getStreamingStimulation(file['Data'],DH.getSessionDate(file['Data'])) stimulation_list.append(stimulation) with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" for idx, file in enumerate(files): if mode in file['Modes']: signals = DH.getSignals(file['Data'],mode) file['Data'][tab_tag]= {'Signals': []} rdate = signals[0]['Date'] channel = extract_channel_from_series_tag(signals[0]['Channel']) dpg.add_text(f"Day: {rdate.day}/{rdate.month}/{rdate.year} | Channel: {channel.upper()}", parent=tab_tag) plot_td(signals,parent,tab_tag,signal_tags,files=idx) toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) # dpg.add_checkbox(label='Plot Stimulation Amplitude', callback=plot_streaming_stimulation,user_data=(tab_tag, stimulation_list, plot_tag),parent=f"{tab_tag}_buttons") with dpg.group(parent=tab_tag, tag = f"{tab_tag}_add_events", horizontal=True): interval_events(tab_tag,'T Before (s)','T After (s)') dpg.add_button(label='Analyze Streaming Events', parent=f"{tab_tag}_add_events", callback = insert_events, user_data=(window_id, tab_tag, signal_tags)) add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags)
[docs]def comb_setup_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags): def setup_plot(mode, tab_tag, type): signals = None plot_tag = f"plot_{tab_tag}" dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): signal_tags = {} with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" for idx, file in enumerate(files): file['Data'][tab_tag]= {'Signals': []} if 'Setup' in file['Modes']: signals = DH.getSignals(file['Data'],'Setup')[type] plot_td(signals,parent,tab_tag,signal_tags, files=idx) toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags) for idx, ext in enumerate(['OFF','ON']): new_mode = mode + ' ' + ext new_tag = tab_tag + '_' + ext if dpg.does_item_exist(new_tag): dpg.delete_item(new_tag) setup_plot(new_mode, new_tag, idx) def setup_artifacts(): types_artifacts, channels, days = [], [], [] tab_tag_art = tab_tag + "_ARTIFACTS" if dpg.does_item_exist(tab_tag_art): dpg.delete_item(tab_tag_art) bands = getBandsByTab(tab_tag_art) dpg.add_tab(label='Setup ARTIFACTS', tag=tab_tag_art, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag_art, horizontal=True): # STIM ON signal_tags, all_artifacts, tables = {}, [], [] plot_tag = f"plot_{tab_tag_art}" with dpg.plot(label=f"{mode} SETUP - Artifacts", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)") #tag=f"{tab_label}_x_axis" y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="LFP Power (µVp)", tag=f"y_axis_{tab_tag_art}") for _, file in enumerate(files): file['Data'][tab_tag_art]= {'Signals': []} date = utl.parse_datetime(file['Data']['Device']['SessionDate']) date = f"{date.day:02d}/{date.month:02d}/{date.year}" days.append(date) if 'Setup' in file['Modes']: artifacts = DH.getSignals(file['Data'],'Setup')[2] for idx, signal in enumerate(artifacts): artifact = signal['Artifact'] all_artifacts.append(signal) label = f"{signal['Channel']} | {artifact}" if extract_type_window(window_id) == 'Combined': label = label + f' {date}' if isinstance(artifact,str): types_artifacts.append(f"{extract_artifact_type(artifact).upper()} Artifact") # channel = electrodes_names[signal['Channel']].upper() channel,side = extract_channel_hemisphere(electrodes_names[signal['Channel']].upper()) channels.append(channel) x = signal['X'] y = signal['Y'] # Create a unique series tag for each side series_tag = f"{tab_tag_art}_series_{idx}_{date}" file['Data'][tab_tag_art]['Signals'].append(signal) # Add the line series for each side dpg.add_line_series(x, y, label=label, parent=f"y_axis_{tab_tag_art}", tag=series_tag)#, fill=[1.0, 0, 0, 0.5]) --> define transparency? signal_tags[series_tag] = True # Track visibility if not dpg.does_item_exist(f"{tab_tag_art}_hemisphere"): dpg.add_tree_node(tag=f"{tab_tag_art}_hemisphere", label="By Hemisphere", parent=tab_tag_art, default_open=False ) if not dpg.does_item_exist(f"{tab_tag_art}_channel"): dpg.add_tree_node(tag=f"{tab_tag_art}_channel", label="By Channel", parent=tab_tag_art, default_open=False ) if not dpg.does_item_exist(f"{tab_tag_art}_individual"): dpg.add_tree_node(tag=f"{tab_tag_art}_individual", label="Individual Streams", parent=tab_tag_art, default_open=False ) if not dpg.does_item_exist(f"{tab_tag_art}_{side}"): dpg.add_collapsing_header(label = f'{side} HEMISPHERE', tag =f"{tab_tag_art}_{side}", parent = f"{tab_tag_art}_hemisphere") if not dpg.does_item_exist(f"{tab_tag_art}_{channel}_{side}"): dpg.add_collapsing_header(label = f"{channel}_{side}", tag =f"{tab_tag_art}_{channel}_{side}", parent = f"{tab_tag_art}_channel") if not dpg.does_item_exist(f"{series_tag}_header"): dpg.add_collapsing_header(tag=f"{series_tag}_header", parent=f"{tab_tag_art}_individual",label=label) for band in bands.items(): info = ft.extract_psd_features(signal, band,bands=bands) create_table(f"{series_tag}_header",f"{series_tag}_table", info, trial_label=band[0]) create_table(f"{tab_tag_art}_{side}",f"{tab_tag_art}_{side}_table", info, trial_label=f"{band[0]} | label") create_table(f"{tab_tag_art}_{channel}_{side}",f"{tab_tag_art}_{channel}_{side}_table", info, trial_label=band[0]) for table in [f"{series_tag}_table",f"{tab_tag_art}_{side}_table",f"{tab_tag_art}_{channel}_{side}_table"]: tables.append(table) mean_power = pre.MeanSignal(all_artifacts) std_dev = pre.StdDevSignal(all_artifacts) minus,plus = mean_power-std_dev,mean_power+std_dev s = dpg.add_shade_series(x, y1=minus,y2=plus, label="Std. Deviation", parent=f"y_axis_{tab_tag_art}", tag=f"{tab_tag_art}_std") m = dpg.add_line_series(x, mean_power, label="Mean Power", parent=f"y_axis_{tab_tag_art}", tag=f"{tab_tag_art}_mean") dpg.bind_item_theme(s,create_alpha_theme()) dpg.bind_item_theme(m, change_line_thickness(5)) mean_power = {'Y': mean_power, 'X':x} if not dpg.does_item_exist(f"{tab_tag_art}_mean_metric"): dpg.add_collapsing_header(tag=f"{tab_tag_art}_mean_metric",parent=tab_tag_art,label = 'Mean') for band in bands.items(): info = ft.extract_psd_features(mean_power, band,bands=bands) create_table(f"{tab_tag_art}_mean_metric",f"{tab_tag_art}_mean_metric_table", info, trial_label=band[0]) tables.append(f"{tab_tag_art}_mean_metric_table") tables = np.unique(tables).tolist() toggleDisplay(all_artifacts,signal_tags,window_id,tab_tag_art,plot_tag=plot_tag,tables=tables) setup_artifacts()
[docs]def comb_survey_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" for idx, file in enumerate(files): if mode in file['Modes']: signals = DH.getSignals(file['Data'],mode) file['Data'][tab_tag]= {'Signals': []} rdate = signals[0]['Date'] dpg.add_text(f"Day: {rdate.day}/{rdate.month}/{rdate.year}", parent=tab_tag) plot_td(signals,parent,tab_tag,signal_tags,files=idx) toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) with dpg.group(parent=tab_tag, tag = f"{tab_tag}_add_events", horizontal=True): interval_events(tab_tag,'T Before (s)','T After (s)') dpg.add_button(label='Analyze Streaming Events', parent=f"{tab_tag}_add_events", callback = insert_events, user_data=(window_id, tab_tag, signal_tags)) add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags) # 'Recordings PSDs' new_tab_tag = f"{tab_tag}_psd" new_plot_tag = f"{plot_tag}_psd" bands = getBandsByTab(tab_tag) app_state['windows'][window_id]['Files'][idx]['Data'][new_tab_tag] = {'Signals': []} dpg.add_tab(label=f"{mode} PSD", tag=new_tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) dpg.add_text(f"Total recordings: {len(signals)}", parent=new_tab_tag) line_tags, channels, colors = [], [], [] child = dpg.add_child_window(height=100, width=-1, parent=new_tab_tag,) with dpg.group(parent=new_tab_tag, label = f"{new_tab_tag}_group", horizontal=True): signal_tags, dates, all_signals = {}, [], [] with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=new_plot_tag): dpg.bind_colormap(new_plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Frequency (Hz)", tag=f"x_axis_{new_tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µVp)", tag=f"y_axis_{new_tab_tag}") parent = f"y_axis_{new_tab_tag}" for idx, file in enumerate(files): if mode in file['Modes']: new_signals = file['Data'][mode]['LFPMontage'] date = file['Data']['Device']['SessionDate'][:10] dates.append(date) for _,signal in enumerate(new_signals): app_state['windows'][window_id]['Files'][idx]['Data'][new_tab_tag]['Signals'].append(signal) all_signals.append(signal) side = utl.after_point(signal['Hemisphere']) f = signal['LFPFrequency'] m = signal['LFPMagnitude'] channel = utl.after_point(signal['SensingElectrodes']) if len(files)>1: line_label = f"{date} | {channel} | {side}" else: line_label = f"{channel} | {side}" # line_tag = f"{file['name']}_{new_tab_tag}_{channel}_{side}" line_tag = f"{new_tab_tag}_file_{idx}_series_{_}" line_tags.append(line_tag) channels.append(channel) artifact = utl.after_point(signal['ArtifactStatus']) dpg.add_line_series(f,m,label=line_label,parent=parent, tag = line_tag) colors.append(dpg.get_colormap_color(0,_*(idx+1))) artifact_info = f"{date}_{tab_tag}" if dpg.does_item_exist(artifact_info): tt = dpg.add_text(f"{channel}_{side}: {artifact}", parent=artifact_info) else: dpg.add_collapsing_header(label = date, tag=artifact_info, parent=child) tt = dpg.add_text(f"{channel}_{side}: {artifact}", parent=artifact_info) if artifact != 'ARTIFACT_NOT_PRESENT': dpg.bind_item_theme(tt, change_text_color(1)) signal_tags[line_tag] = True if 'PeakFrequencyInHertz' in signal: peak = signal['PeakFrequencyInHertz'] peak_m = m[f.index(peak)] dpg.add_scatter_series([peak],[peak_m], parent=parent, tag =f"{line_tag}_peak") dpg.bind_item_theme(f"{line_tag}_peak",change_plot_color(dpg.get_colormap_color(0,_))) colors.append(dpg.get_colormap_color(0,_*(idx+1))) line_tags.append(f"{line_tag}_peak") if not dpg.does_item_exist(f"side_{new_tab_tag}"): dpg.add_tree_node(label = f"By Hemisphere", tag =f"side_{new_tab_tag}", parent = new_tab_tag,default_open=False) if not dpg.does_item_exist(f"channel_{new_tab_tag}"): dpg.add_tree_node(label = f"By Channel", tag =f"channel_{new_tab_tag}", parent = new_tab_tag,default_open=False) if not dpg.does_item_exist(f"ind_{new_tab_tag}"): dpg.add_tree_node(label = f"Individual Streams", tag =f"ind_{new_tab_tag}", parent = new_tab_tag,default_open=False) if not dpg.does_item_exist(f"{side}_{new_tab_tag}"): dpg.add_collapsing_header(label = f"{side} Hemisphere", tag =f"{side}_{new_tab_tag}", parent = f"side_{new_tab_tag}") if not dpg.does_item_exist(f"{channel}_{side}_{new_tab_tag}"): dpg.add_collapsing_header(label = f"{channel} | {side}", tag =f"{channel}_{side}_{new_tab_tag}", parent = f"channel_{new_tab_tag}") if not dpg.does_item_exist(f"{line_tag}_header"): header = dpg.get_item_label(line_tag) dpg.add_collapsing_header(label = header, tag =f"{line_tag}_header", parent = f"ind_{new_tab_tag}") else: dpg.delete_item(f"{line_tag}_header", children_only=True) #TABLE CONTENT p = {'X': f, 'Y': m} for band in bands.items(): info = ft.extract_psd_features(p, band,bands=bands) create_table(f"{line_tag}_header",f"{line_tag}_header_table", info, trial_label=band[0]) #individual create_table(f"{side}_{new_tab_tag}",f"{side}_{new_tab_tag}_table", info, trial_label=f"{band[0]} | {channel} | {date[:10]}") #hemisphere create_table(f"{channel}_{side}_{new_tab_tag}",f"{channel}_{side}_{new_tab_tag}_table", info, trial_label=f"{band[0]} | {date[:10]}") #by channel mean, std = pre.MeanSignal(new_signals,'LFPMagnitude'), pre.StdDevSignal(new_signals,'LFPMagnitude') dev = getDeviation(new_tab_tag) minus,plus = mean-(dev*std),mean+(dev*std) dpg.add_shade_series(f,y1=minus,y2=plus,label='Std. Dev',tag=f"{new_tab_tag}_std",parent=parent) dpg.bind_item_theme(f"{new_tab_tag}_std",create_alpha_theme()) dpg.add_line_series(f,mean,label='Mean Power',parent=parent, tag = f"{new_tab_tag}_mean") dpg.bind_item_theme(f"{new_tab_tag}_mean",change_line_thickness(5)) colors.append(dpg.get_colormap_color(0,_*(idx+1)+1)) #std dev color colors.append(dpg.get_colormap_color(0,_*(idx+1)+2)) #mean color toggleDisplay(new_signals,signal_tags,window_id,new_tab_tag,new_plot_tag,dates=dates,colors=colors)
[docs]def comb_indefinite_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) with dpg.group(parent=tab_tag, label = f"{tab_tag}_group", horizontal=True): with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag): dpg.bind_colormap(plot_tag, 0) dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) dpg.add_plot_axis(dpg.mvXAxis, label="Time (s)", tag=f"x_axis_{tab_tag}") dpg.add_plot_axis(dpg.mvYAxis, label="LFP (µV)", tag=f"y_axis_{tab_tag}") parent = f"y_axis_{tab_tag}" for idx, file in enumerate(files): if mode in file['Modes']: signals = DH.getSignals(file['Data'],mode) file['Data'][tab_tag]= {'Signals': []} rdate = signals[0]['Date'] plot_td(signals,parent,tab_tag,signal_tags,files=idx) else: app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag] = [] toggleDisplay(signals, signal_tags, window_id, tab_tag,plot_tag) with dpg.group(parent=tab_tag, tag = f"{tab_tag}_add_events", horizontal=True): interval_events(tab_tag,'T Before (s)','T After (s)') dpg.add_button(label='Analyze Streaming Events', parent=f"{tab_tag}_add_events", callback = insert_events, user_data=(window_id, tab_tag, signal_tags)) add_filters_buttons(tab_tag,signal_tags) add_features_buttons(tab_tag, signal_tags)
[docs]def comb_timeline_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags): dpg.add_tab(label=mode, tag=tab_tag, parent=f"tab_bar_window_{window_id}", closable=True) files = app_state['windows'][window_id]['Files'] flag = False datas, temp_signals, events_tags, ocd_events, channels, sense = [], [], [], [], [], [] dates = [file['Data']['Device']['SessionDate'][:10] for file in files] if len(dates) != len(np.unique(dates).tolist()): final_index = -1 else: final_index = 10 for file in files: data = file['Data'] initial, _ = DH.getEventsElectrodes(data) date = data['Device']['SessionDate'][:final_index] header = f"{date}_{tab_tag}" if not isinstance(initial,str): channels.append({'Left': electrodes_names[initial[0]], 'Right': electrodes_names[initial[1]]}) dpg.add_collapsing_header(tag=header,label=date,parent=tab_tag) if not isinstance(initial, str): file_stim = {'Left': DH.info_electrodes(data,version='Initial'), 'Right': DH.info_electrodes(data,version='Initial',side=1)} dpg.add_text(f"Sensing Electrodes until {DH.getSessionDate(data)} | Left: {getUpdatedElectrodeName(initial[0])} | Right: {getUpdatedElectrodeName(initial[1])}", parent=header) a = {} for key, info in file_stim.items(): freq = info['Sense_frequency'] f1, f2 = float(np.round(freq-2.5,2)),float(np.round(freq+2.5,2)) a[key] = [f1,f2] sense.append(a) with dpg.group(horizontal=True, parent=header): l = dpg.add_text("Left") dpg.bind_item_theme(l, change_text_color(-1)) dpg.add_text(f"Sensing Band: {sense[-1]['Left']} Hz |") l = dpg.add_text("Right") dpg.bind_item_theme(l, change_text_color(-3)) dpg.add_text(f"Sensing Band: {sense[-1]['Right']} Hz") else: ch = getUpdatedElectrodeName(initial) dpg.add_text(f'{DH.getSessionDate(data)}: {electrodes_names[ch]}', parent=header) evt = DH.getEventSummary(data) if 'Eventos' in evt.keys(): for e in evt['Eventos']: event = f"{getUpdatedEventName(e['EventName'])} Count: {e['EventCount']}" events = DH.getSignals(file['Data'],'Events') for edx, event in enumerate(events): ocd_events.append(getUpdatedEventName(event['EventName'])) days, ploted_colors, all_tables = [], [], [] #Signals legend with dpg.group(parent=tab_tag, horizontal=True): dpg.add_text('Signals Legend: ') left = dpg.add_text("Left ") dpg.add_text("|") right = dpg.add_text(" Right") dpg.bind_item_theme(left, change_text_color(-1)) dpg.bind_item_theme(right, change_text_color(-3)) ocd_events = np.unique(ocd_events).tolist() unique_channels = [channels[idx][side] for idx in range(len(channels)) for side in channels[idx].keys()] unique_channels = np.unique(unique_channels).tolist() all_sensing_bands = [sensei[u] for sensei in sense for u in ['Left','Right']] unique_sense = np.unique(all_sensing_bands).tolist() with dpg.group(parent=tab_tag, horizontal=True): dpg.add_text('Events Legend: ') for count, oc in enumerate(ocd_events): color_id = list(ocd_events).index(oc) ev = dpg.add_text(f"{oc} ") dpg.bind_item_theme(ev, change_text_color(color_id)) if count < len(ocd_events)-1: dpg.add_text("|") with dpg.group(parent=tab_tag, tag = f"{tab_tag}_group", horizontal=True): dpg.add_group(width=600, tag=f"{tab_tag}_plot_group", horizontal=False) with dpg.plot(label=f"{mode} Signals", height=400, width=600, tag=plot_tag, parent=f"{tab_tag}_plot_group"): dpg.bind_colormap(plot_tag, 0) # legend = dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast,no_buttons = True) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Time (MM/DD/YYYY UTC)", tag=f"x_axis_{tab_tag}", scale=dpg.mvPlotScale_Time) y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Mean LFP Power (µVp)", tag=f"y_axis_{tab_tag}") for idx, file in enumerate(files): app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag] = {} if mode in file['Modes']: left, right = sense[idx]['Left'], sense[idx]['Right'] # sensing_band = f"[{np.round(left[0]-2.5,2)},{np.round(left[0]+2.5,2)}]" sensing_band = sense[idx] signals = DH.getSignals(file['Data'],mode) events = DH.getSignals(file['Data'],'Events') file['Data'][tab_tag]= {'Signals': []} rdate = signals[0]['Date'] for sdx, signal in enumerate(signals): extra = DH.correctMissingTimeline(file['Data'], signal,'X_missing')[0] signal = DH.correctMissingTimeline(file['Data'], signal,'X')[0] temp_signals.append(signal) if extra['Right']['X_missing'] != []: datas.append(file['Data']) flag = True else: datas.append(None) signal_tag = f"{tab_tag}_file_{idx}_series_{sdx}" date = signal['Date'][:final_index] app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag]['Signals'].append({'Date':date,'Left': [], 'Right': []}) days.append(date) for side in ['Left', 'Right']: channel = getUpdatedElectrodeName(channels[idx][side]) sign = signal[side] | {'Sense': sense[idx]} label_left = f"{side} - day {date}" try: x = [utl.convert_to_timestamp(val) for val in sign['X']] y = [float(val) for val in sign['Y']] sign['X'] = x except ValueError as e: print(f"Error converting data for {label_left}: {e}") continue # Create a unique series tag for each side series_tag = f"{signal_tag}_{side.lower()}" app_state['windows'][window_id]['Files'][idx]['Data'][tab_tag]['Signals'][sdx][side] = sign labels = [dpg.get_item_label(k) for k in list(signal_tags.keys())] if label_left not in labels: # Add the line series for each side series_ud = [None,None] if len(unique_channels)>1: series_ud[0] = f"{channel}_{side.upper()}" if len(unique_sense)>1: series_ud[1] = sensing_band l = dpg.add_line_series(x, y, label=label_left, parent=y_axis, tag=series_tag,user_data=series_ud)#, fill=[1.0, 0, 0, 0.5]) --> define transparency? if side=='Left':color_id=-1 else: color_id=-3 ploted_colors.append(colors[color_id]) th = change_line_color(color_id) dpg.bind_item_theme(l, th) signal_tags[series_tag] = True # Track visibility _, tables = calculate_timeline_features(tab_tag,series_tag) all_tables.extend(tables) '''if not dpg.does_item_exist(f"{tab_tag}_hemisphere"): dpg.add_tree_node(tag=f"{tab_tag}_hemisphere", label="By Hemisphere", parent=tab_tag, default_open=False ) if not dpg.does_item_exist(f"{tab_tag}_channel") and len(unique_channels)>1: dpg.add_tree_node(tag=f"{tab_tag}_channel", label="By Channel", parent=tab_tag, default_open=False ) if not dpg.does_item_exist(f"{tab_tag}_sensing") and len(unique_sense)>1: dpg.add_tree_node(tag=f"{tab_tag}_sensing", label="By Sensing Band", parent=tab_tag, default_open=False ) if not dpg.does_item_exist(f"{tab_tag}_individual"): dpg.add_tree_node(tag=f"{tab_tag}_individual", label="All Individual Streams", parent=tab_tag, default_open=False ) if not dpg.does_item_exist(f"{tab_tag}_{side}"): dpg.add_collapsing_header(label = f'{side} Hemisphere', tag =f"{tab_tag}_{side}", parent = f"{tab_tag}_hemisphere") if not dpg.does_item_exist(f"{tab_tag}_{channel}") and len(unique_channels)>1: dpg.add_collapsing_header(label = f"{channel}", tag =f"{tab_tag}_{channel}", parent = f"{tab_tag}_channel") if not dpg.does_item_exist(f"{tab_tag}_{sensing_band}") and len(unique_sense)>1: dpg.add_collapsing_header(label = f"{sensing_band} Hz", tag =f"{tab_tag}_{sensing_band}", parent = f"{tab_tag}_sensing") if not dpg.does_item_exist(f"{tab_tag}_header"): dpg.add_collapsing_header(tag=f"{tab_tag}_header", parent=f"{tab_tag}_individual", label = 'Streams Metrics') info = ft.extract_timeline_features(sign) create_table(f"{tab_tag}_header",f"{tab_tag}_table", info, trial_label=label_left) if len(unique_channels)>1: create_table(f"{tab_tag}_{channel}",f"{tab_tag}_{channel}_table", info, trial_label=f"{date} | {sensing_band}") create_table(f"{tab_tag}_{side}",f"{tab_tag}_{side}_table", info, trial_label=f"{date} | {channel}") if len(unique_sense)>1: create_table(f"{tab_tag}_{sensing_band}",f"{tab_tag}_{sensing_band}_table", info, trial_label=f"{date} | {channel}") ''' for edx, event in enumerate(events): x_event = utl.convert_to_timestamp(event['DateInitial']) if event['DateInitial'][:10]<rdate[:10]: continue label = getUpdatedEventName(event['EventName']) e_tag = f"{tab_tag}_{edx}_{idx}" try: color = list(ocd_events).index(label) except Exception: ocd_events.append(label) color = list(ocd_events).index(label) t = dpg.add_inf_line_series(x=[x_event],parent = y_axis, tag =f"{tab_tag}_{edx}_{idx}", label=label) events_tags.append([e_tag,idx]) theme = change_inf_line_color(color) ploted_colors.append(colors[color]) dpg.bind_item_theme(t, theme) signal_tags[t] = True date = file['Data']['Device']['SessionDate'][:final_index] dpg.add_text(f"Total days: {len(days)}", parent=f"{date}_{tab_tag}") dpg.add_text(f"Timezone/offset: {DH.extract_time_offset(data,True)}", parent=f"{date}_{tab_tag}") dpg.add_separator(parent=f"{date}_{tab_tag}") days = sorted(np.unique(days).tolist()) all_tables = np.unique(all_tables).flatten().tolist() sides = ['Left','Right'] with dpg.group(label = f"{tab_tag}_buttons", parent=f"{tab_tag}_group"): toggleVisibility(signal_tags,parent=tab_tag, group=f"{tab_tag}_buttons", user_data = window_id) with dpg.group(horizontal=True): with dpg.child_window(height=cwh, width=cww,horizontal_scrollbar=True): dpg.add_text('By day: ') for day in days: toggleType(signal_tags,parent=f"{tab_tag}_buttons", user_data=(day, window_id, tab_tag)) #check day with dpg.child_window(height=cwh, width=cww,horizontal_scrollbar=True): dpg.add_text('By hemisphere: ') for day in sides: toggleType(signal_tags,parent=f"{tab_tag}_buttons", user_data=(day, window_id, tab_tag)) if len(ocd_events)>1: with dpg.child_window(height=cwh, width=cww,horizontal_scrollbar=True): dpg.add_text('By Event: ') for ocd in ocd_events: toggleType(signal_tags,parent=f"{tab_tag}_buttons", user_data=(ocd, window_id, tab_tag)) #check hemisphere dpg.add_separator() b1 = dpg.add_button(label = 'Export Plot Figure',width=-1,callback = filename_window,user_data =('2D', plot_tag, f"y_axis_{tab_tag}",ploted_colors),tag=f"{y_axis}_export_plot") b2 = dpg.add_button(label='Export Signals',width=-1,callback=filename_window,user_data=(y_axis,plot_tag,'2D')) add_exporting_tag(window_id,plot=b1,signal=b2) dpg.add_separator() with dpg.group(horizontal=True): dpg.add_slider_float(label = 'Remove Outlier RMS', callback=RemoveTimelineOutliers,default_value=3.0,user_data=(tab_tag,signal_tags), width=100, min_value=0,max_value=10) dpg.add_button(label='Recalculate Metrics', callback = recalculate_timeline, user_data=(tab_tag, list(signal_tags.keys()), all_tables)) if flag == True: dpg.add_text( 'Warning: There are missing points in Timeline', tag=f"{tab_tag}_wmp", parent=f"{tab_tag}_buttons") dpg.add_checkbox(label='Show missing points',parent=f"{tab_tag}_buttons", default_value=False, callback=changeTimelinePlot, user_data=(signal_tags, temp_signals, datas, y_axis)) dpg.add_button(label = 'Analyze Events', callback= AnalyzeTimelineEvents, user_data=(tab_tag, events_tags, signal_tags),parent=f"{tab_tag}_buttons",width=-1) add_timeline_features_buttons(tab_tag, signal_tags)
[docs]def comb_impedance_tab(mode,tab_tag,window_id,files): dict_impedance = {} dict_impedance['Left'], dict_impedance['Right'] = {}, {} dates = [] for _, file in enumerate(files): if mode in file['Modes']: Data = file['Data'] date = Data['Device']['SessionDate'] dates.append(date[:10]) status, l, r = DH.getImpedance(Data) impedance = DH.getImpedance(Data) if not isinstance(impedance, str): #preenchido _, l, r = impedance dict_impedance['Left'][date] = l dict_impedance['Right'][date] = r else: l=False name_dates = sorted(dates) if not len(dict_impedance['Left'])>1: impedance_tab(Data,tab_tag) return sorted_left = {key: value for key, value in sorted(dict_impedance['Left'].items())} sorted_right = {key: value for key, value in sorted(dict_impedance['Right'].items())} # dict_impedance['Left'], dict_impedance['Right'] = sorted_left, sorted_right with dpg.tab(tag = tab_tag, label = mode, parent=f"tab_bar_window_{window_id}", closable=True): dpg.add_text(f"Impedance Status: {status}", parent=tab_tag, show=True) def impedance_header(read,side): if side == 'Left': array = sorted_left else: array = sorted_right with dpg.collapsing_header(label=f"{read} {side}"): raw_values, labels = [], [] for _,id in array.items(): for v in id[read]: value= v['ResultValue'] label = utl.after_underscore(utl.after_point(v["Electrode2"])) if read == 'Bipolar': label = utl.after_underscore(utl.after_point(v["Electrode2"])) + '- \n' + label raw_values.append(value) labels.append(label) with dpg.plot(label=f"{read} {side}",width=800,height=400): dpg.add_plot_legend(outside=True,location=dpg.mvPlot_Location_North) x_axis = dpg.add_plot_axis(dpg.mvXAxis, label="Day") y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="Impedance (Ohm)") ticks = tuple((labels[i],float(i)) for i in range(len(labels))) dpg.set_axis_ticks(x_axis,ticks) dpg.add_bar_group_series(raw_values, name_dates, group_size=len(name_dates),parent=y_axis) impedance_header('Monopolar', 'Left') impedance_header('Monopolar', 'Right') impedance_header('Bipolar', 'Left') impedance_header('Bipolar', 'Right')
#-----------------------------------COMBINED ANALISYS-------------------------------------
[docs]def create_multiple_window(): window_id = len(app_state["windows"]) # Unique ID app_state["windows"][window_id] = {"Files": []} # Track window state app_state['windows'][window_id]['Bands'] = global_bands def file_dialog_callback(sender, app_data, user_data): window_id = user_data # Get window-specific ID files = app_data['selections'] # Dict { "full_path": "file_name" } if len(list(files.values())) == 0: dpg.bind_item_theme(f"file_list_{window_id}",change_text_color(1)) for file in files: app_state["windows"][window_id]["Files"].append( {"name": file, "path": files[file]}) dpg.set_item_label(f"window_{window_id}", f"Combined {window_id}") update_file_list(window_id) def remove_file_callback(sender,app_data,user_data): window_id, file_index = user_data del app_state['windows'][window_id]['Files'][file_index] update_file_list(window_id) def update_file_list(window_id): dpg.delete_item(f"file_list_group_{window_id}", children_only = True) files = app_state['windows'][window_id]['Files'] if not files: if not dpg.does_item_exist(f"file_list_{window_id}"): dpg.add_text('No file selected', parent=f"file_list_group_{window_id}", tag =f"file_list_{window_id}") dpg.configure_item(f"open_file_btn_{window_id}", show=False) dpg.configure_item(f"mode_list_group_{window_id}", show=False) dpg.configure_item(f"modes_{window_id}", show = False) else: dpg.configure_item(f"file_list_{window_id}", show=False) for idx, file in enumerate(files): with dpg.group(horizontal=True, parent=f"file_list_group_{window_id}"): dpg.add_text(file['name']) dpg.add_button(label='X',width=20, height=20, callback=remove_file_callback, user_data=(window_id,idx)) dpg.configure_item(f"open_file_btn_{window_id}", show=True) def create_file_dialog(window_id): '''Creates a file dialog for a given window.''' with dpg.file_dialog( label='Multiple files can be selected simultaneously only if from the same folder!', directory_selector=False, show=False, callback=file_dialog_callback, id=f"file_dialog_{window_id}", width=700, height=400, modal=True, #only allows 1 file to be selected default_path=directories['rec'], user_data=window_id # Pass window ID for tracking ): dpg.add_file_extension(".*") dpg.add_file_extension(".json", color=(0, 255, 0, 255), custom_text="[JSON File]") def open_file_callback(sender, app_data, user_data): window_id = user_data files = app_state["windows"][window_id]["Files"] dpg.delete_item(f"mode_list_group_{window_id}", children_only = True) all_modes = [] if not files: dpg.configure_item(f"modes_{window_id}", show = False) dpg.configure_item(f"mode_list_group_{window_id}", show = False) else: for idx, file in enumerate(files): file_path = file['path'] Data = DH.getData(file_path) if isinstance(Data,str): t = dpg.add_text(f"{file['name']} corrupted.", parent=f"file_list_group_{window_id}") red = change_text_color(1) dpg.bind_item_theme(t, red) continue _ = session_window(Data, window_id, idx) device_window(Data,window_id, idx) stimulation_window(Data,window_id, idx) modes = DH.getModes(Data) if _: modes.append('Impedance') if modes == ['None. Recording is Damaged!']: remove_file_callback('','',user_data=(window_id,idx)) dpg.add_text(f"File {file['name']} was removed due to being damaged!", parent = f"mode_list_group_{window_id}",show=True) dpg.add_text("Try to load again!", parent = f"mode_list_group_{window_id}",show=True) update_file_list(window_id) else: file['Data'] = Data file['Modes'] = modes for mode in modes: if mode != ['None. Recording is Damaged!']: all_modes.append(mode) else: pass for mode in np.unique(all_modes): if mode == 'Damaged': continue dpg.add_button(label = mode, parent=f"mode_list_group_{window_id}",callback=open_tab_mode ,user_data=(mode,window_id),show=True) dpg.configure_item(f"mode_list_group_{window_id}", show = True) dpg.configure_item(f"session_parameteres_{window_id}", show = True) # dpg.configure_item(f"window_{window_id}",width=900, height=500) dpg.configure_item(f"window_{window_id}", autosize=True) time.sleep(0.1) dpg.configure_item(f"window_{window_id}", autosize=False) def open_tab_mode(sender,app_data,user_data): mode, window_id = user_data files = app_state["windows"][window_id]["Files"] dpg.configure_item(item=f"open_file_btn_{window_id}",show=False) dpg.configure_item(item=f"{window_id}_add_file",show=False) tab_tag = f"tab_{mode}_{window_id}" if dpg.does_item_exist(tab_tag): #check if its already created # if not dpg.is_item_shown(tab_tag): # If closed, show it again # dpg.configure_item(tab_tag, show=True) # '''maybe delete tab, create a new one from scrath enabling reset (?)''' # return dpg.delete_item(tab_tag) if mode == 'Survey': dpg.delete_item(f"{tab_tag}_psd") plot_tag = f"plot_{tab_tag}" signal_tags = {} match mode: case 'Setup': comb_setup_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags=signal_tags) case 'Survey': comb_survey_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags=signal_tags) case 'Streaming': comb_streaming_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags=signal_tags) case 'Indefinite': comb_indefinite_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags=signal_tags) case 'Events': comb_event_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags=signal_tags) case 'Timeline': comb_timeline_tab(mode,tab_tag,window_id,plot_tag,files,signal_tags=signal_tags) case 'Impedance': comb_impedance_tab(mode,tab_tag,window_id,files) case 'Damaged': return dpg.configure_item(f"window_{window_id}", autosize=False) with dpg.window(label=f"Window {window_id + 1}", width=400, height=300, tag=f"window_{window_id}"): window_menu(window_id) dpg.add_button(label="Add File", callback=lambda: dpg.show_item(f"file_dialog_{window_id}"), tag = f"{window_id}_add_file") dpg.add_text("No file selected", tag=f"file_list_{window_id}") with dpg.group(tag=f"file_list_group_{window_id}"): pass dpg.add_text('', tag=f"damage_{window_id}", show=True) dpg.add_text('', tag=f"modes_{window_id}", show=False) #Modes in dpg.add_button(label="Load Files", callback=open_file_callback, tag=f"open_file_btn_{window_id}", show=False, user_data=window_id) with dpg.group(horizontal=True): with dpg.group(): dpg.add_text(f"Session Information", show=False, tag = f"session_parameteres_{window_id}_title") dpg.add_child_window(tag = f"session_parameteres_{window_id}", height= 150, width=300, show=False,horizontal_scrollbar=True) with dpg.group(): dpg.add_text(f"Device Information", show=False, tag = f"device_parameteres_{window_id}_title") dpg.add_child_window(tag = f"device_parameteres_{window_id}", height= 150, width=300, show=False,horizontal_scrollbar=True) with dpg.group(): dpg.add_text(f"Stimulation Information", show=False, tag = f"stimulation_parameteres_{window_id}_title") dpg.add_child_window(tag = f"stimulation_parameteres_{window_id}", height= 150, width=300, show=False,horizontal_scrollbar=True) with dpg.group(horizontal=True,tag = f"mode_list_group_{window_id}"): pass with dpg.tab_bar(tag=f"tab_bar_window_{window_id}",reorderable=True): pass create_file_dialog(window_id)
#-----------------------------------INDIVIDUAL ANALISYS-----------------------------------
[docs]def create_individual_window(): '''Dynamically creates a new independent window.''' window_id = len(app_state["windows"]) # Unique ID app_state["windows"][window_id] = {"Files": []} # Track window state app_state['windows'][window_id]['Bands'] = global_bands def file_dialog_callback(sender, app_data, user_data): window_id = user_data if isinstance(app_data['selections'], dict): # Check if files were selected selected_files = list(app_data['selections'].values()) if len(selected_files) > 1: dpg.set_value(f"file_list_{window_id}","Only one file can be selected! Please try again.") elif len(selected_files) == 0: dpg.bind_item_theme(f"file_list_{window_id}",change_text_color(1)) else: print(f"Selected file: {selected_files[0]}") # Get window-specific ID files = app_data['selections'] # Dict { "full_path": "file_name" } # Store full paths and filenames per window app_state["windows"][window_id]["Files"] = [ {"name": full_path, "path": file_name} for full_path, file_name in files.items() ] # Update text display with full paths file_list_text = "\n".join(file["name"] for file in app_state["windows"][window_id]["Files"]) if files else "No file selected" dpg.set_value(f"file_list_{window_id}", file_list_text) dpg.configure_item(f"open_file_btn_{window_id}", show=True) def create_file_dialog(window_id): '''Creates a file dialog for a given window.''' with dpg.file_dialog( tag = f"file_dialog_{window_id}", directory_selector=False, show=False, callback=file_dialog_callback, # id=f"file_dialog_{window_id}", width=700, height=400, #modal=True, #if True: user must select a file before returning to main window - não vi diferença default_path=directories['rec'], user_data=window_id # Pass window ID for tracking ): dpg.add_file_extension(".*") dpg.add_file_extension(".json", color=(0, 255, 0, 255), custom_text="[JSON File]") def open_tab_mode(sender, app_data, user_data): #create tab mode, window_id = user_data dpg.configure_item(item=f"open_file_btn_{window_id}",show=False) dpg.configure_item(item=f"file_selector_{window_id}",show=False) tab_tag = f"tab_{mode}_{window_id}" if dpg.does_item_exist(tab_tag): #check if its already created if not dpg.is_item_shown(tab_tag): # If closed, show it again # dpg.configure_item(tab_tag, show=True) # dpg.add_tab_button(label='X',parent=f"tab_bar_window_{window_id}") dpg.delete_item(tab_tag) if mode == 'Survey': dpg.delete_item(f"{tab_tag}_psd") '''maybe delete tab, create a new one from scrath enabling reset (?)''' else: return Data = app_state['windows'][window_id]['Data'] if mode == 'Impedance': impedance_tab(Data,tab_tag) '''status, l, r = DH.getImpedance(Data) def build_result_table(session_name, session_data): # Collect unique electrodes electrodes1 = sorted(set(entry["Electrode1"] for entry in session_data)) electrodes2 = sorted(set(entry["Electrode2"] for entry in session_data)) # Build a lookup dictionary result_dict = { (entry["Electrode2"], entry["Electrode1"]): entry["ResultValue"] for entry in session_data } with dpg.table(header_row=True, borders_innerH=True, borders_innerV=True, borders_outerH=True, borders_outerV=True, resizable=True, policy=dpg.mvTable_SizingStretchProp): # Header row dpg.add_table_column(label="SenSight") for e1 in electrodes1: new_label = utl.after_underscore(e1) if new_label==e1: new_label=utl.after_point(e1) dpg.add_table_column(label=new_label) # Rows for e2 in electrodes2: with dpg.table_row(): dpg.add_text(utl.after_underscore(e2)) for e1 in electrodes1: val = result_dict.get((e2, e1), "") if e2==e1: val = '-' if val == "": val = result_dict.get((e1, e2), "") dpg.add_text(str(val)) with dpg.tab(tag = tab_tag, label = mode, parent=f"tab_bar_window_{window_id}"): with dpg.collapsing_header(label='Monopolar Left'): build_result_table("Monopolar", l["Monopolar"]) with dpg.collapsing_header(label='Monopolar Right'): build_result_table("Monopolar", r["Monopolar"]) with dpg.collapsing_header(label='Bipolar Left'): build_result_table("Bipolar", l["Bipolar"]) with dpg.collapsing_header(label='Bipolar Right'): build_result_table("Bipolar", r["Bipolar"]) ''' else: app_state['windows'][window_id]['Data'][tab_tag]= {'Signals': []} signals = DH.getSignals(Data,mode) plot_tag = f"plot_{tab_tag}" signal_tags = {} match mode: case 'Setup': setup_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags) case 'Survey': survey_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags) case 'Streaming': streaming_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags) case 'Indefinite': indefinite_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags) case 'Events': if len(signals)>0: event_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags) # if not isinstance(signals,dict): electrode_tab(Data,f"tab_electrode_{window_id}") case 'Timeline': if len(signals)>0: timeline_tab(mode,tab_tag,window_id,plot_tag,signals,signal_tags) # electrode_tab(Data,f"tab_electrode_{window_id}") dpg.configure_item(f"window_{window_id}", autosize=False) def open_file_callback(sender, app_data, user_data): #open file window_id = user_data # Get window-specific ID files = app_state["windows"][window_id]["Files"] file_path = files[0]['path'] #check if its not opening the same file previous_file_path = app_state['windows'][window_id].get('PreviousFilePath', None) if file_path == previous_file_path: return else: #show modes and buttons Data = DH.getData(file_path) app_state['windows'][window_id]['Data'] = Data if isinstance(Data,str): warning_text = f"{files[0]['name']} file not terminated!" og = dpg.get_value(f"damage_{window_id}") if og != "": text = f"{og} | {warning_text}" else: text = warning_text dpg.set_value(f"damage_{window_id}", text) red = change_text_color(1) dpg.bind_item_theme(f"damage_{window_id}", red) return app_state['windows'][window_id]['Modes'] = DH.getModes(Data) if app_state['windows'][window_id]['Modes'] == ['None. Recording is Damaged!']: dpg.set_value(f"modes_{window_id}", 'Recording is damaged! Choose another file.') dpg.configure_item(f"modes_{window_id}", show=True) modes = [] else: dpg.delete_item(item=f"mode_list_group_{window_id}",children_only=True) l = session_window(Data,window_id) device_window(Data,window_id) stimulation_window(Data,window_id) modes = app_state['windows'][window_id]['Modes'] if l: modes.append('Impedance') if modes != ['None. Recording is Damaged!']: dpg.set_value(f"modes_{window_id}", 'Recording modes present in the file:') dpg.configure_item(f"modes_{window_id}", show=True) dpg.set_item_label(f"window_{window_id}", files[0]['name']) for mode in modes: if mode == 'Damaged': dpg.set_value(f"damage_{window_id}",'Session is damaged, recordings available.') dpg.configure_item(f"damage_{window_id}",show=True) continue dpg.add_button(label = mode, parent=f"mode_list_group_{window_id}",callback=open_tab_mode ,user_data=(mode,window_id),show=True) dpg.configure_item(f"mode_list_group_{window_id}", show = True) app_state['windows'][window_id]['PreviousFilePath'] = file_path dpg.configure_item(f"window_{window_id}", autosize=True) time.sleep(0.1) dpg.configure_item(f"window_{window_id}", autosize=False) with dpg.window(label=f"Window {window_id + 1}", width=400, height=300, tag=f"window_{window_id}"): #individual window window_menu(window_id) dpg.add_button(label="File Selector", callback=lambda: dpg.show_item(f"file_dialog_{window_id}"), tag = f"file_selector_{window_id}") dpg.add_text("No file selected", tag=f"file_list_{window_id}") dpg.add_button(label="Open File", callback=open_file_callback, tag=f"open_file_btn_{window_id}", show=False, user_data=window_id) with dpg.group(horizontal=True): with dpg.group(): dpg.add_text(f"Session Information", show=False, tag = f"session_parameteres_{window_id}_title") dpg.add_child_window(tag = f"session_parameteres_{window_id}", height= 150, width=300, show=False,horizontal_scrollbar=True) with dpg.group(): dpg.add_text(f"Device Information", show=False, tag = f"device_parameteres_{window_id}_title") dpg.add_child_window(tag = f"device_parameteres_{window_id}", height= 150, width=300, show=False,horizontal_scrollbar=True) with dpg.group(): dpg.add_text(f"Stimulation Information", show=False, tag = f"stimulation_parameteres_{window_id}_title") dpg.add_child_window(tag = f"stimulation_parameteres_{window_id}", height= 150, width=300, show=False,horizontal_scrollbar=True) dpg.add_text('', tag=f"damage_{window_id}", show=True) dpg.add_text('', tag=f"modes_{window_id}", show=False) #Modes in with dpg.group(horizontal=True,tag = f"mode_list_group_{window_id}"): pass with dpg.tab_bar(tag=f"tab_bar_window_{window_id}",reorderable=True): pass create_file_dialog(window_id) # Create a file dialog for this window
#---------------------------------------MAIN WINDOW---------------------------------------
[docs]def load_main_ui(): dpg.delete_item("Logo") set_font() light_theme = themes.create_theme_imgui_light() dark_theme = themes.create_theme_imgui_dark() default_classic_theme = dpg.add_theme() # DPG default # Main window with dpg.window(label="NeoDBS", width=1000, height=600, tag="Main"): with dpg.menu_bar(): with dpg.menu(label="New"): dpg.add_menu_item(label="Individual Window", callback=create_individual_window, user_data=default_classic_theme) dpg.add_menu_item(label="Combined Window", callback=create_multiple_window, user_data=light_theme) with dpg.menu(label="Edit"): dpg.add_menu_item(label='Resize Font',callback=resize_font) dpg.add_menu_item(label="Rename Electrodes", callback=change_electrodes_names) dpg.add_menu_item(label='Rename Event/Group',callback = change_event_name) with dpg.menu(label="Save"): dpg.add_menu_item(label="Save All Plots", callback=save_all, user_data = ('Plots',None)) dpg.add_menu_item(label="Save All Tables", callback=save_all, user_data = ('Tables', None)) dpg.add_menu_item(label="Save All Signals", callback=save_all, user_data = ('Signals', None)) with dpg.menu(label="Themes"): dpg.add_menu_item(label="Classic", callback=switch_theme, user_data=default_classic_theme) dpg.add_menu_item(label="Light", callback=switch_theme, user_data=light_theme) dpg.add_menu_item(label="Dark", callback=switch_theme, user_data=dark_theme) with dpg.menu(label="Directories"): dpg.add_menu_item(label="Recording Directory", callback=change_directory,user_data='rec') dpg.add_menu_item(label="Events Directory", callback=change_directory, user_data='events') dpg.add_menu_item(label="Save in... Directory", callback=change_directory,user_data='save') with dpg.group(horizontal=True, pos=[150, 250]): dpg.add_button(label="Individual/Recording Analysis", callback=create_individual_window,height=40,width=350,tag="individual") dpg.bind_item_theme("individual",classic_button()) dpg.add_spacer(indent=5) dpg.add_button(label="Combined/Event Analysis", callback=create_multiple_window,height=40,width=350,tag="combined") dpg.bind_item_theme("combined",classic_button()) dpg.set_viewport_width(1000) dpg.set_viewport_height(600) dpg.set_viewport_resizable(True) dpg.set_primary_window("Main", True)
# dpg.delete_item("Logo")
[docs]def main(): dpg.create_context() dpg.create_viewport(title="NeoDBS", width=500, height=500) dpg.set_viewport_large_icon('NeoDBS.ico') dpg.set_viewport_small_icon('NeoDBS.ico') dpg.setup_dearpygui() with dpg.texture_registry(show=False): image_path = utl.resource_path("NeoDBS.png") try: width, height, channels, data = dpg.load_image(str(image_path)) dpg.add_static_texture(width, height, data, tag="logo_tex") flag = True except Exception as e: width, height = 500, 500 flag = False # GLOBAL FONT REGISTRY with dpg.font_registry(tag="FONT_REGISTRY"): pass # Create window that matches image size exactly with dpg.window(label="", tag="Logo", no_title_bar=True, no_resize=True, no_close=True, no_collapse=True, no_move=True, width=width, height=height): dpg.bind_theme(design()) if flag: dpg.add_image("logo_tex", width=width, height=height, pos=[0, 0]) start = dpg.add_button(label='start',height=30,width=200,pos=[150,340],callback=load_main_ui) dpg.bind_item_theme(start,change_button_color(3)) dpg.bind_item_font(start,set_font(24,True)) version = dpg.add_text('Version 0.1.0') dpg.bind_item_font(version, set_font(just_theme=True)) dpg.bind_item_theme(version,change_text_color(5)) dpg.set_viewport_resizable(False) dpg.show_viewport() dpg.start_dearpygui() dpg.destroy_context()