Skip to content

Commit 723c4af

Browse files
author
root
committed
major update:
- directory structure reorganized - process_ecog signal_processing module included as a module of ecogvis - lowercase package name - Added MANIFEST.in to include .ui files on installation
1 parent 4ef1781 commit 723c4af

29 files changed

+835
-356
lines changed

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
recursive-include ecogvis/ui *

ecogvis/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#from .functions import *
2+
#from .signal_processing import *
3+
#from .ui import *
4+
from ecogvis import *

ecog_vis.py ecogvis/ecogvis.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
QAction, QStackedLayout)
1313
import pyqtgraph as pg
1414
from pyqtgraph.widgets.MatplotlibWidget import MatplotlibWidget
15-
from Function.subFunctions import ecogVIS
16-
from Function.subDialogs import (CustomIntervalDialog, SelectChannelsDialog,
15+
from ecogvis.functions.subFunctions import ecogVIS
16+
from ecogvis.functions.subDialogs import (CustomIntervalDialog, SelectChannelsDialog,
1717
SpectralChoiceDialog, PeriodogramDialog, NoHighGammaDialog, NoPreprocessedDialog,
1818
NoTrialsDialog, ExitDialog, ERPDialog, CalcHighGammaDialog, GroupPeriodogramDialog)
1919

@@ -839,11 +839,12 @@ def main(filename):
839839
sys.exit(app.exec_())
840840

841841
# If called from a command line, e.g.: $ python ecog_ts_gui.py
842-
if __name__ == '__main__':
843-
app = QApplication(sys.argv) #instantiate a QtGui (holder for the app)
844-
if len(sys.argv)==1:
845-
fname = ''
846-
else:
847-
fname = sys.argv[1]
848-
ex = Application(filename=fname)
849-
sys.exit(app.exec_())
842+
# NOT WORKING ANYMORE
843+
# if __name__ == '__main__':
844+
# app = QApplication(sys.argv) #instantiate a QtGui (holder for the app)
845+
# if len(sys.argv)==1:
846+
# fname = ''
847+
# else:
848+
# fname = sys.argv[1]
849+
# ex = Application(filename=fname)
850+
# sys.exit(app.exec_())
File renamed without changes.

ecogvis/functions/__init__.py

Whitespace-only changes.
File renamed without changes.

Function/subDialogs.py ecogvis/functions/subDialogs.py

+17-28
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import pyqtgraph as pg
55
import pyqtgraph.opengl as gl
66
import pyqtgraph.exporters as pgexp
7-
from ecog.utils import bands as default_bands
8-
from ecog.signal_processing.preprocess_data import preprocess_data
7+
from ecogvis.signal_processing import bands as default_bands
8+
from ecogvis.signal_processing.preprocess_data import preprocess_data
99
from .FS_colorLUT import get_lut
1010
from threading import Event, Thread
1111
import numpy as np
1212
from scipy import signal
1313
import os
1414
import time
1515

16-
path = os.path.dirname(__file__)
16+
ui_path = os.path.join(os.path.dirname(__file__), '..', 'ui')
1717

1818
# Creates custom interval type -------------------------------------------------
19-
Ui_CustomInterval, _ = uic.loadUiType(os.path.join(path,"intervals_gui.ui"))
19+
Ui_CustomInterval, _ = uic.loadUiType(os.path.join(ui_path,"intervals_gui.ui"))
2020
class CustomIntervalDialog(QtGui.QDialog, Ui_CustomInterval):
2121
def __init__(self):
2222
super().__init__()
@@ -143,7 +143,7 @@ def cancel(self):
143143

144144

145145
# Exit confirmation ------------------------------------------------------------
146-
Ui_Exit, _ = uic.loadUiType(os.path.join(path,"exit_gui.ui"))
146+
Ui_Exit, _ = uic.loadUiType(os.path.join(ui_path,"exit_gui.ui"))
147147
class ExitDialog(QtGui.QDialog, Ui_Exit):
148148
def __init__(self, parent):
149149
super().__init__()
@@ -241,7 +241,7 @@ def unselect_all(self):
241241

242242

243243
# Creates Spectral Analysis choice dialog --------------------------------------
244-
Ui_SpectralChoice, _ = uic.loadUiType(os.path.join(path,"spectral_choice_gui.ui"))
244+
Ui_SpectralChoice, _ = uic.loadUiType(os.path.join(ui_path,"spectral_choice_gui.ui"))
245245
class SpectralChoiceDialog(QtGui.QDialog, Ui_SpectralChoice):
246246
def __init__(self, nwb, fpath, fname):
247247
super().__init__()
@@ -252,7 +252,7 @@ def __init__(self, nwb, fpath, fname):
252252

253253
self.data_exists = False #Indicates if data already exists or will be created
254254
self.decomp_type = None #Indicates the chosen decomposition type
255-
self.custom_bands = None #Values for custom filter bands (user input)
255+
self.chosen_bands = None #Values for custom filter bands (user input)
256256
self.value = -1 #Reference value for user pressed exit button
257257

258258
self.radioButton_1.clicked.connect(self.choice_default)
@@ -269,7 +269,6 @@ def __init__(self, nwb, fpath, fname):
269269

270270
def choice_default(self): # default chosen
271271
self.decomp_type = 'default'
272-
self.custom_bands = None
273272
self.pushButton_1.setEnabled(False)
274273
self.pushButton_2.setEnabled(False)
275274
self.tableWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
@@ -306,25 +305,15 @@ def choice_default(self): # default chosen
306305

307306
def choice_highgamma(self): # default chosen
308307
self.decomp_type = 'high_gamma'
309-
self.custom_bands = None
310308
self.pushButton_1.setEnabled(False)
311309
self.pushButton_2.setEnabled(False)
312310
self.tableWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
313311
try: # if data already exists in file
314312
decomp = self.nwb.modules['ecephys'].data_interfaces['high_gamma']
315-
text = "'High gamma' frequency decomposition data already exists in current file.\n" \
316-
"It corresponds to the averaged power of the bands shown in the table."
313+
text = "'High gamma' frequency decomposition data already exists in current file."
317314
self.label_1.setText(text)
318315
self.data_exists = True
319316
self.runButton.setEnabled(False)
320-
# Populate table with values
321-
self.tableWidget.setHorizontalHeaderLabels(['center [Hz]','sigma [Hz]'])
322-
p0 = default_bands.chang_lab['cfs'][29:37]
323-
p1 = default_bands.chang_lab['sds'][29:37]
324-
self.tableWidget.setRowCount(len(p0))
325-
for i in np.arange(len(p0)):
326-
self.tableWidget.setItem(i, 0, QTableWidgetItem(str(round(p0[i],1))))
327-
self.tableWidget.setItem(i, 1, QTableWidgetItem(str(round(p1[i],1))))
328317
except: # if data does not exist in file
329318
text = "'High gamma' frequency decomposition data does not exist in current file.\n" \
330319
"It can be created from the averaged power of the bands shown in the table. "\
@@ -340,11 +329,14 @@ def choice_highgamma(self): # default chosen
340329
for i in np.arange(len(p0)):
341330
self.tableWidget.setItem(i, 0, QTableWidgetItem(str(round(p0[i],1))))
342331
self.tableWidget.setItem(i, 1, QTableWidgetItem(str(round(p1[i],1))))
332+
# Allows user to populate table with values
333+
self.tableWidget.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked)
334+
self.pushButton_1.setEnabled(True)
335+
self.pushButton_2.setEnabled(True)
343336

344337

345338
def choice_custom(self): # default chosen
346339
self.decomp_type = 'custom'
347-
self.custom_bands = None
348340
try: # if data already exists in file
349341
decomp = self.nwb.modules['ecephys'].data_interfaces['Bandpower_custom']
350342
text = "'Custom' frequency decomposition data already exists in current file.\n" \
@@ -357,12 +349,9 @@ def choice_custom(self): # default chosen
357349
p0 = decomp.bands['filter_param_0']
358350
p1 = decomp.bands['filter_param_1']
359351
self.tableWidget.setRowCount(len(p0))
360-
self.custom_bands = np.zeros((2,len(p0)))
361352
for i in np.arange(len(p0)):
362353
self.tableWidget.setItem(i, 0, QTableWidgetItem(str(round(p0[i],1))))
363354
self.tableWidget.setItem(i, 1, QTableWidgetItem(str(round(p1[i],1))))
364-
self.custom_bands[i,0] = round(p0[i],1)
365-
self.custom_bands[i,1] = round(p1[i],1)
366355
except: # if data does not exist in file
367356
text = "'Custom' frequency decomposition data does not exist in current file.\n" \
368357
"To create it, add the bands of interest to the table. "\
@@ -390,12 +379,12 @@ def del_band(self):
390379
self.tableWidget.removeRow(nRows-1)
391380

392381
def run_decomposition(self):
393-
if self.decomp_type=='custom':
382+
if self.decomp_type!='default':
394383
nRows = self.tableWidget.rowCount()
395-
self.custom_bands = np.zeros((2,nRows))
384+
self.chosen_bands = np.zeros((2,nRows))
396385
for i in np.arange(nRows):
397-
self.custom_bands[0,i] = float(self.tableWidget.item(i, 0).text())
398-
self.custom_bands[1,i] = float(self.tableWidget.item(i, 1).text())
386+
self.chosen_bands[0,i] = float(self.tableWidget.item(i, 0).text())
387+
self.chosen_bands[1,i] = float(self.tableWidget.item(i, 1).text())
399388
# If Decomposition data does not exist in NWB file and user decides to create it
400389
self.label_2.setText('Processing spectral decomposition. Please wait...')
401390
self.pushButton_1.setEnabled(False)
@@ -409,7 +398,7 @@ def run_decomposition(self):
409398
block = [ aux.split('.')[0][1:] ]
410399
self.thread = ChildProgram(path=self.fpath, subject=subj,
411400
blocks=block, filter=self.decomp_type,
412-
bands_vals=self.custom_bands)
401+
bands_vals=self.chosen_bands)
413402
self.thread.finished.connect(self.out_close)
414403
self.thread.start()
415404

File renamed without changes.

ecogvis/signal_processing/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .hilbert_transform import *
2+
from .resample import *
3+
from .linenoise_notch import *
4+
#from .zscore import *
5+
from .common_referencing import *
6+
from .preprocess_data import *

ecogvis/signal_processing/bands.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
Frequency band information for different types of data processing.
3+
"""
4+
import numpy as np
5+
6+
# Chang lab frequencies
7+
fq_min = 4.0749286538265
8+
fq_max = 200.
9+
scale = 7.
10+
cfs = 2 ** (np.arange(np.log2(fq_min) * scale, np.log2(fq_max) * scale) / scale)
11+
cfs = np.array(cfs)
12+
sds = 10 ** ( np.log10(.39) + .5 * (np.log10(cfs)))
13+
sds = np.array(sds) * np.sqrt(2.)
14+
chang_lab = {'fq_min': fq_min,
15+
'fq_max': fq_max,
16+
'scale': scale,
17+
'cfs': cfs,
18+
'sds': sds}
19+
20+
# Standard neuro bands
21+
bands = ['theta', 'alpha', 'beta', 'gamma', 'high gamma']
22+
min_freqs = [4., 8., 15., 30., 70.]
23+
max_freqs = [7., 14., 29., 59., 150.]
24+
HG_freq = 200.
25+
neuro = {'bands': bands,
26+
'min_freqs': min_freqs,
27+
'max_freqs': max_freqs,
28+
'HG_freq': HG_freq}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from __future__ import division
2+
import numpy as np
3+
4+
5+
__all__ = ['subtract_CAR',
6+
'subtract_common_median_reference']
7+
8+
def subtract_CAR(X, b_size=16):
9+
"""
10+
Compute and subtract common average reference in 16 channel blocks.
11+
"""
12+
13+
channels, time_points = X.shape
14+
s = channels // b_size
15+
r = channels % b_size
16+
17+
X_1 = X[:channels-r].copy()
18+
19+
X_1 = X_1.reshape((s, b_size, time_points))
20+
X_1 -= np.nanmean(X_1, axis=1, keepdims=True)
21+
if r > 0:
22+
X_2 = X[channels-r:].copy()
23+
X_2 -= np.nanmean(X_2, axis=0, keepdims=True)
24+
X = np.vstack([X_1.reshape((s*b_size, time_points)), X_2])
25+
return X
26+
else:
27+
return X_1.reshape((s*b_size, time_points))
28+
29+
30+
def subtract_common_median_reference(X, channel_axis=-2):
31+
"""
32+
Compute and subtract common median reference
33+
for the entire grid.
34+
35+
Parameters
36+
----------
37+
X : ndarray (..., n_channels, n_time)
38+
Data to common median reference.
39+
40+
Returns
41+
-------
42+
Xp : ndarray (..., n_channels, n_time)
43+
Common median referenced data.
44+
"""
45+
46+
median = np.nanmedian(X, axis=channel_axis, keepdims=True)
47+
Xp = X - median
48+
49+
return Xp

ecogvis/signal_processing/fft.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import numpy as np
2+
3+
from numpy.fft import rfftfreq, fftfreq
4+
5+
try:
6+
from mkl_fft._numpy_fft import rfft, irfft, fft, ifft
7+
except ImportError:
8+
try:
9+
from pyfftw.interfaces.numpy_fft import rfft, irfft, fft, ifft
10+
except ImportError:
11+
from numpy.fft import rfft, irfft, fft, ifft
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from __future__ import division
2+
import numpy as np
3+
4+
from .fft import fftfreq, fft, ifft
5+
try:
6+
from accelerate.mkl.fftpack import fft, ifft
7+
except ImportError:
8+
try:
9+
from pyfftw.interfaces.numpy_fft import fft, ifft
10+
except ImportError:
11+
from numpy.fft import fft, ifft
12+
13+
14+
__authors__ = "Alex Bujan, Jesse Livezey"
15+
16+
17+
__all__ = ['gaussian', 'hamming', 'hilbert_transform']
18+
19+
20+
def gaussian(X, rate, center, sd):
21+
time = X.shape[-1]
22+
freq = fftfreq(time, 1./rate)
23+
24+
k = np.exp((-(np.abs(freq) - center)**2)/(2 * (sd**2)))
25+
k /= np.linalg.norm(k)
26+
27+
return k
28+
29+
30+
def hamming(X, rate, min_freq, max_freq):
31+
time = X.shape[-1]
32+
freq = fftfreq(time, 1./rate)
33+
34+
pos_in_window = np.logical_and(freq >= min_freq, freq <= max_freq)
35+
neg_in_window = np.logical_and(freq <= -min_freq, freq >= -max_freq)
36+
37+
k = np.zeros(len(freq))
38+
window_size = np.count_nonzero(pos_in_window)
39+
window = np.hamming(window_size)
40+
k[pos_in_window] = window
41+
window_size = np.count_nonzero(neg_in_window)
42+
window = np.hamming(window_size)
43+
k[neg_in_window] = window
44+
k /= np.linalg.norm(k)
45+
46+
return k
47+
48+
49+
def hilbert_transform(X, rate, filters=None, phase=None, X_fft_h=None):
50+
"""
51+
Apply bandpass filtering with Hilbert transform using
52+
a prespecified set of filters.
53+
54+
Parameters
55+
----------
56+
X : ndarray (n_channels, n_time)
57+
Input data, dimensions
58+
rate : float
59+
Number of samples per second.
60+
filters : filter or list of filters (optional)
61+
One or more bandpass filters
62+
63+
Returns
64+
-------
65+
Xh : ndarray, complex
66+
Bandpassed analytic signal
67+
"""
68+
if not isinstance(filters, list):
69+
filters = [filters]
70+
time = X.shape[-1]
71+
freq = fftfreq(time, 1. / rate)
72+
73+
Xh = np.zeros((len(filters),) + X.shape, dtype=np.complex)
74+
if X_fft_h is None:
75+
# Heavyside filter
76+
h = np.zeros(len(freq))
77+
h[freq > 0] = 2.
78+
h[0] = 1.
79+
h = h[np.newaxis, :]
80+
X_fft_h = fft(X) * h
81+
if phase is not None:
82+
X_fft_h *= phase
83+
for ii, f in enumerate(filters):
84+
if f is None:
85+
Xh[ii] = ifft(X_fft_h)
86+
else:
87+
f = f / np.linalg.norm(f)
88+
Xh[ii] = ifft(X_fft_h * f)
89+
if Xh.shape[0] == 1:
90+
return Xh[0], X_fft_h
91+
92+
return Xh, X_fft_h

0 commit comments

Comments
 (0)