diff --git a/PythonPlugin/PythonEditor.cpp b/PythonPlugin/PythonEditor.cpp index 3dac266..86bcb03 100644 --- a/PythonPlugin/PythonEditor.cpp +++ b/PythonPlugin/PythonEditor.cpp @@ -146,6 +146,11 @@ void PythonEditor::buttonEvent(Button* button) } } +void PythonEditor::channelChanged(int chan, bool newState) +{ + pythonPlugin->channelChanged(chan, newState); +} + void PythonEditor::saveCustomParameters(XmlElement* xml) { diff --git a/PythonPlugin/PythonEditor.h b/PythonPlugin/PythonEditor.h index 719ab3b..a979628 100644 --- a/PythonPlugin/PythonEditor.h +++ b/PythonPlugin/PythonEditor.h @@ -51,6 +51,8 @@ class PythonEditor : public GenericEditor void buttonEvent(Button* button); + void channelChanged(int chan, bool newState) override; + void setFile(String file); void saveCustomParameters(XmlElement*); diff --git a/PythonPlugin/PythonPlugin.cpp b/PythonPlugin/PythonPlugin.cpp index 938677d..2bd26ed 100644 --- a/PythonPlugin/PythonPlugin.cpp +++ b/PythonPlugin/PythonPlugin.cpp @@ -69,7 +69,6 @@ v #else #define GET_TID uint64_t tid; pthread_threadid_np(NULL, &tid) #endif - #define DEBUG_LOG(str) JUCE_BLOCK_WITH_FORCED_SEMICOLON(std::cout << str << std::endl;) #define LOG_ENTER(fname) \ @@ -334,7 +333,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find init function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } initfunc_t initF = (initfunc_t)initializer; @@ -345,7 +344,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find ready function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } pluginIsReady = (isreadyfunc_t)cfunc; @@ -356,7 +355,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find startup function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } pluginStartupFunction = (startupfunc_t)cfunc; @@ -368,7 +367,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find getParamNum function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } getParamNumFunction = (getparamnumfunc_t)cfunc; @@ -379,7 +378,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find getParamConfig function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } getParamConfigFunction = (getparamconfigfunc_t)cfunc; @@ -390,7 +389,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find plugin function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } pluginFunction = (pluginfunc_t)cfunc; @@ -402,7 +401,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find event function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } eventFunction = (eventfunc_t)cfunc; @@ -413,20 +412,42 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find spike function in plugin " << '"' << pluginName << '"' << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } spikeFunction = (spikefunc_t)cfunc; // CJB added end + cfunc = plugin.getFunction("updateSettings"); + if (!cfunc) + { + std::cout << "Can't find updateSettings function in plugin " + << '"' << pluginName << "\"" << std::endl + << lastError() << std::endl; + resetPlugin(); + return; + } + updateSettingsFunction = (updatefunc_t)cfunc; + + cfunc = plugin.getFunction("channelChanged"); + if (!cfunc) + { + std::cout << "Can't find channelChanged function in plugin " + << '"' << pluginName << "\"" << std::endl + << lastError() << std::endl; + resetPlugin(); + return; + } + channelChangedFunction = (chanchangefunc_t)cfunc; + cfunc = plugin.getFunction("setIntParam"); if (!cfunc) { std::cout << "Can't find setIntParam function in plugin " << '"' << pluginName << "\"" << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } setIntParamFunction = (setintparamfunc_t)cfunc; @@ -437,7 +458,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find setFloatParam function in plugin " << '"' << pluginName << "\"" << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } setFloatParamFunction = (setfloatparamfunc_t)cfunc; @@ -448,7 +469,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find getIntParam function in plugin " << '"' << pluginName << "\"" << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } getIntParamFunction = (getintparamfunc_t)cfunc; @@ -459,7 +480,7 @@ void PythonPlugin::setFile(String fullpath) std::cout << "Can't find getFloatParam function in plugin " << '"' << pluginName << "\"" << std::endl << lastError() << std::endl; - plugin.close(); + resetPlugin(); return; } getFloatParamFunction = (getfloatparamfunc_t)cfunc; @@ -476,7 +497,7 @@ void PythonPlugin::setFile(String fullpath) DEBUG_LOG("after initplugin"); - (*pluginStartupFunction)(dataSampleRate); + (*pluginStartupFunction)(nChans, dataSampleRate, chanEnabled.getRawDataPointer()); // load the parameter configuration numPythonParams = (*getParamNumFunction)(); @@ -526,6 +547,36 @@ void PythonPlugin::updateSettings() } else { dataSampleRate = GenericProcessor::getSampleRate(); } + + // update number of channels + int prevChans = nChans; + nChans = getNumInputs(); + + chanEnabled.resize(nChans); + + for (int c = prevChans; c < nChans; ++c) + { + // new channels are enabled by default + chanEnabled.set(c, true); + } + + if (updateSettingsFunction) + { + const PythonLock pyLock; + (*updateSettingsFunction)(nChans, dataSampleRate); + } +} + +void PythonPlugin::channelChanged(int chan, bool state) +{ + jassert(chan >= 0 && chan < chanEnabled.size()); + chanEnabled.set(chan, state); + + if (channelChangedFunction) + { + const PythonLock pyLock; + (*channelChangedFunction)(chan, state); + } } void PythonPlugin::setIntPythonParameter(String name, int value) @@ -564,6 +615,25 @@ float PythonPlugin::getFloatPythonParameter(String name) return value; } +void PythonPlugin::resetPlugin() +{ + pluginFunction = nullptr; + pluginIsReady = nullptr; + pluginStartupFunction = nullptr; + getParamNumFunction = nullptr; + getParamConfigFunction = nullptr; + updateSettingsFunction = nullptr; + channelChangedFunction = nullptr; + setIntParamFunction = nullptr; + setFloatParamFunction = nullptr; + getIntParamFunction = nullptr; + getFloatParamFunction = nullptr; + eventFunction = nullptr; + spikeFunction = nullptr; + + plugin.close(); +} + // PythonLock diff --git a/PythonPlugin/PythonPlugin.h b/PythonPlugin/PythonPlugin.h index 4b6167b..1ad847f 100644 --- a/PythonPlugin/PythonPlugin.h +++ b/PythonPlugin/PythonPlugin.h @@ -60,13 +60,15 @@ typedef PyObject * (*initfunc_t)(void); //#else //typedef PyMODINIT_FUNC (*initfunc_t)(void); //#endif -typedef void (*startupfunc_t)(float); // passes the sampling rate +typedef void (*startupfunc_t)(int, float, int*); // passes the sampling rate and channel states typedef void (*eventfunc_t)(int, int, int, double, int);// CJB added typedef void (*spikefunc_t)(int, int, float[18]);// CJB added typedef void (*pluginfunc_t)(float *, int, int, int, PythonEvent *); typedef int (*isreadyfunc_t)(void); typedef int (*getparamnumfunc_t)(void); typedef void (*getparamconfigfunc_t)(struct ParamConfig*); +typedef void (*updatefunc_t)(int, float); +typedef void (*chanchangefunc_t)(int, int); typedef void (*setintparamfunc_t)(char*, int); typedef void (*setfloatparamfunc_t)(char*, float); typedef int (*getintparamfunc_t)(char*); @@ -127,6 +129,7 @@ class PythonPlugin : public GenericProcessor } void updateSettings(); + void channelChanged(int chan, bool state); void createEventChannels(); void setFile(String fullpath); String getFile(); @@ -155,6 +158,9 @@ class PythonPlugin : public GenericProcessor private: void sendEventPlugin(int eventType, int sourceID, int subProcessorIdx, double timestamp, int sourceIndex); //CJB added + // close plugin library and reset all functions to null + void resetPlugin(); + /* Added by EBB Why do it this way: * Using a class allows object destruction to control releasing the GIL (RAII) @@ -185,21 +191,27 @@ class PythonPlugin : public GenericProcessor int numPythonParams = 0; ParamConfig *params; Component **paramsControl; - // var for stashing the sample rate + + // data to keep track of and send to plugin + int nChans = 0; float dataSampleRate = 44100; + Array chanEnabled; + // function pointers to the python plugin - pluginfunc_t pluginFunction; - isreadyfunc_t pluginIsReady; - startupfunc_t pluginStartupFunction; - getparamnumfunc_t getParamNumFunction; - getparamconfigfunc_t getParamConfigFunction; - setintparamfunc_t setIntParamFunction; - setfloatparamfunc_t setFloatParamFunction; - getintparamfunc_t getIntParamFunction; - getfloatparamfunc_t getFloatParamFunction; - eventfunc_t eventFunction; - spikefunc_t spikeFunction; - const EventChannel* ttlChannel{ nullptr }; + pluginfunc_t pluginFunction = nullptr; + isreadyfunc_t pluginIsReady = nullptr; + startupfunc_t pluginStartupFunction = nullptr; + getparamnumfunc_t getParamNumFunction = nullptr; + getparamconfigfunc_t getParamConfigFunction = nullptr; + updatefunc_t updateSettingsFunction = nullptr; + chanchangefunc_t channelChangedFunction = nullptr; + setintparamfunc_t setIntParamFunction = nullptr; + setfloatparamfunc_t setFloatParamFunction = nullptr; + getintparamfunc_t getIntParamFunction = nullptr; + getfloatparamfunc_t getFloatParamFunction = nullptr; + eventfunc_t eventFunction = nullptr; + spikefunc_t spikeFunction = nullptr; + const EventChannel* ttlChannel = nullptr; bool wasTriggered = 0; uint16 lastChan = 0; diff --git a/PythonPlugin/PythonSource.cpp b/PythonPlugin/PythonSource.cpp index 2e943fa..8483948 100644 --- a/PythonPlugin/PythonSource.cpp +++ b/PythonPlugin/PythonSource.cpp @@ -1,28 +1,28 @@ /* ------------------------------------------------------------------ - + Python Plugin Copyright (C) 2016 FP Battaglia - + based on Open Ephys GUI Copyright (C) 2013, 2015 Open Ephys - + ------------------------------------------------------------------ - + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . - + */ /* ============================================================================== @@ -40,7 +40,17 @@ PythonSource::PythonSource() : PythonPlugin("Python Source") //, threshold(200.0), state(true) { + nChans = 16; +} + +int getDefaultNumDataOutputs(DataChannel::DataChannelTypes type, int subProcessorIdx = 0) +{ + return nChans; +} +int getNumOutputs(int subProcessorIdx) +{ + return nChans; } PythonSource::~PythonSource() diff --git a/PythonPlugin/PythonSource.h b/PythonPlugin/PythonSource.h index cada6ab..5521bc7 100644 --- a/PythonPlugin/PythonSource.h +++ b/PythonPlugin/PythonSource.h @@ -1,28 +1,28 @@ /* ------------------------------------------------------------------ - + Python Plugin Copyright (C) 2016 FP Battaglia - + based on Open Ephys GUI Copyright (C) 2013, 2015 Open Ephys - + ------------------------------------------------------------------ -v +v This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . - + */ /* ============================================================================== @@ -34,6 +34,11 @@ v ============================================================================== */ +//virtual int getNumOutputs() const; +//GenericProcessor.h: virtual int getNumOutputs(int subProcessorIdx) const; +//GenericProcessor.h: int numOutputs; +//GenericProcessor.h: virtual int getDefaultNumDataOutputs(DataChannel::DataChannelTypes type, int subProcessorIdx = 0) const; + #ifndef __PYTHONSOURCE_H #define __PYTHONSOURCE_H @@ -60,7 +65,9 @@ class PythonSource : public PythonPlugin return false; } + virtual int getDefaultNumDataOutputs(DataChannel::DataChannelTypes type, int subProcessorIdx = 0) const; + virtual int getNumOutputs(int subProcessorIdx) const; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PythonSource); diff --git a/README.md b/README.md index c287b69..6d6671b 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ def handleEvents(eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): - The handleSpike(self,electrode,sortedID,n_arr) function passes on spike events generated elsewhere in the OE signal chain to the python plugin. the n_arr is an 18 element long spike waveform. ### Compilation -Currently, only Cython version 0.28.2 is supported. Recently downloaded or upgraded versions of Anaconda will come with version 0.29.2, which will cause the application to crash upon loading a python module. To avoid this, we recommend creating a virtual enviroment with the correct versions of python and cython by running: +Currently, only Cython version 0.28.2 is supported. Recently downloaded or upgraded versions of Anaconda will come with version 0.29.2, which will cause the application to crash upon loading a python module. To avoid this, create a virtual enviroment with the correct versions of python and cython by running: ``` conda create -n oeEnv python=3.6 cython=0.28.2 diff --git a/python_modules/plugin.pyx b/python_modules/plugin.pyx index 3ab55c1..016e918 100644 --- a/python_modules/plugin.pyx +++ b/python_modules/plugin.pyx @@ -38,12 +38,18 @@ cdef extern from "PythonEvent.h": # noinspection PyPep8Naming -cdef public void pluginStartup(float sampling_rate): +cdef public void pluginStartup(int nChans, float samplingRate, int *chanStates): print("pre anything") global isDebug print("after is debug") global pluginOp - pluginOp.startup(sampling_rate) + cdef bint[:] states + if nChans == 0: + # pointer might be null + pluginOp.startup(nChans, samplingRate, []) + else: + states = ( chanStates) + pluginOp.startup(nChans, samplingRate, states) # noinspection PyPep8Naming cdef public int getParamNum(): @@ -154,6 +160,15 @@ cdef void add_event(PythonEvent *e_c, object e_py): cdef public int pluginisready(): return pluginOp.is_ready() + +# called from C++ updateSettings (not during acquisition) +cdef public void updateSettings(int nChans, float samplingRate): + pluginOp.update_settings(nChans, samplingRate) + +# called any time param button is changed (maybe during acquisition) +cdef public void channelChanged(int chan, int newState): + pluginOp.channel_changed(chan, newState) + # noinspection PyPep8Naming cdef public void setIntParam(char *name, int value): if isDebug: diff --git a/python_modules/pulse_test_delay/pulse_test_delay.pyx b/python_modules/pulse_test_delay/pulse_test_delay.pyx index ad59b54..f81595c 100644 --- a/python_modules/pulse_test_delay/pulse_test_delay.pyx +++ b/python_modules/pulse_test_delay/pulse_test_delay.pyx @@ -14,6 +14,8 @@ class pulse_test_delay(object): def __init__(self): """initialize object data""" self.Enabled = 1 + self.chan_enabled = [] + self.chan_in = 0 self.thresh_min = -2 self.thresh_max = 2 @@ -23,10 +25,13 @@ class pulse_test_delay(object): self.triggered = 0 self.samplingRate = 0 - def startup(self, sr): + def startup(self, nchans, srate, states): """to be run upon startup""" - self.samplingRate = sr - print (self.samplingRate) + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) + self.arduino = serial.Serial('/dev/tty.usbmodem45561', 57600) print ("Arduino: ", self.arduino) self.Enabled = 1 @@ -47,6 +52,22 @@ class pulse_test_delay(object): ("int_set", "chan_in", chan_labels), ("float_range", "threshold", self.thresh_min, self.thresh_max, self.thresh_start)) + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + if srate != self.samplingRate: + self.samplingRate = srate + print(self.samplingRate) + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + del self.chan_enabled[nchans:] + elif len(self.chan_enabled) < nchans: + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" #print ("plugin start") diff --git a/python_modules/pulse_test_delay/setup.py b/python_modules/pulse_test_delay/setup.py index 5c979a4..6c9f122 100644 --- a/python_modules/pulse_test_delay/setup.py +++ b/python_modules/pulse_test_delay/setup.py @@ -5,12 +5,29 @@ cfg = runpy.run_path('../.config.py') - setup( - name="pulse_test_delay", - ext_modules=cythonize(Extension('pulse_test_delay', sources=["pulse_test_delay.pyx"], - export_symbols=['pluginStartup', 'pluginisready', 'getParamNum', 'getParamConfig', - 'pluginFunction', 'eventFunction', 'spikeFunction', 'setIntParam', - 'setFloatParam', 'getIntParam', 'getFloatParam'])), - include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] -) + name="pulse_test_delay", + include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], + ext_modules=cythonize( + Extension( + 'pulse_test_delay', + sources=["pulse_test_delay.pyx"], + export_symbols=[ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 + ) +) \ No newline at end of file diff --git a/python_modules/spwdouble/setup.py b/python_modules/spwdouble/setup.py index eb6b2e0..a5c620f 100644 --- a/python_modules/spwdouble/setup.py +++ b/python_modules/spwdouble/setup.py @@ -7,7 +7,28 @@ setup( - name= "spwdouble", - ext_modules = cythonize(Extension('spwdouble',sources=["spwdouble.pyx"],export_symbols=['pluginStartup','pluginisready','getParamNum','getParamConfig','pluginFunction','eventFunction','spikeFunction','setIntParam','setFloatParam','getIntParam','getFloatParam'])), - include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] + name="spwdouble", + include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], + ext_modules=cythonize( + Extension( + 'spwdouble', + sources=["spwdouble.pyx"], + export_symbols=[ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 ) +) \ No newline at end of file diff --git a/python_modules/spwdouble/spwdouble.pyx b/python_modules/spwdouble/spwdouble.pyx index ea0785d..b6d2998 100644 --- a/python_modules/spwdouble/spwdouble.pyx +++ b/python_modules/spwdouble/spwdouble.pyx @@ -16,6 +16,8 @@ class spwdouble(object): def __init__(self): """initialize object data""" self.Enabled = 1 + self.chan_enabled = [] + self.jitter_count_down_thresh = 0 self.jitter_count_down = 0 self.jitter_time = 200. # in ms @@ -82,20 +84,13 @@ class spwdouble(object): self.state = self.READY logging.basicConfig(filename='spwdouble.log', format='%(asctime)s %(message)s', level=logging.DEBUG) - def startup(self, sr): + def startup(self, nchans, srate, states): """to be run upon startup""" - self.samplingRate = sr - - # noinspection PyTupleAssignmentBalance - self.filter_b, self.filter_a = scipy.signal.butter(3, - (self.band_lo/(self.samplingRate/2), self.band_hi/(self.samplingRate/2)), - 'pass') - print(self.filter_a) - print(self.filter_b) - print(self.band_lo) - print(self.band_hi) - print(self.band_lo/(self.samplingRate/2)) - print(self.band_hi/(self.samplingRate/2)) + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) + self.Enabled = 1 try: self.arduino = serial.Serial('/dev/ttyACM0', 57600) @@ -136,6 +131,32 @@ class spwdouble(object): timestamp = self.n_samples events.append({'type': 3, 'sampleNum': timestamp, 'eventId': code, 'eventChannel': channel}) + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + if srate != self.samplingRate: + self.samplingRate = srate + + # noinspection PyTupleAssignmentBalance + self.filter_b, self.filter_a = scipy.signal.butter(3, + (self.band_lo/(self.samplingRate/2), self.band_hi/(self.samplingRate/2)), + 'pass') + print(self.filter_a) + print(self.filter_b) + print(self.band_lo) + print(self.band_hi) + print(self.band_lo/(self.samplingRate/2)) + print(self.band_hi/(self.samplingRate/2)) + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + del self.chan_enabled[nchans:] + elif len(self.chan_enabled) < nchans: + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" if isDebug: diff --git a/python_modules/spwfinder/setup.py b/python_modules/spwfinder/setup.py index de02dc8..d6aecb6 100644 --- a/python_modules/spwfinder/setup.py +++ b/python_modules/spwfinder/setup.py @@ -7,6 +7,27 @@ setup( name= "spwfinder", - ext_modules = cythonize(Extension('spwfinder',sources=["spwfinder.pyx"],export_symbols=['pluginStartup','pluginisready','getParamNum','getParamConfig','pluginFunction','eventFunction','spikeFunction','setIntParam','setFloatParam','getIntParam','getFloatParam'])), - include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] + include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], + ext_modules = cythonize( + Extension( + 'spwfinder', + sources=["spwfinder.pyx"], + export_symbols=[ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 ) +) \ No newline at end of file diff --git a/python_modules/spwfinder/spwfinder.pyx b/python_modules/spwfinder/spwfinder.pyx index d6eb1f0..7ae3d67 100644 --- a/python_modules/spwfinder/spwfinder.pyx +++ b/python_modules/spwfinder/spwfinder.pyx @@ -16,6 +16,8 @@ class spwfinder(object): def __init__(self): """initialize object data""" self.Enabled = 1 + self.chan_enabled = [] + self.jitter = False self.jitter_count_down_thresh = 0 self.jitter_count_down = 0 @@ -76,22 +78,13 @@ class spwfinder(object): self.FIRING = 4 self.state = self.READY - def startup(self, sr): + def startup(self, nchans, srate, states): """to be run upon startup""" - self.samplingRate = sr - - # noinspection PyTupleAssignmentBalance - self.filter_b, self.filter_a = scipy.signal.butter(3, - (self.band_lo / (self.samplingRate / 2), - self.band_hi / (self.samplingRate / 2)), - btype='bandpass', - output='ba') - print(self.filter_a) - print(self.filter_b) - print(self.band_lo) - print(self.band_hi) - print(self.band_lo / (self.samplingRate / 2)) - print(self.band_hi / (self.samplingRate / 2)) + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) + self.Enabled = 1 self.jitter = 0 try: @@ -133,6 +126,34 @@ class spwfinder(object): timestamp = self.n_samples events.append({'type': 3, 'sampleNum': timestamp, 'eventId': code, 'eventChannel': channel}) + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + if srate != self.samplingRate: + self.samplingRate = srate + + # noinspection PyTupleAssignmentBalance + self.filter_b, self.filter_a = scipy.signal.butter(3, + (self.band_lo / (self.samplingRate / 2), + self.band_hi / (self.samplingRate / 2)), + btype='bandpass', + output='ba') + print(self.filter_a) + print(self.filter_b) + print(self.band_lo) + print(self.band_hi) + print(self.band_lo / (self.samplingRate / 2)) + print(self.band_hi / (self.samplingRate / 2)) + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + del self.chan_enabled[nchans:] + elif len(self.chan_enabled) < nchans: + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" """Access to voltage data buffer. Returns events""" diff --git a/python_modules/spwrandom/setup.py b/python_modules/spwrandom/setup.py index b4787ce..0e747bd 100644 --- a/python_modules/spwrandom/setup.py +++ b/python_modules/spwrandom/setup.py @@ -7,6 +7,27 @@ setup( name= "spwrandom", - ext_modules = cythonize(Extension('spwrandom',sources=["spwrandom.pyx"],export_symbols=['pluginStartup','pluginisready','getParamNum','getParamConfig','pluginFunction','eventFunction','spikeFunction','setIntParam','setFloatParam','getIntParam','getFloatParam'])), - include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] + include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], + ext_modules = cythonize( + Extension( + 'spwrandom', + sources=["spwrandom.pyx"], + export_symbols=[ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 ) +) diff --git a/python_modules/spwrandom/spwrandom.pyx b/python_modules/spwrandom/spwrandom.pyx index 3914f61..9c3afd6 100644 --- a/python_modules/spwrandom/spwrandom.pyx +++ b/python_modules/spwrandom/spwrandom.pyx @@ -14,6 +14,8 @@ class spwrandom(object): def __init__(self): """initialize object data""" self.Enabled = 1 + self.chan_enabled = [] + self.refractory_count_down_thresh = 0 self.refractory_count_down = 0 self.refractory_time = 100. # time that the plugin will not react to trigger after one pulse @@ -54,10 +56,12 @@ class spwrandom(object): self.swing_thresh_start = 1000. self.swing_thresh = self.swing_thresh_start - def startup(self, sr): + def startup(self, nchans, srate, states): """to be run upon startup""" - self.samplingRate = sr - print (self.samplingRate) + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) print('starting random stimulation at rate ', self.random_stim_rate) @@ -98,6 +102,22 @@ class spwrandom(object): timestamp = self.n_samples events.append({'type': 3, 'sampleNum': timestamp, 'eventId': code, 'eventChannel': channel}) + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + if srate != self.samplingRate: + self.samplingRate = srate + print (self.samplintRate) + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + del self.chan_enabled[nchans:] + elif len(self.chan_enabled) < nchans: + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" #print("plugin start") diff --git a/python_modules/template/setup.py b/python_modules/template/setup.py index 4bafa05..5a61026 100644 --- a/python_modules/template/setup.py +++ b/python_modules/template/setup.py @@ -8,17 +8,26 @@ setup( name="EXAMPLE", include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], - ext_modules=cythonize(Extension('EXAMPLE', sources=["EXAMPLE.pyx"], export_symbols=[ - 'pluginStartup', - 'pluginisready', - 'getParamNum', - 'getParamConfig', - 'pluginFunction', - 'eventFunction', - 'spikeFunction', - 'setIntParam', - 'setFloatParam', - 'getIntParam', - 'getFloatParam' - ])) + ext_modules=cythonize( + Extension( + 'EXAMPLE', + sources=["EXAMPLE.pyx"], + export_symbols=[ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 + ) ) diff --git a/python_modules/template/template.pyx b/python_modules/template/template.pyx index 1de58fc..27b2f2f 100644 --- a/python_modules/template/template.pyx +++ b/python_modules/template/template.pyx @@ -9,30 +9,53 @@ class EXAMPLE(object): def __init__(self): """initialize object data""" self.Enabled = 1 - def startup(self, sr): + self.samplingRate = 0. + self.chan_enabled = [] + + def startup(self, nchans, srate, states): """to be run upon startup""" + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) + def plugin_name(self): """tells OE the name of the program""" return "EXAMPLE" + def is_ready(self): """tells OE everything ran smoothly""" return self.Enabled + def param_config(self): """return button, sliders, etc to be present in the editor OE side""" return [] + + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + self.samplingRate = srate + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + del self.chan_enabled[nchans:] + elif len(self.chan_enabled) < nchans: + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" events = [] return events + def handleEvents(self, eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): """handle events passed from OE""" + def handleSpike(self, electrode, sortedID, n_arr): """handle spikes passed from OE""" - pluginOp = EXAMPLE() include '../plugin.pyx' - - - diff --git a/python_modules/test/setup.py b/python_modules/test/setup.py index 67ef900..7bfc949 100644 --- a/python_modules/test/setup.py +++ b/python_modules/test/setup.py @@ -8,17 +8,26 @@ setup( name="test", include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], - ext_modules=cythonize(Extension('test', sources=["test.pyx"], export_symbols=[ - 'pluginStartup', - 'pluginisready', - 'getParamNum', - 'getParamConfig', - 'pluginFunction', - 'eventFunction', - 'spikeFunction', - 'setIntParam', - 'setFloatParam', - 'getIntParam', - 'getFloatParam' - ])) + ext_modules=cythonize( + Extension( + 'test', + sources=["test.pyx"], + export_symbols=[ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 + ) ) diff --git a/python_modules/test/test.pyx b/python_modules/test/test.pyx index e62c137..d15c0d2 100644 --- a/python_modules/test/test.pyx +++ b/python_modules/test/test.pyx @@ -9,23 +9,59 @@ class test(object): def __init__(self): """initialize object data""" self.Enabled = 1 - def startup(self, sr): + self.samplingRate = 0. + self.chan_enabled = [] + + def startup(self, nchans, srate, states): """to be run upon startup""" + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) + def plugin_name(self): """tells OE the name of the program""" return "test" + def is_ready(self): """tells OE everything ran smoothly""" return self.Enabled + def param_config(self): """return button, sliders, etc to be present in the editor OE side""" return [] + + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + print('Setting sample rate to', srate) + self.samplingRate = srate + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + print('Removing all but first', nchans, 'channels') + del self.chan_enabled[nchans:] + + elif len(self.chan_enabled) < nchans: + print('Adding', nchans - old_nchans, 'new channels') + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + if state: + print('Enabling channel', chan) + else: + print('Disabling channel', chan) + + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" events = [] return events + def handleEvents(self, eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): """handle events passed from OE""" + def handleSpike(self, electrode, sortedID, n_arr): """handle spikes passed from OE""" @@ -34,5 +70,3 @@ pluginOp = test() include '../plugin.pyx' - - diff --git a/python_modules/test2/setup.py b/python_modules/test2/setup.py index 20c9946..a5222a9 100644 --- a/python_modules/test2/setup.py +++ b/python_modules/test2/setup.py @@ -8,19 +8,28 @@ setup( name= "test2", include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']], - ext_modules = cythonize(Extension('test2', sources = ["test2.pyx"], export_symbols = [ - 'pluginStartup', - 'pluginisready', - 'getParamNum', - 'getParamConfig', - 'pluginFunction', - 'eventFunction', - 'spikeFunction', - 'setIntParam', - 'setFloatParam', - 'getIntParam', - 'getFloatParam' - ])) + ext_modules = cythonize( + Extension( + 'test2', + sources = ["test2.pyx"], + export_symbols = [ + 'pluginStartup', + 'pluginisready', + 'getParamNum', + 'getParamConfig', + 'pluginFunction', + 'eventFunction', + 'spikeFunction', + 'setIntParam', + 'setFloatParam', + 'getIntParam', + 'getFloatParam', + 'updateSettings', + 'channelChanged' + ] + ), + language_level=3 + ) ) diff --git a/python_modules/test2/test2.pyx b/python_modules/test2/test2.pyx index 2fb7e15..106c928 100644 --- a/python_modules/test2/test2.pyx +++ b/python_modules/test2/test2.pyx @@ -10,23 +10,51 @@ class test2(object): def __init__(self): """initialize object data""" self.Enabled = 1 - def startup(self, sr): + self.samplingRate = 0. + self.chan_enabled = [] + + def startup(self, nchans, srate, states): """to be run upon startup""" + self.update_settings(nchans, srate) + for chan in range(nchans): + if not states[chan]: + self.channel_changed(chan, False) + def plugin_name(self): """tells OE the name of the program""" return "test2" + def is_ready(self): """tells OE everything ran smoothly""" return self.Enabled + def param_config(self): """return button, sliders, etc to be present in the editor OE side""" return [] + + def update_settings(self, nchans, srate): + """handle changing number of channels and sample rates""" + self.samplingRate = srate + + old_nchans = len(self.chan_enabled) + if old_nchans > nchans: + del self.chan_enabled[nchans:] + + elif len(self.chan_enabled) < nchans: + self.chan_enabled.extend([True] * (nchans - old_nchans)) + + def channel_changed(self, chan, state): + """do something when channels are turned on or off in PARAMS tab""" + self.chan_enabled[chan] = state + def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" events = [] return events + def handleEvents(self, eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): """handle events passed from OE""" + def handleSpike(self, electrode, sortedID, n_arr): """handle spikes passed from OE"""