From 312bbe36751076deca62ffd0bd94168754bbfb8a Mon Sep 17 00:00:00 2001 From: Mark Schatza Date: Thu, 16 May 2019 09:22:51 -0500 Subject: [PATCH 1/6] Start of yPower plugin --- python_modules/test/setup.py | 4 +- python_modules/test2/setup.py | 4 +- python_modules/test2/test2.pyx | 16 ++-- python_modules/yPowerThresh/setup.py | 9 ++ python_modules/yPowerThresh/yPowerThresh.pyx | 93 ++++++++++++++++++++ 5 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 python_modules/yPowerThresh/setup.py create mode 100644 python_modules/yPowerThresh/yPowerThresh.pyx diff --git a/python_modules/test/setup.py b/python_modules/test/setup.py index d8ebae7..9cacca5 100644 --- a/python_modules/test/setup.py +++ b/python_modules/test/setup.py @@ -4,8 +4,6 @@ setup( name= "test", - ext_modules = cythonize("test.pyx"), + ext_modules = cythonize(Extension('test',sources=["test.pyx"],export_symbols=['pluginStartup','pluginisready','getParamNum','getParamConfig','pluginFunction','eventFunction','spikeFunction','setIntParam','setFloatParam','getIntParam','getFloatParam'])), include_dirs = [numpy.get_include()] ) - - diff --git a/python_modules/test2/setup.py b/python_modules/test2/setup.py index 6858d03..b0c2c05 100644 --- a/python_modules/test2/setup.py +++ b/python_modules/test2/setup.py @@ -4,8 +4,6 @@ setup( name= "test2", - ext_modules = cythonize("test2.pyx"), + ext_modules = cythonize(Extension('test2',sources=["test2.pyx"],export_symbols=['pluginStartup','pluginisready','getParamNum','getParamConfig','pluginFunction','eventFunction','spikeFunction','setIntParam','setFloatParam','getIntParam','getFloatParam'])), include_dirs = [numpy.get_include()] ) - - diff --git a/python_modules/test2/test2.pyx b/python_modules/test2/test2.pyx index 4c838b5..a3a8114 100644 --- a/python_modules/test2/test2.pyx +++ b/python_modules/test2/test2.pyx @@ -8,10 +8,15 @@ isDebug = False class test2(object): def __init__(self): + print('hello from init\n\n') """initialize object data""" self.Enabled = 1 + self.threshMin = -100 + self.threshMax = 100 def startup(self, sr): """to be run upon startup""" + #self.samplingRate = sr + print('start') def plugin_name(self): """tells OE the name of the program""" return "test2" @@ -20,9 +25,13 @@ class test2(object): return self.Enabled def param_config(self): """return button, sliders, etc to be present in the editor OE side""" - return [] + thresholdMin = ("float_range", "threshold min", self.threshMin, self.threshMax, 50) + thresholdMax = ("float_range", "threshold max", self.threshMin, self.threshMax, -50) + intMin = ("int_set", "int setting", [0,1,2,3,4]) + enable = ("toggle", "enabled", True) + return [enable, intMin] def bufferfunction(self, n_arr): - """Access to voltage data buffer. Returns events""" + """Access to voltage data buffer. Returns events""" events = [] return events def handleEvents(eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): @@ -34,6 +43,3 @@ class test2(object): pluginOp = test2() include '../plugin.pyx' - - - diff --git a/python_modules/yPowerThresh/setup.py b/python_modules/yPowerThresh/setup.py new file mode 100644 index 0000000..4bfb56e --- /dev/null +++ b/python_modules/yPowerThresh/setup.py @@ -0,0 +1,9 @@ +from distutils.core import setup, Extension +from Cython.Build import cythonize +import numpy + +setup( + name= "yPowerThresh", + ext_modules = cythonize(Extension('yPowerThresh',sources=["yPowerThresh.pyx"],export_symbols=['pluginStartup','pluginisready','getParamNum','getParamConfig','pluginFunction','eventFunction','spikeFunction','setIntParam','setFloatParam','getIntParam','getFloatParam'])), + include_dirs = [numpy.get_include()] + ) diff --git a/python_modules/yPowerThresh/yPowerThresh.pyx b/python_modules/yPowerThresh/yPowerThresh.pyx new file mode 100644 index 0000000..95e5812 --- /dev/null +++ b/python_modules/yPowerThresh/yPowerThresh.pyx @@ -0,0 +1,93 @@ +import sys +import numpy as np +cimport numpy as np +from cython cimport view +from scipy.signal import butter, lfilter, hilbert +import pandas as pd + +isDebug = False + +class yPowerThresh(object): + def __init__(self): + """initialize object data""" + self.Enabled = 1 + self.mean = 0 + self.count = 0 + self.std = 0 + self.thresh = 0 + + self.activeChan = 0 + self.peaks = np.zeros(12) + + # Wait 100 milliseconds between peaks(remove long lasting peaks counting mulitple times) + self.waitTime = 100 + self.curWait = 101 + + # Keep track of how long we've been creating our threshold + self.threshTime = 0 + def startup(self, sr): + """to be run upon startup""" + self.fs = sr + self.prelimLen = 3 * 60 * sr + + def plugin_name(self): + """tells OE the name of the program""" + return "yPowerThresh" + 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""" + self.activeChan = 0 # Create dropdown from activeChans? + return [] + def bufferfunction(self, n_arr): + """Access to voltage data buffer. Returns events""" + events = [] + + if self.threshTime < self.prelimLen: + if self.curWait > self.waitTime: + wave = band(n_arr[self.activeChan][:], 80, 200, self.fs) + hilbertData = hilbert(wave) + hilbertDF = pd.DataFrame(hilbertData) + hilbertDF = hilbertDF.abs() + hilbertDF = hilbertDF.pow(2) + max = hilbertDF.max() + + if max > self.peaks[-1]: + for i in range(len(self.peaks)): + if max >= self.peaks[i]: + np.concatenate(self.peaks[:i], [max], self.peaks[i:]) + self.peaks = self.peaks[:-1].copy() + self.curWait = 0 + + + #self.mean = self.mean * (self.count - 1) / self.count + hilbertDF.mean() / self.count + #self.std = self.std * (self.count - 1) / self.count + hilbertDF.std() / self.count + #self.count += 1 + else: + self.curWait += len(n_arr[self.activeChan]) / self.fs * 1000 + else: + events.append(self.peaks[-1]) + + return events + def handleEvents(eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): + """handle events passed from OE""" + def handleSpike(self, electrode, sortedID, n_arr): + """handle spikes passed from OE""" + +def butter_bandpass(lowcut, highcut, fs, order=5): + nyq = 0.5 * fs + low = lowcut / nyq + high = highcut / nyq + b, a = butter(order, [low, high], btype='band') + return b, a + +def band(data, low, high, fs): + b, a = butter_bandpass(low, high, fs, order=order) + y = lfilter(b, a, data) + return y + + +pluginOp = yPowerThresh() + +include '../plugin.pyx' From 707b3993ad9c7ef3fecf3f64db228ac32092f81e Mon Sep 17 00:00:00 2001 From: Mark Schatza Date: Fri, 17 May 2019 07:50:58 -0500 Subject: [PATCH 2/6] High gamma power threshold working --- python_modules/plugin.pyx | 2 - python_modules/yPowerThresh/yPowerThresh.pyx | 59 +++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/python_modules/plugin.pyx b/python_modules/plugin.pyx index bbf50ce..7a66045 100644 --- a/python_modules/plugin.pyx +++ b/python_modules/plugin.pyx @@ -39,9 +39,7 @@ cdef extern from "../../PythonPlugin/PythonEvent.h": # noinspection PyPep8Naming cdef public void pluginStartup(float sampling_rate) with gil: - print("pre anything") global isDebug - print("after is debug") global pluginOp pluginOp.startup(sampling_rate) diff --git a/python_modules/yPowerThresh/yPowerThresh.pyx b/python_modules/yPowerThresh/yPowerThresh.pyx index 95e5812..52adc0d 100644 --- a/python_modules/yPowerThresh/yPowerThresh.pyx +++ b/python_modules/yPowerThresh/yPowerThresh.pyx @@ -16,20 +16,19 @@ class yPowerThresh(object): self.std = 0 self.thresh = 0 - self.activeChan = 0 - self.peaks = np.zeros(12) + self.activeChan = 1 # Wait 100 milliseconds between peaks(remove long lasting peaks counting mulitple times) self.waitTime = 100 self.curWait = 101 - # Keep track of how long we've been creating our threshold - self.threshTime = 0 def startup(self, sr): """to be run upon startup""" self.fs = sr - self.prelimLen = 3 * 60 * sr - + self.prelimLen = 1 * 10 + self.peaks = np.zeros(3) + # Keep track of how long we've been creating our threshold + self.threshTime = 0 def plugin_name(self): """tells OE the name of the program""" return "yPowerThresh" @@ -38,36 +37,41 @@ class yPowerThresh(object): return self.Enabled def param_config(self): """return button, sliders, etc to be present in the editor OE side""" - self.activeChan = 0 # Create dropdown from activeChans? - return [] + chanLabels = range(1, 33) + #channel = {"int_set", "Active Channel", chanLabels} + #self.activeChan = 0 # Create dropdown from activeChans? + return [("int_set", "activeChan", chanLabels)] def bufferfunction(self, n_arr): """Access to voltage data buffer. Returns events""" events = [] + chanIn = self.activeChan - 1 if self.threshTime < self.prelimLen: + self.threshTime += len(n_arr[chanIn]) / self.fs if self.curWait > self.waitTime: - wave = band(n_arr[self.activeChan][:], 80, 200, self.fs) - hilbertData = hilbert(wave) - hilbertDF = pd.DataFrame(hilbertData) - hilbertDF = hilbertDF.abs() - hilbertDF = hilbertDF.pow(2) - max = hilbertDF.max() - - if max > self.peaks[-1]: + wave = pd.DataFrame(n_arr[chanIn][:]) + # wave = band(n_arr[self.activeChan][:], 80, 200, self.fs) + # hilbertData = hilbert(wave) + # hilbertDF = pd.DataFrame(hilbertData) + # hilbertDF = hilbertDF.abs() + # hilbertDF = hilbertDF.pow(2) + max = wave.max() + if max[0] > self.peaks[-1]: for i in range(len(self.peaks)): - if max >= self.peaks[i]: - np.concatenate(self.peaks[:i], [max], self.peaks[i:]) + if max[0] >= self.peaks[i]: + self.peaks = np.concatenate((self.peaks[:i], [max[0]], self.peaks[i:])) self.peaks = self.peaks[:-1].copy() self.curWait = 0 - + break #self.mean = self.mean * (self.count - 1) / self.count + hilbertDF.mean() / self.count #self.std = self.std * (self.count - 1) / self.count + hilbertDF.std() / self.count #self.count += 1 else: - self.curWait += len(n_arr[self.activeChan]) / self.fs * 1000 + self.curWait += len(n_arr[chanIn]) / self.fs * 1000 else: - events.append(self.peaks[-1]) + for i in range(len(n_arr[chanIn])): + n_arr[chanIn][i] = self.peaks[-1] return events def handleEvents(eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): @@ -75,19 +79,6 @@ class yPowerThresh(object): def handleSpike(self, electrode, sortedID, n_arr): """handle spikes passed from OE""" -def butter_bandpass(lowcut, highcut, fs, order=5): - nyq = 0.5 * fs - low = lowcut / nyq - high = highcut / nyq - b, a = butter(order, [low, high], btype='band') - return b, a - -def band(data, low, high, fs): - b, a = butter_bandpass(low, high, fs, order=order) - y = lfilter(b, a, data) - return y - - pluginOp = yPowerThresh() include '../plugin.pyx' From 067b2a193b42dcdccf7a0cdff031ee90320ecc54 Mon Sep 17 00:00:00 2001 From: Ethan Blackwood Date: Fri, 31 May 2019 09:45:49 -0500 Subject: [PATCH 3/6] Make our master branch match upstream master (local development can go in 'scratch' branch --- PythonPlugin/OpenEphysLib.cpp | 2 +- PythonPlugin/PythonPlugin.cpp | 985 ++++++----------------- PythonPlugin/PythonPlugin.h | 101 +-- python_modules/plugin.pyx | 26 +- python_modules/pulse_test_delay/setup.py | 6 +- python_modules/spwdouble/setup.py | 6 +- python_modules/spwfinder/setup.py | 5 +- python_modules/spwfinder/spwfinder.pyx | 16 + python_modules/spwrandom/setup.py | 5 +- python_modules/test2/setup.py | 2 + python_modules/test2/test2.pyx | 16 +- 11 files changed, 327 insertions(+), 843 deletions(-) diff --git a/PythonPlugin/OpenEphysLib.cpp b/PythonPlugin/OpenEphysLib.cpp index 63aa327..14dd25c 100644 --- a/PythonPlugin/OpenEphysLib.cpp +++ b/PythonPlugin/OpenEphysLib.cpp @@ -46,7 +46,7 @@ extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) { info->apiVersion = PLUGIN_API_VER; /*API version, defined by the GUI source. Should not be changed to ensure it is always equal to the one used in the latest codebase. The GUI refueses to load plugins with mismatched API versions */ - info->name = "Example library"; //Name of the Library, used only for information + info->name = "Python Plugin"; //Name of the Library, used only for information info->libVersion = 1; //Version of the library, used only for information info->numPlugins = NUM_PLUGINS; } diff --git a/PythonPlugin/PythonPlugin.cpp b/PythonPlugin/PythonPlugin.cpp index c7ac43e..938677d 100644 --- a/PythonPlugin/PythonPlugin.cpp +++ b/PythonPlugin/PythonPlugin.cpp @@ -45,6 +45,7 @@ v #include #include +#include #ifdef DEBUG #define PYTHON_DEBUG @@ -59,88 +60,34 @@ v #endif #endif - -PythonPlugin::PythonPlugin(const String &processorName) - : GenericProcessor(processorName) //, threshold(200.0), state(true) - -{ - - //parameters.add(Parameter("thresh", 0.0, 500.0, 200.0, 0)); - filePath = ""; - plugin = 0; - - // if on windows, PYTHON_HOME_NAME is set by PythonEnv.props (corresponds to CONDA_HOME environment variable) -#ifndef _WIN32 -#define QUOTE(name) #name -#define STR(macro) QUOTE(macro) -#define PYTHON_HOME_NAME STR(PYTHON_HOME) -#endif - - char * old_python_home = getenv("PYTHONHOME"); - if (old_python_home == NULL || strcmp(old_python_home, PYTHON_HOME_NAME) != 0) - { -#ifdef PYTHON_DEBUG - std::cout << "setting PYTHONHOME" << std::endl; -#endif - -#ifdef _WIN32 - _putenv_s("PYTHONHOME", PYTHON_HOME_NAME); -#else - setenv("PYTHONHOME", PYTHON_HOME_NAME, 1); -#endif - } - -#ifdef PYTHON_DEBUG - std::cout << "PYTHONHOME: " << getenv("PYTHONHOME") << std::endl; -#endif - -#ifdef _WIN32 - // set PYTHONPATH to avoid error described here: https://stackoverflow.com/questions/5694706/py-initialize-fails-unable-to-load-the-file-system-codec - _putenv_s("PYTHONPATH", PYTHON_HOME_NAME "\\DLLs;" PYTHON_HOME_NAME "\\Lib;" PYTHON_HOME_NAME "\\Lib\\site-packages"); -#endif - +// debug logs when entering function #ifdef PYTHON_DEBUG #if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); +#define GET_TID pid_t tid = syscall(SYS_gettid) #elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); +#define GET_TID DWORD tid = GetCurrentThreadId() #else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in constructor pthread_threadid_np()=" << tid << std::endl; +#define GET_TID uint64_t tid; pthread_threadid_np(NULL, &tid) #endif -#if PY_MAJOR_VERSION==3 - Py_SetProgramName ((wchar_t *)"PythonPlugin"); -#else - Py_SetProgramName ((char *)"PythonPlugin"); -#endif - Py_Initialize (); - PyEval_InitThreads(); +#define DEBUG_LOG(str) JUCE_BLOCK_WITH_FORCED_SEMICOLON(std::cout << str << std::endl;) - - PyRun_SimpleString("import sys"); - PyRun_SimpleString("sys.setcheckinterval(10000)"); -#ifdef PYTHON_DEBUG - std::cout << Py_GetPrefix() << std::endl; - std::cout << Py_GetVersion() << std::endl; +#define LOG_ENTER(fname) \ + GET_TID; \ + std::cout << "in " << fname << " pthread_threadid_np()=" << tid << std::endl + +#else // not debugging +#define DEBUG_LOG(str) +#define LOG_ENTER(fname) #endif - GUIThreadState = PyEval_SaveThread(); -} -PythonPlugin::~PythonPlugin() +PythonPlugin::PythonPlugin(const String &processorName) + : GenericProcessor(processorName) //, threshold(200.0), state(true) { -#ifdef _WIN32 - //Close libary - PyGILState_Ensure(); - FreeLibrary((HMODULE)plugin); -#else - dlclose(plugin); -#endif + LOG_ENTER("constructor"); } + void PythonPlugin::createEventChannels() { EventChannel* ev = new EventChannel(EventChannel::TTL, 8, 1, CoreServices::getGlobalSampleRate(), this); @@ -160,148 +107,46 @@ void PythonPlugin::createEventChannels() AudioProcessorEditor* PythonPlugin::createEditor() { - -// std::cout << "in PythonEditor::createEditor()" << std::endl; editor = new PythonEditor(this, true); return editor; - } bool PythonPlugin::isReady() { -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in isReady pthread_threadid_np()=" << tid << std::endl; -#endif + LOG_ENTER("isReady"); - bool ret; - PyEval_RestoreThread(GUIThreadState); - if (plugin == 0 ) + if (plugin.getNativeHandle() == nullptr) { CoreServices::sendStatusMessage ("No plugin selected in Python Plugin."); - ret = false; - } - else if (pluginIsReady && !(*pluginIsReady)()) - { - CoreServices::sendStatusMessage ("Python Plugin is not ready"); - ret = false; + return false; } else { - ret = true; + const PythonLock pyLock; + if (pluginIsReady && !(*pluginIsReady)()) + { + CoreServices::sendStatusMessage("Python Plugin is not ready"); + return false; + } + return true; } - GUIThreadState = PyEval_SaveThread(); - return ret; - -} - -void PythonPlugin::setParameter(int parameterIndex, float newValue) -{ - editor->updateParameterButtons(parameterIndex); - - //Parameter& p = parameters.getReference(parameterIndex); - //p.setValue(newValue, 0); - - //threshold = newValue; - - //std::cout << float(p[0]) << std::endl; - editor->updateParameterButtons(parameterIndex); } -void PythonPlugin::resetConnections() -{ -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in resetConnection pthread_threadid_np()=" << tid << std::endl; -#endif - - nextAvailableChannel = 0; - - wasConnected = false; - -#ifdef PYTHON_DEBUG - std::cout << "resetting ThreadState, which was " << processThreadState << std::endl; -#endif - processThreadState = 0; -} - void PythonPlugin::process(AudioSampleBuffer& buffer) { - checkForEvents(true); + LOG_ENTER("process"); + + PythonEvent *pyEvents = (PythonEvent *)calloc(1, sizeof(PythonEvent)); + pyEvents->type = 0; // this marks an empty event -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - // std::cout << "in process pthread_threadid_np()=" << tid << std::endl; -#endif - - - if(!processThreadState) { - - //DEBUG - PyThreadState *nowState; - nowState = PyGILState_GetThisThreadState(); -#ifdef PYTHON_DEBUG - std::cout << "currentState: " << nowState << std::endl; - std::cout << "initialiting ThreadState" << std::endl; -#endif - if(nowState) //UGLY HACK!!! - { - processThreadState = nowState; - } - else - { - processThreadState = PyThreadState_New(GUIThreadState->interp); - } - if(!processThreadState) - std::cout << "ThreadState is Null!" << std::endl; + const PythonLock pyLock; + (*pluginFunction)(*(buffer.getArrayOfWritePointers()), buffer.getNumChannels(), buffer.getNumSamples(), getNumSamples(0), pyEvents); } - PyEval_RestoreThread(processThreadState); - - PythonEvent *pyEvents = (PythonEvent *)calloc(1, sizeof(PythonEvent)); - pyEvents->type = 0; // this marks an empty event -#ifdef PYTHON_DEBUG - // std::cout << "in process, trying to acquire lock" << std::endl; -#endif - - // PyEval_InitThreads(); -// -// std::cout << "in process, threadstate: " << PyGILState_GetThisThreadState() << std::endl; -// PyGILState_STATE gstate; -// gstate = PyGILState_Ensure(); -// std::cout << "in process, lock acquired" << std::endl; - (*pluginFunction)(*(buffer.getArrayOfWritePointers()), buffer.getNumChannels(), buffer.getNumSamples(), getNumSamples(0), pyEvents); -// PyGILState_Release(gstate); -// std::cout << "in process, lock released" << std::endl; - if(wasTriggered) { uint8 ttlData = 0; @@ -313,12 +158,6 @@ void PythonPlugin::process(AudioSampleBuffer& buffer) } if(pyEvents->type != 0) { -#ifdef PYTHON_DEBUG - // std::cout << "Event emitted " << (int)pyEvents->type << std::endl; -#endif - // uint8 ttlData = 1 << module.outputChan; - // TTLEventPtr event = TTLEvent::createTTLEvent(moduleEventChannels[m], getTimestamp(module.inputChan) + i, &ttlData, sizeof(uint8), module.outputChan); - // addEvent(moduleEventChannels[m], event, i); lastChan = (uint16)pyEvents->eventId; uint8 ttlData = 1 << lastChan; @@ -351,101 +190,11 @@ void PythonPlugin::process(AudioSampleBuffer& buffer) wasTriggered = true; } } - - processThreadState = PyEval_SaveThread(); -#ifdef PYTHON_DEBUG - // std::cout << "Thread saved" << std::endl; -#endif } /** START CJB ADDED **/ void PythonPlugin::handleEvent(const EventChannel* eventInfo, const MidiMessage& event, int sampleNum){ - /** For reference - in event info - uint16 getCurrentNodeID() const; - //Gets the index of this channel in the processor which currently owns this copy of the info object - uint16 getCurrentNodeChannelIdx() const; - // Gets the type of the processor which currently owns this copy of the info object - String getCurrentNodeType() const; - // Gets the name of the processor which currently owns this copy of the info object - String getCurrentNodeName() const; - - # struct PythonEvent: - # unsigned char type - # int sampleNum - # unsigned char eventId - # unsigned char eventChannel - # unsigned char numBytes - # unsigned char *eventData - # PythonEvent *nextEvent - **/ - - /** - - enum EventChannelTypes - { - //Numeration kept to maintain compatibility with old code - TTL = 3, - TEXT = 5, - //generic binary types. These will be treated by the majority of record engines as simple binary blobs, - //while having strict typing helps creating stabler plugins - INT8_ARRAY = 10, - UINT8_ARRAY, - INT16_ARRAY, - UINT16_ARRAY, - INT32_ARRAY, - UINT32_ARRAY, - INT64_ARRAY, - UINT64_ARRAY, - FLOAT_ARRAY, - DOUBLE_ARRAY, - //For error checking - INVALID, - //Alias for checking binary types - BINARY_BASE_VALUE = 10 - }; - - **/ - - /** - - #ifdef PYTHON_DEBUG - #if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); - #else - uint64_t tid; - pthread_threadid_np(NULL, &tid); - #endif - std::cout << "in setfloatparam pthread_threadid_np()=" << tid << std::endl; - #endif - PyEval_RestoreThread(GUIThreadState); - (*setFloatParamFunction)(name.getCharPointer().getAddress(), value); - GUIThreadState = PyEval_SaveThread(); - **/ - - /** - - Event packet structure: - EventType - 1byte - SubType - 1byte - Source processor ID - 2bytes - Source Subprocessor index - 2 bytes - Source Event index - 2 bytes - Timestamp - 8 bytes - Event Virtual Channel - 2 bytes - data - variable - - - EventChannel::EventChannelTypes getEventType() const; - const EventChannel* getChannelInfo() const; - uint16 getChannel() const; - const void* getRawDataPointer() const; - - static EventChannel::EventChannelTypes getEventType(const MidiMessage& msg); - - **/ int eventType; int sourceID; int subProcessorIdx; @@ -492,43 +241,18 @@ void PythonPlugin::handleEvent(const EventChannel* eventInfo, const MidiMessage& } } -void PythonPlugin::sendEventPlugin(int eventType, int sourceID, int subProcessorIdx, double timestamp, int sourceIndex){ -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in sendEventPlugin pthread_threadid_np()=" << tid << std::endl; -#endif +void PythonPlugin::sendEventPlugin(int eventType, int sourceID, int subProcessorIdx, double timestamp, int sourceIndex) +{ + LOG_ENTER("sendEventPlugin"); - PyEval_RestoreThread(GUIThreadState); + const PythonLock pyLock; (*eventFunction)(eventType, sourceID, subProcessorIdx,timestamp,sourceIndex); - GUIThreadState = PyEval_SaveThread(); } -void PythonPlugin::handleSpike(const SpikeChannel* spikeInfo, const MidiMessage& event, int samplePosition){ - /** - const SpikeChannel* getChannelInfo() const; - - const float* getDataPointer() const; - - const float* getDataPointer(int channel) const; - - float getThreshold(int chan) const; - - uint16 getSortedID() const; - - - - - - **/ - +void PythonPlugin::handleSpike(const SpikeChannel* spikeInfo, const MidiMessage& event, int samplePosition) +{ + LOG_ENTER("handleSpike"); + SpikeEventPtr newSpike = SpikeEvent::deserializeFromMessage(event, spikeInfo); const float* dataPtr = newSpike->getDataPointer(); float spikeBuf[18]; @@ -537,95 +261,10 @@ void PythonPlugin::handleSpike(const SpikeChannel* spikeInfo, const MidiMessage& } //juce::uint16 int sortedID = int(newSpike->getSortedID()); - int electrode = getSpikeChannelIndex(newSpike); + int electrode = getSpikeChannelIndex(newSpike); -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in handleSpike pthread_threadid_np()=" << tid << std::endl; -#endif - - /* - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - - Perform Python actions here. - result = CallSomeFunction(); - evaluate result or handle exception - Release the thread. No Python API allowed beyond this point. - PyGILState_Release(gstate); - */ - //PyGILState_STATE gstate; - //gstate = PyGILState_Ensure(); - - if(!processThreadState) - { - //* - - //DEBUG - //PyEval_RestoreThread(processThreadState); - PyThreadState *nowState; - nowState = PyGILState_GetThisThreadState(); -#ifdef PYTHON_DEBUG - std::cout << "currentState: " << nowState << std::endl; - std::cout << "initialiting ThreadState" << std::endl; -#endif - if(nowState) //UGLY HACK!!! - { - processThreadState = nowState; - } - else - { - processThreadState = PyThreadState_New(GUIThreadState->interp); - } - if(!processThreadState) - std::cout << "ThreadState is Null!" << std::endl; - } - - PyEval_RestoreThread(processThreadState); - - //PythonEvent *pyEvents = (PythonEvent *)calloc(1, sizeof(PythonEvent)); - - // pyEvents->type = 0; // this marks an empty event -#ifdef PYTHON_DEBUG - // std::cout << "in process, trying to acquire lock" << std::endl; -#endif - - // PyEval_InitThreads(); - // - // std::cout << "in process, threadstate: " << PyGILState_GetThisThreadState() << std::endl; - // PyGILState_STATE gstate; - // gstate = PyGILState_Ensure(); - // std::cout << "in process, lock acquired" << std::endl; + const PythonLock pyLock; (*spikeFunction)(electrode, sortedID, spikeBuf); - processThreadState = PyEval_SaveThread(); - - //PyGILState_Release(gstate); - - //processThreadState = PyEval_SaveThread(); - /** - #ifdef PYTHON_DEBUG - #if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); - #else - uint64_t tid; - pthread_threadid_np(NULL, &tid); - #endif - std::cout << "in handleSpike pthread_threadid_np()=" << tid << std::endl; - #endif - - PyEval_RestoreThread(GUIThreadState); - (*spikeFunction)(sortedID, spikeBuf); - GUIThreadState = PyEval_SaveThread(); - **/ } /** END CJB ADDED **/ @@ -639,402 +278,236 @@ void PythonPlugin::handleSpike(const SpikeChannel* spikeInfo, const MidiMessage& void set FloatParameter(char *name, float value) set float parameter */ -#if defined(_WIN32) -std::string GetLastErrorAsString() + +String lastError() { + String message; +#ifdef _WIN32 /*Get the error message, if any.*/ DWORD errorMessageID = ::GetLastError(); - if(errorMessageID == 0) - return std::string(); //No error message has been recorded - - LPSTR messageBuffer = nullptr; - size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + if (errorMessageID != 0) // Error message has been recorded + { + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); - std::string message(messageBuffer, size); + message = String(messageBuffer, size); - //Free the buffer. - LocalFree(messageBuffer); + //Free the buffer. + LocalFree(messageBuffer); + } +#else + message = String(dlerror()); +#endif return message; } -#endif void PythonPlugin::setFile(String fullpath) { -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#elif defined(_WIN32) - DWORD tid = GetCurrentThreadId(); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in setFile pthread_threadid_np()=" << tid << std::endl; -#endif + LOG_ENTER("setFile"); -#ifdef _WIN32 - //Load plugin filePath = fullpath; - std::string path = filePath.toStdString(); - plugin = LoadLibraryA(path.c_str()); -#else - filePath = fullpath; - - const char* path = filePath.getCharPointer(); - plugin = dlopen(path, RTLD_LAZY); -#endif - if (!plugin) - { - std::cout << "Can't open plugin " - << '"' << path << "\"" -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - return; - - } + if (!plugin.open(filePath)) + { + std::cout << "Can't open plugin " + << '"' << filePath << '"' << std::endl + << lastError() << std::endl; + return; + } - -#ifdef _WIN32 - String initPlugin = filePath.fromLastOccurrenceOf(String("\\"), false, true); // windows -#else - String initPlugin = filePath.fromLastOccurrenceOf(String("/"), false, true); // linux/mac -#endif - initPlugin = initPlugin.upToFirstOccurrenceOf(String("."), false, true); + String pluginName = File(filePath).getFileName().upToFirstOccurrenceOf(".", false, true); #if PY_MAJOR_VERSION>=3 String initPluginName = String("PyInit_"); #else String initPluginName = String("init"); #endif - initPluginName.append(initPlugin, 200); + initPluginName.append(pluginName, 200); std::cout << "init function is: " << initPluginName << std::endl; - void *initializer; - -#ifdef _WIN32 - initializer = GetProcAddress((HMODULE)plugin, initPluginName.getCharPointer()); -#else - initializer = dlsym(plugin, initPluginName.getCharPointer()); -#endif - -#ifdef PYTHON_DEBUG - std::cout << "initializer: " << initializer << std::endl; -#endif + void *initializer = plugin.getFunction(initPluginName); + DEBUG_LOG("initializer: " << initializer); if (!initializer) { std::cout << "Can't find init function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); return; } + initfunc_t initF = (initfunc_t)initializer; - initfunc_t initF = (initfunc_t) initializer; - void *cfunc; -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "pluginisready"); -#else - cfunc = dlsym(plugin, "pluginisready"); -#endif + void *cfunc = plugin.getFunction("pluginisready"); if (!cfunc) { std::cout << "Can't find ready function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); return; } pluginIsReady = (isreadyfunc_t)cfunc; -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "pluginStartup"); -#else - cfunc = dlsym(plugin, "pluginStartup"); -#endif + cfunc = plugin.getFunction("pluginStartup"); if (!cfunc) { - std::cout << "Can't find startup function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; - return; + std::cout << "Can't find startup function in plugin " + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); + return; } pluginStartupFunction = (startupfunc_t)cfunc; - std::cout<<"loaded pluginStartup \n \n \n \n \n "; - -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "getParamNum"); -#else - cfunc = dlsym(plugin, "getParamNum"); -#endif + std::cout << "loaded pluginStartup \n \n \n \n \n "; + cfunc = plugin.getFunction("getParamNum"); if (!cfunc) { std::cout << "Can't find getParamNum function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); return; } getParamNumFunction = (getparamnumfunc_t)cfunc; - -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "getParamConfig"); -#else - cfunc = dlsym(plugin, "getParamConfig"); -#endif + cfunc = plugin.getFunction("getParamConfig"); if (!cfunc) { - std::cout << "Can't find getParamNum function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; - return; - + std::cout << "Can't find getParamConfig function in plugin " + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); + return; } getParamConfigFunction = (getparamconfigfunc_t)cfunc; - -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "pluginFunction"); -#else - cfunc = dlsym(plugin, "pluginFunction"); -#endif - // std::cout << "plugin: " << cfunc << std::endl; + cfunc = plugin.getFunction("pluginFunction"); if (!cfunc) { - std::cout << "Can't find plugin function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; - return; + std::cout << "Can't find plugin function in plugin " + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); + return; } pluginFunction = (pluginfunc_t)cfunc; - + // CJB added start -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "eventFunction"); -#else - cfunc = dlsym(plugin,"eventFunction"); -#endif - // std::cout << "plugin: " << cfunc << std::endl; + cfunc = plugin.getFunction("eventFunction"); if (!cfunc) { - std::cout << "Can't find plugin function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + std::cout << "Can't find event function in plugin " + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); return; - } eventFunction = (eventfunc_t)cfunc; - -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "spikeFunction"); -#else - cfunc = dlsym(plugin,"spikeFunction"); -#endif - // std::cout << "plugin: " << cfunc << std::endl; + + cfunc = plugin.getFunction("spikeFunction"); if (!cfunc) { - std::cout << "Can't find plugin function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + std::cout << "Can't find spike function in plugin " + << '"' << pluginName << '"' << std::endl + << lastError() << std::endl; + plugin.close(); return; } spikeFunction = (spikefunc_t)cfunc; - + // CJB added end -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "setIntParam"); -#else - cfunc = dlsym(plugin, "setIntParam"); -#endif - // std::cout << "plugin: " << cfunc << std::endl; + cfunc = plugin.getFunction("setIntParam"); if (!cfunc) { std::cout << "Can't find setIntParam function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << "\"" << std::endl + << lastError() << std::endl; + plugin.close(); return; } setIntParamFunction = (setintparamfunc_t)cfunc; - -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "setFloatParam"); -#else - cfunc = dlsym(plugin, "setFloatParam"); -#endif - // std::cout << "plugin: " << cfunc << std::endl; + + cfunc = plugin.getFunction("setFloatParam"); if (!cfunc) { std::cout << "Can't find setFloatParam function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << "\"" << std::endl + << lastError() << std::endl; + plugin.close(); return; } - setFloatParamFunction = (setfloatparamfunc_t)cfunc; -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "getIntParam"); -#else - cfunc = dlsym(plugin, "getIntParam"); -#endif - - // std::cout << "plugin: " << cfunc << std::endl; + cfunc = plugin.getFunction("getIntParam"); if (!cfunc) { std::cout << "Can't find getIntParam function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << "\"" << std::endl + << lastError() << std::endl; + plugin.close(); return; } getIntParamFunction = (getintparamfunc_t)cfunc; - -#ifdef _WIN32 - cfunc = GetProcAddress((HMODULE)plugin, "getFloatParam"); -#else - cfunc = dlsym(plugin, "getFloatParam"); -#endif - // std::cout << "plugin: " << cfunc << std::endl; + + cfunc = plugin.getFunction("getFloatParam"); if (!cfunc) { std::cout << "Can't find getFloatParam function in plugin " - << '"' << path << "\"" << std::endl -#ifdef _WIN32 - << GetLastErrorAsString() -#else - << dlerror() -#endif - << std::endl; - plugin = 0; + << '"' << pluginName << "\"" << std::endl + << lastError() << std::endl; + plugin.close(); return; } - getFloatParamFunction = (getfloatparamfunc_t)cfunc; + // now the API should be fully loaded -// now the API should be fully loaded - - PyEval_RestoreThread(GUIThreadState); + const PythonLock pyLock; // initialize the plugin -#ifdef PYTHON_DEBUG - std::cout << "before initplugin" << std::endl; // DEBUG -#endif - + + DEBUG_LOG("before initplugin"); + (*initF)(); -#ifdef PYTHON_DEBUG - std::cout << "after initplugin" << std::endl; // DEBUG -#endif + DEBUG_LOG("after initplugin"); + (*pluginStartupFunction)(dataSampleRate); // load the parameter configuration numPythonParams = (*getParamNumFunction)(); -#ifdef PYTHON_DEBUG - std::cout << "the plugin wants " << numPythonParams - << " parameters" << std::endl; -#endif + DEBUG_LOG("the plugin wants " << numPythonParams << " parameters"); + params = (ParamConfig *)calloc(numPythonParams, sizeof(ParamConfig)); paramsControl = (Component **)calloc(numPythonParams, sizeof(Component *)); (*getParamConfigFunction)(params); -#ifdef PYTHON_DEBUG - std::cout << "release paramconfig" << std::endl; -#endif + DEBUG_LOG("release paramconfig"); + auto ed = static_cast(getEditor()); for(int i = 0; i < numPythonParams; i++) { -#ifdef PYTHON_DEBUG - std::cout << "param " << i << " is a " << params[i].type << std::endl; - std::cout << "it is named: " << params[i].name << std::endl << std::endl; -#endif + DEBUG_LOG("param " << i << " is a " << params[i].type); + DEBUG_LOG("it is named: " << params[i].name << std::endl); + switch (params[i].type) { case TOGGLE: - paramsControl[i] = dynamic_cast(getEditor())->addToggleButton(String(params[i].name), params[i].isEnabled); + paramsControl[i] = ed->addToggleButton(String(params[i].name), params[i].isEnabled); break; case INT_SET: - paramsControl[i] = dynamic_cast(getEditor())->addComboBox(String(params[i].name), params[i].nEntries, params[i].entries); + paramsControl[i] = ed->addComboBox(String(params[i].name), params[i].nEntries, params[i].entries); break; case FLOAT_RANGE: - paramsControl[i] = dynamic_cast(getEditor())->addSlider(String(params[i].name), params[i].rangeMin, params[i].rangeMax, params[i].startValue); + paramsControl[i] = ed->addSlider(String(params[i].name), params[i].rangeMin, params[i].rangeMax, params[i].startValue); break; default: break; } } - GUIThreadState = PyEval_SaveThread(); } @@ -1057,120 +530,118 @@ void PythonPlugin::updateSettings() void PythonPlugin::setIntPythonParameter(String name, int value) { + LOG_ENTER("setIntPythonParameter"); -#ifdef _WIN32 -#else -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in setintparam pthread_threadid_np()=" << tid << std::endl; -#endif -#endif - - PyEval_RestoreThread(GUIThreadState); + const PythonLock pyLock; (*setIntParamFunction)(name.getCharPointer().getAddress(), value); - GUIThreadState = PyEval_SaveThread(); } void PythonPlugin::setFloatPythonParameter(String name, float value) { + LOG_ENTER("setFloatPythonParameter"); -#ifdef _WIN32 -#else -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in setfloatparam pthread_threadid_np()=" << tid << std::endl; -#endif -#endif - PyEval_RestoreThread(GUIThreadState); + const PythonLock pyLock; (*setFloatParamFunction)(name.getCharPointer().getAddress(), value); - GUIThreadState = PyEval_SaveThread(); } int PythonPlugin::getIntPythonParameter(String name) { -#ifdef _WIN32 -#else -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in getintparam pthread_threadid_np()=" << tid << std::endl; -#endif -#endif + LOG_ENTER("getIntPythonParameter"); int value; - PyEval_RestoreThread(GUIThreadState); + const PythonLock pyLock; value = (*getIntParamFunction)(name.getCharPointer().getAddress()); - GUIThreadState = PyEval_SaveThread(); return value; } float PythonPlugin::getFloatPythonParameter(String name) { + LOG_ENTER("getFloatPythonParameter"); -#ifdef _WIN32 -#else -#ifdef PYTHON_DEBUG -#if defined(__linux__) - pid_t tid; - tid = syscall(SYS_gettid); -#else - uint64_t tid; - pthread_threadid_np(NULL, &tid); -#endif - std::cout << "in getfloatparam pthread_threadid_np()=" << tid << std::endl; -#endif -#endif - - PyEval_RestoreThread(GUIThreadState); float value; + const PythonLock pyLock; value = (*getFloatParamFunction)(name.getCharPointer().getAddress()); - GUIThreadState = PyEval_SaveThread(); return value; } -//saving settings +// PythonLock -void PythonPlugin::saveCustomParametersToXml (XmlElement* parentElement) -{ - XmlElement* mainNode = parentElement->createNewChildElement ("PYTHONPLUGIN"); - mainNode->setAttribute ("filepath", filePath); -} - -void PythonPlugin::loadCustomParametersFromXml() +PythonPlugin::PythonLock::PythonLock() + : pgss(PyGILState_Ensure()) { - if (parametersAsXml) + // if current state is not the mainState or saved threadState, need to save it + PyThreadState* currState = PyThreadState_Get(); + if (currState != mainState && currState != threadState) { - //PythonEditor* ed = (PythonEditor*) getEditor(); - - forEachXmlChildElement(*parametersAsXml, mainNode) + // abusing the API a little - call ...Ensure again to increment the counter + // and prevent it from being deleted automatically when the lock is released + PyGILState_Ensure(); + + // delete the old thread state, if any + if (threadState) { - if (mainNode->hasTagName("PYTHONPLUGIN")) - { - filePath = mainNode->getStringAttribute("filepath"); - std::cout<<"set file path to: " << filePath << "\n"; - - } + PyThreadState_Clear(threadState); + PyThreadState_Delete(threadState); } + + threadState = currState; } } +PythonPlugin::PythonLock::~PythonLock() +{ + PyGILState_Release(pgss); +} + +static PyThreadState* startInterpreter() +{ + // if on windows, PYTHON_HOME_NAME is set by PythonEnv.props (corresponds to CONDA_HOME environment variable) +#ifndef _WIN32 +#define QUOTE(name) #name +#define STR(macro) QUOTE(macro) +#define PYTHON_HOME_NAME STR(PYTHON_HOME) +#endif + + char * old_python_home = getenv("PYTHONHOME"); + if (old_python_home == NULL || strcmp(old_python_home, PYTHON_HOME_NAME) != 0) + { +#ifdef PYTHON_DEBUG + std::cout << "setting PYTHONHOME" << std::endl; +#endif + +#ifdef _WIN32 + _putenv_s("PYTHONHOME", PYTHON_HOME_NAME); +#else + setenv("PYTHONHOME", PYTHON_HOME_NAME, 1); +#endif + } + +#ifdef PYTHON_DEBUG + std::cout << "PYTHONHOME: " << getenv("PYTHONHOME") << std::endl; +#endif + +#ifdef _WIN32 + // set PYTHONPATH to avoid error described here: https://stackoverflow.com/questions/5694706/py-initialize-fails-unable-to-load-the-file-system-codec + _putenv_s("PYTHONPATH", PYTHON_HOME_NAME "\\DLLs;" PYTHON_HOME_NAME "\\Lib;" PYTHON_HOME_NAME "\\Lib\\site-packages"); +#endif + +#if PY_MAJOR_VERSION==3 + Py_SetProgramName((wchar_t *)"PythonPlugin"); +#else + Py_SetProgramName((char *)"PythonPlugin"); +#endif + Py_Initialize(); + PyEval_InitThreads(); + PyRun_SimpleString("import sys"); + PyRun_SimpleString("sys.setcheckinterval(10000)"); +#ifdef PYTHON_DEBUG + std::cout << Py_GetPrefix() << std::endl; + std::cout << Py_GetVersion() << std::endl; +#endif + return PyEval_SaveThread(); +} +const PyThreadState* PythonPlugin::PythonLock::mainState(startInterpreter()); +PyThreadState* PythonPlugin::PythonLock::threadState(nullptr); diff --git a/PythonPlugin/PythonPlugin.h b/PythonPlugin/PythonPlugin.h index 4adc719..4b6167b 100644 --- a/PythonPlugin/PythonPlugin.h +++ b/PythonPlugin/PythonPlugin.h @@ -50,44 +50,27 @@ #include #endif - -#if PY_MAJOR_VERSION>=3 -#define DL_IMPORT PyAPI_FUNC -#endif - -#ifndef __PYX_EXTERN_C - #ifdef __cplusplus - #define __PYX_EXTERN_C extern "C" - #else - #define __PYX_EXTERN_C extern - #endif -#endif - - - #include "PythonParamConfig.h" #include "PythonEvent.h" #include "PythonEditor.h" -//extern "C" typedef void (*initfunc_t)(void); - //#if PY_MAJOR_VERSION>=3 typedef PyObject * (*initfunc_t)(void); //#else //typedef PyMODINIT_FUNC (*initfunc_t)(void); //#endif -typedef DL_IMPORT(void) (*startupfunc_t)(float); // passes the sampling rate -typedef DL_IMPORT(void) (*eventfunc_t)(int, int, int, double, int);// CJB added -typedef DL_IMPORT(void) (*spikefunc_t)(int, int, float[18]);// CJB added -typedef DL_IMPORT(void) (*pluginfunc_t)(float *, int, int, int, PythonEvent *); -typedef DL_IMPORT(int) (*isreadyfunc_t)(void); -typedef DL_IMPORT(int) (*getparamnumfunc_t)(void); -typedef DL_IMPORT(void) (*getparamconfigfunc_t)(struct ParamConfig*); -typedef DL_IMPORT(void) (*setintparamfunc_t)(char*, int); -typedef DL_IMPORT(void) (*setfloatparamfunc_t)(char*, float); -typedef DL_IMPORT(int) (*getintparamfunc_t)(char*); -typedef DL_IMPORT(float) (*getfloatparamfunc_t)(char*); +typedef void (*startupfunc_t)(float); // passes the sampling rate +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 (*setintparamfunc_t)(char*, int); +typedef void (*setfloatparamfunc_t)(char*, float); +typedef int (*getintparamfunc_t)(char*); +typedef float (*getfloatparamfunc_t)(char*); #ifdef _WIN32 @@ -101,15 +84,12 @@ typedef DL_IMPORT(float) (*getfloatparamfunc_t)(char*); //============================================================================= /* */ -class PythonPlugin : public GenericProcessor +class PythonPlugin : public GenericProcessor { public: /** The class constructor, used to initialize any members. */ PythonPlugin(const String &processorName = "Python Plugin"); - /** The class destructor, used to deallocate memory */ - ~PythonPlugin(); - /** Determines whether the processor is treated as a source. */ virtual bool isSource() { @@ -135,14 +115,9 @@ class PythonPlugin : public GenericProcessor size of the buffer). */ virtual void process(AudioSampleBuffer& buffer /* , MidiBuffer& events */); - + void handleEvent (const EventChannel* eventInfo, const MidiMessage& event, int sampleNum); // CJB added void handleSpike(const SpikeChannel* channelInfo, const MidiMessage& event, int samplePosition); //CJB added - - /** Any variables used by the "process" function _must_ be modified only through - this method while data acquisition is active. If they are modified in any - other way, the application will crash. */ - void setParameter(int parameterIndex, float newValue); AudioProcessorEditor* createEditor(); @@ -176,21 +151,37 @@ class PythonPlugin : public GenericProcessor int getIntPythonParameter(String name); float getFloatPythonParameter(String name); - - void resetConnections(); - - void saveCustomParametersToXml (XmlElement* parentElement) override; - void loadCustomParametersFromXml() override; + private: void sendEventPlugin(int eventType, int sourceID, int subProcessorIdx, double timestamp, int sourceIndex); //CJB added + + /* Added by EBB + Why do it this way: + * Using a class allows object destruction to control releasing the GIL (RAII) + * Private inner class so that random other objects with other threads can't use it; + it's just for the main GUI and process threads + * Static state pointers b/c all instances of PythonPlugin use the same threads and therefore + can use the same Python states + * State pointers encapuslated in here so that they can only be manipulated by creating + and destroying PythonLocks (abstracting away confusing Python C API) + */ + class PythonLock + { + public: + PythonLock(); + ~PythonLock(); + + private: + const PyGILState_STATE pgss; + + static const PyThreadState* mainState; + static PyThreadState* threadState; + + JUCE_DECLARE_NON_COPYABLE(PythonLock); + }; + String filePath; - void *plugin; - // private members and methods go here - // - // e.g.: - // - // float threshold; - // bool state; + DynamicLibrary plugin; int numPythonParams = 0; ParamConfig *params; Component **paramsControl; @@ -208,17 +199,11 @@ class PythonPlugin : public GenericProcessor getfloatparamfunc_t getFloatParamFunction; eventfunc_t eventFunction; spikefunc_t spikeFunction; - PyThreadState *GUIThreadState = 0; - PyThreadState *processThreadState = 0; const EventChannel* ttlChannel{ nullptr }; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PythonPlugin); bool wasTriggered = 0; uint16 lastChan = 0; - //Windows Port Variables -#ifdef _WIN32 - HINSTANCE old_python_home; - PyThreadState *mainstate = NULL; -#endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PythonPlugin); }; diff --git a/python_modules/plugin.pyx b/python_modules/plugin.pyx index 31e6f7a..3ab55c1 100644 --- a/python_modules/plugin.pyx +++ b/python_modules/plugin.pyx @@ -38,17 +38,19 @@ cdef extern from "PythonEvent.h": # noinspection PyPep8Naming -cdef public void pluginStartup(float sampling_rate) with gil: +cdef public void pluginStartup(float sampling_rate): + print("pre anything") global isDebug + print("after is debug") global pluginOp pluginOp.startup(sampling_rate) # noinspection PyPep8Naming -cdef public int getParamNum() with gil: +cdef public int getParamNum(): return len(pluginOp.param_config()) # noinspection PyPep8Naming -cdef public void getParamConfig(ParamConfig *params) with gil: +cdef public void getParamConfig(ParamConfig *params): cdef int *ent cdef char * par_name cdef size_t par_len @@ -90,7 +92,7 @@ cdef public void getParamConfig(ParamConfig *params) with gil: # noinspection PyPep8Naming -cdef public void pluginFunction(float *data_buffer, int nChans, int nSamples, int nRealSamples, PythonEvent *events) with gil: +cdef public void pluginFunction(float *data_buffer, int nChans, int nSamples, int nRealSamples, PythonEvent *events): global sr n_arr = np.asarray( data_buffer) #pluginOp.set_events(events) @@ -127,15 +129,15 @@ cdef public void pluginFunction(float *data_buffer, int nChans, int nSamples, in last_e_c.nextEvent = NULL # noinspection PyPep8Naming -cdef public void eventFunction(int eventType, int sourceID, int subProcessorIdx, double timestamp, int sourceIndex) with gil: +cdef public void eventFunction(int eventType, int sourceID, int subProcessorIdx, double timestamp, int sourceIndex): pluginOp.handleEvents(eventType,sourceID,subProcessorIdx,timestamp,sourceIndex) # noinspection PyPep8Naming -cdef public void spikeFunction(int electrode, int sortedID, float[18] spikeSample) with gil: +cdef public void spikeFunction(int electrode, int sortedID, float[18] spikeSample): n_arr = np.asarray( spikeSample) pluginOp.handleSpike(electrode,sortedID,n_arr) -cdef void add_event(PythonEvent *e_c, object e_py) with gil: +cdef void add_event(PythonEvent *e_c, object e_py): e_c.type = e_py['type'] e_c.sampleNum = e_py['sampleNum'] if 'eventId' in e_py: @@ -149,29 +151,29 @@ cdef void add_event(PythonEvent *e_c, object e_py) with gil: # TODO to be tested if this works with a numpy input -cdef public int pluginisready() with gil: +cdef public int pluginisready(): return pluginOp.is_ready() # noinspection PyPep8Naming -cdef public void setIntParam(char *name, int value) with gil: +cdef public void setIntParam(char *name, int value): if isDebug: print("In Python: ", name, ": ", value) setattr(pluginOp, name.decode('utf-8'), value) # noinspection PyPep8Naming -cdef public void setFloatParam(char *name, float value) with gil: +cdef public void setFloatParam(char *name, float value): # print ("In Python: ", name, ": ", value) setattr(pluginOp, name.decode('utf-8'), value) # noinspection PyPep8Naming -cdef public int getIntParam(char *name) with gil: +cdef public int getIntParam(char *name): if isDebug: print("In Python getIntParam: ", name) value = getattr(pluginOp, name.decode('utf-8')) return value # noinspection PyPep8Naming -cdef public float getFloatParam(char *name) with gil: +cdef public float getFloatParam(char *name): # print( "In Python: ", name, ": ", value) value = getattr(pluginOp, name.decode('utf-8')) return value diff --git a/python_modules/pulse_test_delay/setup.py b/python_modules/pulse_test_delay/setup.py index 31a170b..5c979a4 100644 --- a/python_modules/pulse_test_delay/setup.py +++ b/python_modules/pulse_test_delay/setup.py @@ -1,6 +1,10 @@ from distutils.core import setup, Extension from Cython.Build import cythonize import numpy +import runpy + +cfg = runpy.run_path('../.config.py') + setup( name="pulse_test_delay", @@ -8,5 +12,5 @@ export_symbols=['pluginStartup', 'pluginisready', 'getParamNum', 'getParamConfig', 'pluginFunction', 'eventFunction', 'spikeFunction', 'setIntParam', 'setFloatParam', 'getIntParam', 'getFloatParam'])), - include_dirs=[numpy.get_include()] + include_dirs=[numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] ) diff --git a/python_modules/spwdouble/setup.py b/python_modules/spwdouble/setup.py index efc5b5f..eb6b2e0 100644 --- a/python_modules/spwdouble/setup.py +++ b/python_modules/spwdouble/setup.py @@ -1,9 +1,13 @@ from distutils.core import setup, Extension from Cython.Build import cythonize import numpy +import runpy + +cfg = runpy.run_path('../.config.py') + 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()] + include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] ) diff --git a/python_modules/spwfinder/setup.py b/python_modules/spwfinder/setup.py index 35125d3..de02dc8 100644 --- a/python_modules/spwfinder/setup.py +++ b/python_modules/spwfinder/setup.py @@ -1,9 +1,12 @@ from distutils.core import setup, Extension from Cython.Build import cythonize import numpy +import runpy + +cfg = runpy.run_path('../.config.py') 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()] + include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] ) diff --git a/python_modules/spwfinder/spwfinder.pyx b/python_modules/spwfinder/spwfinder.pyx index 434bcd1..d6eb1f0 100644 --- a/python_modules/spwfinder/spwfinder.pyx +++ b/python_modules/spwfinder/spwfinder.pyx @@ -117,6 +117,22 @@ class spwfinder(object): ("float_range", "swing_thresh", self.swing_thresh_min, self.swing_thresh_max, self.swing_thresh_start), ("float_range", "averaging_time", self.averaging_time_min, self.averaging_time_max, self.averaging_time_start)) + def spw_condition(self, n_arr): + return (self.spw_power > self.threshold) and self.swing_state == self.NOT_SWINGING + + def stimulate(self): + try: + self.arduino.write(b'1') + except AttributeError: + print("Can't send pulse") + self.pulseNo += 1 + print("generating pulse ", self.pulseNo) + + def new_event(self, events, code, channel=0, timestamp=None): + if not timestamp: + timestamp = self.n_samples + events.append({'type': 3, 'sampleNum': timestamp, 'eventId': code, 'eventChannel': channel}) + 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 6ca2e0a..b4787ce 100644 --- a/python_modules/spwrandom/setup.py +++ b/python_modules/spwrandom/setup.py @@ -1,9 +1,12 @@ from distutils.core import setup, Extension from Cython.Build import cythonize import numpy +import runpy + +cfg = runpy.run_path('../.config.py') 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()] + include_dirs = [numpy.get_include(), cfg['PYTHON_PLUGIN_SRC_DIR']] ) diff --git a/python_modules/test2/setup.py b/python_modules/test2/setup.py index 1b9bad4..20c9946 100644 --- a/python_modules/test2/setup.py +++ b/python_modules/test2/setup.py @@ -22,3 +22,5 @@ 'getFloatParam' ])) ) + + diff --git a/python_modules/test2/test2.pyx b/python_modules/test2/test2.pyx index e763fe8..2fb7e15 100644 --- a/python_modules/test2/test2.pyx +++ b/python_modules/test2/test2.pyx @@ -8,15 +8,10 @@ isDebug = False class test2(object): def __init__(self): - print('hello from init\n\n') """initialize object data""" self.Enabled = 1 - self.threshMin = -100 - self.threshMax = 100 def startup(self, sr): """to be run upon startup""" - #self.samplingRate = sr - print('start') def plugin_name(self): """tells OE the name of the program""" return "test2" @@ -25,13 +20,9 @@ class test2(object): return self.Enabled def param_config(self): """return button, sliders, etc to be present in the editor OE side""" - thresholdMin = ("float_range", "threshold min", self.threshMin, self.threshMax, 50) - thresholdMax = ("float_range", "threshold max", self.threshMin, self.threshMax, -50) - intMin = ("int_set", "int setting", [0,1,2,3,4]) - enable = ("toggle", "enabled", True) - return [enable, intMin] + return [] def bufferfunction(self, n_arr): - """Access to voltage data buffer. Returns events""" + """Access to voltage data buffer. Returns events""" events = [] return events def handleEvents(self, eventType,sourceID,subProcessorIdx,timestamp,sourceIndex): @@ -43,3 +34,6 @@ class test2(object): pluginOp = test2() include '../plugin.pyx' + + + From 7fdee158107ab2249f1ddf1cb9fbf5864066d006 Mon Sep 17 00:00:00 2001 From: Ethan Blackwood Date: Thu, 6 Jun 2019 16:20:41 -0500 Subject: [PATCH 4/6] Add update_settings and channel_changed python functions --- PythonPlugin/PythonEditor.cpp | 5 + PythonPlugin/PythonEditor.h | 2 + PythonPlugin/PythonPlugin.cpp | 98 ++++++++++++++++--- PythonPlugin/PythonPlugin.h | 40 +++++--- python_modules/plugin.pyx | 19 +++- .../pulse_test_delay/pulse_test_delay.pyx | 27 ++++- python_modules/pulse_test_delay/setup.py | 33 +++++-- python_modules/spwdouble/setup.py | 27 ++++- python_modules/spwdouble/spwdouble.pyx | 47 ++++++--- python_modules/spwfinder/setup.py | 25 ++++- python_modules/spwfinder/spwfinder.pyx | 51 +++++++--- python_modules/spwrandom/setup.py | 25 ++++- python_modules/spwrandom/spwrandom.pyx | 26 ++++- python_modules/template/setup.py | 35 ++++--- python_modules/template/template.pyx | 33 ++++++- python_modules/test/setup.py | 35 ++++--- python_modules/test/test.pyx | 40 +++++++- python_modules/test2/setup.py | 35 ++++--- python_modules/test2/test2.pyx | 30 +++++- 19 files changed, 506 insertions(+), 127 deletions(-) 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/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""" From d4571a3ee4a9c765949393dae3a429900f1d4a11 Mon Sep 17 00:00:00 2001 From: Clayton Barnes Date: Tue, 11 Jun 2019 12:58:30 -0400 Subject: [PATCH 5/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2b912d07a707121a05f3aaab9d1a95f5bdc5fd17 Mon Sep 17 00:00:00 2001 From: Francesco Battaglia Date: Wed, 22 Dec 2021 11:37:33 +0100 Subject: [PATCH 6/6] implementing number of channels in PythonSource, first attempt --- PythonPlugin/PythonSource.cpp | 24 +++++++++++++++++------- PythonPlugin/PythonSource.h | 21 ++++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) 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);