Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
*.npy filter=lfs diff=lfs merge=lfs -text
* text=auto


71 changes: 52 additions & 19 deletions eegvis/eegpanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
#
from bokeh.models.tickers import FixedTicker, SingleIntervalTicker

from . import montageview
from . import montageview #_stanford
from . import montage_derivations_edf_simplified
from . import stackplot_bokeh
from .stackplot_bokeh import limit_sample_check
Expand Down Expand Up @@ -133,13 +133,15 @@ class EeghdfBrowser:

def __init__(
self,
eeghdf_file_names,
eeghdf_files,
stanford_file_names, #eeghdf_file_names
tuh_file_names,
stanford_files, #eeghdf_files,
tuh_files,
page_width_seconds=10.0,
start_seconds=-1,
montage="neonatal",
montage="double banana",
montage_options={},
tuh = True,
tuh = False,
yscale=1.0,
plot_width=950,
plot_height=600,
Expand All @@ -156,11 +158,18 @@ def __init__(
BTW 'trace' is what NK calls its 'as recorded' montage - might be better to call 'raw', 'default' or 'as recorded'
"""
self.tuh = tuh
self.eeghdf_file_names = eeghdf_file_names
self.eeghdf_files = eeghdf_files
self.eeghdf_file = eeghdf_files[0]
self.eeghdf_file_names = stanford_file_names #eeghdf_file_names
self.eeghdf_files = stanford_files #eeghdf_files

self.eeghdf_file = self.eeghdf_files[0]
self.update_eeghdf_file(self.eeghdf_file, montage, montage_options)

self.stanford_file_names = stanford_file_names
self.tuh_file_names = tuh_file_names
self.stanford_files = stanford_files
self.tuh_files = tuh_files


# display related
self.page_width_seconds = page_width_seconds

Expand Down Expand Up @@ -252,11 +261,11 @@ def receive_overviewloc_update(self, overview_loc, **kwargs):
def signals(self):
return self.eeghdf_file.phys_signals

def update_eeghdf_file(self, eeghdf_file, montage="trace", montage_options={}):
def update_eeghdf_file(self, eeghdf_file, montage="double banana", montage_options={}):
self.eeghdf_file = eeghdf_file
hdf = eeghdf_file.hdf
rec = hdf["record-0"]
self.fs = rec.attrs["sample_frequency"]
self.fs = int(rec.attrs["sample_frequency"])

# TODO: this HACK is model specific, we cut out last (signal_length % 60) secs
# we do this b/c model does not output probs for clips < 60sec
Expand Down Expand Up @@ -285,15 +294,17 @@ def update_eeghdf_file(self, eeghdf_file, montage="trace", montage_options={}):

# reference labels are used for montages, since this is an eeghdf file, it can provide these

#TODO: stnaford uses shortcut labels!
self.ref_labels = eeghdf_file.electrode_labels #eeghdf_file.shortcut_elabels
if self.tuh:
self.ref_labels = eeghdf_file.electrode_labels
else:
self.ref_labels = eeghdf_file.shortcut_elabels

if not montage_options:
#if not montage_options:
# then use builtins and/or ones in the file
if self.tuh:
montage_options = montage_derivations_edf_simplified.EDF_SIMPLIFIED_MONTAGE_BUILTINS.copy()
else:
montage_options = montageview.MONTAGE_BUILTINS.copy()
if self.tuh:
montage_options = montage_derivations_edf_simplified.EDF_SIMPLIFIED_MONTAGE_BUILTINS.copy()
else:
montage_options = montageview.MONTAGE_BUILTINS.copy()

# print('starting build of montage options', montage_options)

Expand Down Expand Up @@ -391,7 +402,7 @@ def update(self):
"""
goto_sample = int(self.fs * self.loc_sec)
page_width_samples = int(self.page_width_secs * self.fs)
hw = half_width_epoch_sample = int(page_width_samples / 2)
hw = int(page_width_samples / 2)
s0 = limit_sample_check(goto_sample - hw, self.signals)
s1 = limit_sample_check(goto_sample + hw, self.signals)
window_samples = s1 - s0
Expand Down Expand Up @@ -913,12 +924,32 @@ def show_montage_centered(

def register_top_bar_ui(self):

self.ui_hospital_dropdown = Select(
options=["Stanford","Temple"],
value="Stanford",
title="Hospital:",
)

self.ui_filename_dropdown = Select(
options=self.eeghdf_file_names,
value=self.eeghdf_file_names[0],
title="File Name:",
)

def on_hospital_dropdown_change(attr, oldvalue, newvalue, parent=self):
if newvalue == "Temple":
self.tuh = True
self.eeghdf_file_names = self.tuh_file_names
self.eeghdf_files = self.tuh_files
else:
self.tuh = False
self.eeghdf_file_names = self.stanford_file_names
self.eeghdf_files = self.stanford_files

self.ui_filename_dropdown.options = self.eeghdf_file_names
self.ui_filename_dropdown.value = self.eeghdf_file_names[0]


def on_filename_dropdown_change(attr, oldvalue, newvalue, parent=self):
new_file_index = self.eeghdf_file_names.index(newvalue)
self.eeghdf_file = self.eeghdf_files[new_file_index]
Expand All @@ -932,6 +963,7 @@ def on_filename_dropdown_change(attr, oldvalue, newvalue, parent=self):
self.update()
self.filename_signal.emit(filename=newvalue)

self.ui_hospital_dropdown.on_change("value", on_hospital_dropdown_change)
self.ui_filename_dropdown.on_change("value", on_filename_dropdown_change)

# mlayout = ipywidgets.Layout()
Expand Down Expand Up @@ -998,7 +1030,7 @@ def hf_dropdown_on_change(attr, oldvalue, newvalue, parent=self):
self.ui_high_freq_filter_dropdown.on_change("value", hf_dropdown_on_change)

self.ui_notch_option = CheckboxGroup(
labels=["60Hz Notch"]
labels=["60Hz Notch"], active=[0]
# , "50Hz Notch"], max_width=100, # disabled=False
)

Expand Down Expand Up @@ -1057,6 +1089,7 @@ def ui_gain_watcher(ev, parent=self):

# self.top_bar_layout = bokeh.layouts.row(
self.top_bar_layout = pn.Row(
self.ui_hospital_dropdown,
self.ui_filename_dropdown,
self.ui_montage_dropdown,
self.ui_low_freq_filter_dropdown,
Expand Down
9 changes: 4 additions & 5 deletions eegvis/montageview.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ def double_banana_set_matrix(V):
# dims=('x', 'y'),
# coords={'x': up_db_labels,
# 'y': up_rec_labels})

V.loc["Fp1-F7", "Fp1"] = 1
V.loc["Fp1-F7", "F7"] = -1
V.loc["F7-T3", "F7"] = 1
Expand Down Expand Up @@ -677,25 +676,25 @@ def __init__(self, rec_labels, reversed_polarity=True):

def neonatal_set_matrix(self, V):
# pdb.set_trace()
V.loc["Fp1-T3", "FP1"] = 1
V.loc["Fp1-T3", "Fp1"] = 1
V.loc["Fp1-T3", "T3"] = -1

V.loc["T3-O1", "T3"] = 1
V.loc["T3-O1", "O1"] = -1

V.loc["Fp2-T4", "FP2"] = 1
V.loc["Fp2-T4", "Fp2"] = 1
V.loc["Fp2-T4", "T4"] = -1

V.loc["T4-O2", "T4"] = 1
V.loc["T4-O2", "O2"] = -1

V.loc["Fp1-C3", "FP1"] = 1
V.loc["Fp1-C3", "Fp1"] = 1
V.loc["Fp1-C3", "C3"] = -1

V.loc["C3-O1", "C3"] = 1
V.loc["C3-O1", "O1"] = -1

V.loc["Fp2-C4", "FP2"] = 1
V.loc["Fp2-C4", "Fp2"] = 1
V.loc["Fp2-C4", "C4"] = -1

V.loc["C4-O2", "C4"] = 1
Expand Down
127 changes: 120 additions & 7 deletions eegvis/stacklineplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,24 @@ def stackplot_t(
# uV that covers, then divides up the available unit space among the
# channels. Would need to do something differen tif wanted to show a subset
# of channels then scroll them

myfig = ax.get_figure()
figsizex_inch, figsizey_inch = myfig.get_size_inches()
dpi = myfig.dpi
dpmm = dpi / 25.4
# print(f"{myfig.get_size_inches()=}")
# figsizex_inch, figsizey_inch = myfig.get_size_inches()
# print(f"ax.bbox: {type(ax.bbox)=}")
# print(f"{ax.bbox.width=}, {ax.bbox.height=}")
figsizex_inch, figsizey_inch = (
ax.bbox.width / dpi,
ax.bbox.height / dpi,
) # is this wrong need to explore
# print(f"{figsizex_inch=}, {figsizey_inch=}")

figsizex_mm = 25.4 * figsizex_inch
figsizey_mm = 25.4 * figsizey_inch

# sensetivity such as 7 uV/mm is
# sensetivity such as 7 uV/mm is typical
total_uV = ysensitivity * figsizey_mm
# assume data is in uV
# is lower lim of y still dmin? No
Expand Down Expand Up @@ -343,6 +352,103 @@ def limit_sample_check(x, signals):
# optional: channel labels


def show_arr_as_montaged_eeg(
signals,
montage, # MontageView factory instance
fs,
ylabels=[],
yscale=1.0,
topdown=True,
ax=None,
**kwargs,
):
"""
@signals array-like object with signals[ch_num, sample_num]
@montage MontageView factory instance
@fs sample frequency (num samples per second)
@labels_by_channel
@yscale
@topdown=True implies plot channel 0 starting at top of figure/subfigure
@ax matplotlib Axes object into which to render
@kwargs any other keyword parameters to pass on
"""

inmontage_view = np.dot(
montage.V.data, signals
) # montage.V.data is matrix (linear transform)

n_chan, n_samples = signals.shape
width_sec = n_samples / fs

rlabels = montage.montage_labels
return stackplot(
inmontage_view,
start_time=0,
seconds=width_sec,
ylabels=rlabels,
yscale=yscale,
topdown=topdown,
ax=ax,
**kwargs,
)


def show_grid_arr_as_montaged_eeg(
eeg_clip_arrs,
fs,
elabels,
ytrues=[],
ypreds=[],
n_row=3,
n_col=3,
row_height=3.0,
col_length=4.0,
montage_name="double banana",
ysensitivity=7.0,
units="$\mu$V",
):
"""
eeg_clip_arrs,
elabels,
ytrues=[],
ypreds=[],
n_row=3,
n_col=3,
montage_name="double banana"

TODO!!! set the row/col rations correctly
"""
import eegvis.montageview

fig = plt.figure(figsize=(n_row * 4.0, n_col * 4.0))

axs = fig.subplots(n_row, n_col, sharex=True, sharey=True)
faxs = axs.flatten()
montage_name = "double banana"
if montage_name in eegvis.montageview.MONTAGE_BUILTINS:
montage_derivation = eegvis.montageview.MONTAGE_BUILTINS[montage_name](elabels)
for ii, eegarr in enumerate(eeg_clip_arrs):
if n_row * n_col > ii:
show_arr_as_montaged_eeg(
eegarr,
montage_derivation,
fs=fs,
ylabels=elabels,
ax=faxs[ii],
ysensitivity=ysensitivity,
)
title_str = f"[{ii}]"
if len(ytrues):
title_str += f" y_true={ytrues[ii]}"
if len(ypreds):
title_str += f" y_pred={ypreds[ii]}"

faxs[ii].set_title(title_str)
add_relative_vertical_scalebar(faxs[ii], units=units)

return fig, axs


def show_epoch_centered(
signals,
goto_sec,
Expand Down Expand Up @@ -411,6 +517,7 @@ def show_montage_centered(
):
"""
@signals array-like object with signals[ch_num, sample_num]
@montage
@goto_sec where to go in the signal to show the feature
@epoch_width_sec length of the window to show in secs
@chstart which channel to start
Expand Down Expand Up @@ -697,27 +804,33 @@ def add_relative_vertical_scalebar(
# + is a kind of weird composition operator, reverse what I thought
# transiv = ax.transAxes + ax.transData.inverted() # axes->display -> data
axes2data = deltaAxes + deltaData.inverted() # axes->display -> data

# print(f"{relative_height=}")
# print(f"relative height->display pixels:{deltaAxes.transform((0.0,relative_height))=}")
_x, relative_height_pixels = deltaAxes.transform((0.0, relative_height))
_x, relative_height_data = deltaData.inverted().transform(
(_x, relative_height_pixels)
)
# print(f"{relative_height_data=}")
# hack to use only one significant digit by default
_x, data_height = axes2data.transform((0.0, relative_height))
# print(f"{_x=}, {data_height=}")

data_height = float("%.1g" % data_height)
# print(f"after rounding: {_x=}, {data_height=}")

_x, size_axes = axes2data.inverted().transform((_x, data_height))
# print(f"after converson: {_x=}, {size_axes=}")
_x, bar_height_axescoord = axes2data.inverted().transform((_x, data_height))
# print(f"after converson: {_x=}, {bar_height_axescoord=}")
size_bar = matplotlib.offsetbox.AuxTransformBox(ax.transAxes)

## draw the vertical scale bar in axes coordiates
# Line2D(xdata, ydata, *, ...)
line = Line2D([0, 0], [0, size_axes], color=color) # , **linekw)
line = Line2D([0, 0], [0, bar_height_axescoord], color=color) # , **linekw)
vline1 = Line2D(
[-end_line_extent / 2.0, end_line_extent / 2.0], [0, 0], color=color
)
vline2 = Line2D(
[-end_line_extent / 2.0, end_line_extent / 2.0],
[size_axes, size_axes],
[bar_height_axescoord, bar_height_axescoord],
color=color,
)
size_bar.add_artist(line)
Expand Down