diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6bfd97a..ecd66ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,7 +81,8 @@ endif() if(WITH_CEREBUS) # Add network specific source files list(APPEND SOURCE_FILES - cerebustraceprovider.cpp) + cerebustraceprovider.cpp + cerebusclustersprovider.cpp) endif() # Combine it all diff --git a/src/cerebusclustersprovider.cpp b/src/cerebusclustersprovider.cpp new file mode 100644 index 0000000..8d35d57 --- /dev/null +++ b/src/cerebusclustersprovider.cpp @@ -0,0 +1,56 @@ +/*************************************************************************** + cerebusclustersprovider.cpp - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "cerebusclustersprovider.h" + +#include + +// 0 = unclassified, 1 - cbMAXUNITS = actual units, 254 = artifact (unit + noise), 255 = background (noise) +const int CerebusClustersProvider::CLUSTER_COUNT = cbMAXUNITS + 3; + +CerebusClustersProvider::CerebusClustersProvider(CerebusTracesProvider* source, unsigned int channel, int samplingRate) : + ClustersProvider(QString("cerebus.%1.clu").arg(channel + 1), samplingRate, cbSdk_TICKS_PER_SECOND, 0), + mDataProvider(source), + mChannel(channel) { + + // Name referes to the group, which can not be zero, because zero is trash group. + this->name = QString::number(mChannel + 1); + this->clusterIds << 0 << 1 << 2 << 3 << 4 << 5 << 254 << 255; +} + +CerebusClustersProvider::~CerebusClustersProvider() { + // Nothing to do here +} + +int CerebusClustersProvider::loadData() { + if (mDataProvider->isInitialized()) + return OPEN_ERROR; + else + return OK; +} + +void CerebusClustersProvider::requestData(long start, long end, QObject* initiator, long /*startTimeInRecordingUnits*/) { + Array* data = mDataProvider->getClusterData(mChannel, start, end); + emit dataReady(*data, initiator, this->name); + delete data; +} + +void CerebusClustersProvider::requestNextClusterData(long startTime, long timeFrame, const QList &selectedIds, QObject* initiator, long startTimeInRecordingUnits) { + qDebug() << "requestNextClusterData(...) not supported yet."; +} + +void CerebusClustersProvider::requestPreviousClusterData(long startTime, long timeFrame, QList selectedIds, QObject* initiator, long startTimeInRecordingUnits) { + qDebug() << "requestPreviousClusterData(...) not supported yet."; +} diff --git a/src/cerebusclustersprovider.h b/src/cerebusclustersprovider.h new file mode 100644 index 0000000..894e3f6 --- /dev/null +++ b/src/cerebusclustersprovider.h @@ -0,0 +1,80 @@ +/*************************************************************************** + cerebusclustersprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef _NEVCLUSTERSPROVIDER_H_ +#define _NEVCLUSTERSPROVIDER_H_ + +#include "clustersprovider.h" +#include "cerebustraceprovider.h" + +class CerebusClustersProvider : public ClustersProvider { + Q_OBJECT + +public: + static const int CLUSTER_COUNT; + + CerebusClustersProvider(CerebusTracesProvider* source, unsigned int channel, int samplingRate); + ~CerebusClustersProvider(); + + /** Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + * + * Since data is supplied by CerebusTraceProvider, this is + * just a wrapper around isInitialized() + */ + virtual int loadData(); + + + /**Triggers the retrieve of the cluster information included in the time interval given by @p startTime and @p endTime. + * @param startTime begining of the time interval from which to retrieve the data in miliseconds. + * @param endTime end of the time interval from which to retrieve the data. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. + */ + virtual void requestData(long startTime, long endTime, QObject* initiator, long startTimeInRecordingUnits); + + + /**Looks up for the first of the clusters included in the list @p selectedIds existing after the time @p startTime. + * All the clusters included in the time interval given by @p timeFrame are retrieved. The time interval start time is + * computed in order to have the first cluster found located at @p clusterPosition percentage of the time interval. + * @param startTime starting time, in miliseconds, for the look up. + * @param timeFrame time interval for which to retrieve the data. + * @param selectedIds list of cluster ids to look up for. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits starting time, in recording units, for the look up. + */ + virtual void requestNextClusterData(long startTime, long timeFrame, const QList &selectedIds, QObject* initiator, long startTimeInRecordingUnits); + + + /**Looks up for the first of the clusters included in the list @p selectedIds existing before the time @p endTime. + * All the clusters included in the time interval given by @p timeFrame are retrieved. The time interval start time is + * computed in order to have the first cluster found located at @p clusterPosition percentage of the time interval. + * @param startTime starting time, in miliseconds, for the look up. + * @param timeFrame time interval for which to retrieve the data. + * @param selectedIds list of cluster ids to look up for. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits starting time, in recording units, for the look up. + */ + virtual void requestPreviousClusterData(long startTime, long timeFrame, QList selectedIds, QObject* initiator, long startTimeInRecordingUnits); + +private: + // The actual data source of the cluster data. + CerebusTracesProvider* mDataProvider; + + // Channel this provider is responsible for + unsigned int mChannel; +}; + +#endif diff --git a/src/cerebustraceprovider.cpp b/src/cerebustraceprovider.cpp index 1980df3..16571e4 100644 --- a/src/cerebustraceprovider.cpp +++ b/src/cerebustraceprovider.cpp @@ -36,14 +36,23 @@ CerebusTracesProvider::CerebusTracesProvider(SamplingGroup group) : mReconfigured(false), mScales(NULL), mChannels(NULL), - mLiveData(NULL), - mLivePosition(NULL), - mViewData(NULL), - mViewPosition(NULL) { + mLiveTime(NULL), + mViewTime(NULL), + mLiveTraceData(NULL), + mLiveTracePosition(NULL), + mViewTraceData(NULL), + mViewTracePosition(NULL), + mLiveClusterTime(NULL), + mLiveClusterID(NULL), + mLiveClusterPosition(NULL), + mViewClusterTime(NULL), + mViewClusterID(NULL), + mViewClusterPosition(NULL) { // The sampling rate is hardwired to the sampling group this->samplingRate = SAMPLING_RATES[group]; - mCapacity = this->samplingRate * BUFFER_SIZE; + mTraceCapacity = BUFFER_SIZE * this->samplingRate; + mClusterCapacity = BUFFER_SIZE * cbSdk_TICKS_PER_SECOND; // The buffer always has the same size this->length = 1000 * BUFFER_SIZE; @@ -57,13 +66,38 @@ CerebusTracesProvider::~CerebusTracesProvider() { // Free uninitalize internal structures delete[] mScales; delete[] mChannels; - if(mViewData != mLiveData) { + + if(mViewTraceData != mLiveTraceData) { // We are in paused mode - delete[] mViewData; - delete mViewPosition; + delete mViewTime; + + delete[] mViewTraceData; + delete mViewTracePosition; + + for(int i = 0; i < this->nbChannels; i++) { + delete[] mViewClusterTime[i]; + delete[] mViewClusterID[i]; + delete mViewClusterPosition[i]; + } + + delete[] mViewClusterTime; + delete[] mViewClusterID; + delete[] mViewClusterPosition; } - delete[] mLiveData; - delete mLivePosition; + delete mLiveTime; + + delete[] mLiveTraceData; + delete mLiveTracePosition; + + for(int i = 0; i < this->nbChannels; i++) { + delete[] mLiveClusterTime[i]; + delete[] mLiveClusterID[i]; + delete mLiveClusterPosition[i]; + } + + delete[] mLiveClusterTime; + delete[] mLiveClusterID; + delete[] mLiveClusterPosition; } } @@ -120,44 +154,82 @@ bool CerebusTracesProvider::init() { mScales[i] = info.physcalin; } - // Allocate live sample buffer - mLiveData = new INT16[this->nbChannels * mCapacity]; - memset(mLiveData, 0, this->nbChannels * mCapacity * sizeof(INT16)); - mLivePosition = new size_t(0); - - // Link view buffer to live sample buffer - mViewData = mLiveData; - mViewPosition = mLivePosition; + // Lock mutex to initalize cross-thread data structure. + mMutex.lock(); // Register data callback - mMutex.lock(); mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_CONTINUOUS, packageCallback, this); if (mLastResult != CBSDKRESULT_SUCCESS) { delete[] mChannels; delete[] mScales; - mScales = NULL; - delete[] mLiveData; - delete mLivePosition; cbSdkClose(CEREBUS_INSTANCE); return false; } + // Register spike event callback + mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_SPIKE, packageCallback, this); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + // Register config callback mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_GROUPINFO, packageCallback, this); if (mLastResult != CBSDKRESULT_SUCCESS) { delete[] mChannels; delete[] mScales; - mScales = NULL; - delete[] mLiveData; - delete mLivePosition; cbSdkClose(CEREBUS_INSTANCE); return false; } + // Allocate time storage and request system time + mLiveTime = new UINT32(0); + + mLastResult = cbSdkGetTime(CEREBUS_INSTANCE, mLiveTime); + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + delete mLiveTime; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Allocate live sample buffer + mLiveTraceData = new INT16[this->nbChannels * mTraceCapacity]; + memset(mLiveTraceData, 0, this->nbChannels * mTraceCapacity * sizeof(INT16)); + mLiveTracePosition = new size_t(0); + + // Allocate live event buffer + mLiveClusterTime = new UINT32*[this->nbChannels]; + mLiveClusterID = new UINT8*[this->nbChannels]; + mLiveClusterPosition = new size_t*[this->nbChannels]; + + for(int i = 0; i < this->nbChannels; i++) { + mLiveClusterTime[i] = new UINT32[mClusterCapacity]; + memset(mLiveClusterTime[i], 0, mClusterCapacity * sizeof(UINT32)); + mLiveClusterID[i] = new UINT8[mClusterCapacity]; + memset(mLiveClusterID[i], 0, mClusterCapacity * sizeof(UINT8)); + mLiveClusterPosition[i] = new size_t(0); + } + + // Link view buffer to live sample buffer + mViewTime = mLiveTime; + + mViewTraceData = mLiveTraceData; + mViewTracePosition = mLiveTracePosition; + + mViewClusterTime = mLiveClusterTime; + mViewClusterID = mLiveClusterID; + mViewClusterPosition = mLiveClusterPosition; + + // We are done. mInitialized = true; - mMutex.unlock(); + mMutex.unlock(); return true; } @@ -173,12 +245,50 @@ void CerebusTracesProvider::processData(const cbPKT_GROUP* package) { mMutex.unlock(); return; } + + // Update system time (only updated here, because events are always returned in relation to trace window) + (*mLiveTime) = package->time; + // Copy sampled to history - memcpy(mLiveData + ((*mLivePosition) * this->nbChannels), package->data, this->nbChannels * sizeof(INT16)); + memcpy(mLiveTraceData + ((*mLiveTracePosition) * this->nbChannels), package->data, this->nbChannels * sizeof(INT16)); // Adjust position, wrap around if necessary. - (*mLivePosition)++; - if ((*mLivePosition) == mCapacity) (*mLivePosition) = 0; + (*mLiveTracePosition)++; + if ((*mLiveTracePosition) == mTraceCapacity) (*mLiveTracePosition) = 0; + mMutex.unlock(); +} + +void CerebusTracesProvider::processSpike(const cbPKT_SPK* package) { + // Check if spike event was triggered by member of sampling group + int channelIndex = -1; + for(int i = 0; i < this->nbChannels; i++) { + if(mChannels[i] == package->chid) { + channelIndex = i; + break; + } + } + if (channelIndex == -1) + return; + + mMutex.lock(); + + // Channels were reconfigured and we now might receive data from channels we don't know about. + // (No harm, but also no point to continue. We are going to abort soon anyway.) + if (mReconfigured) { + mMutex.unlock(); + return; + } + + // Copy event data to history + mLiveClusterTime[channelIndex][*mLiveClusterPosition[channelIndex]] = package->time; + mLiveClusterID[channelIndex][*mLiveClusterPosition[channelIndex]] = package->unit; + + // Adjust position, wrap around if necessary. + (*mLiveClusterPosition[channelIndex])++; + if ((*mLiveClusterPosition[channelIndex]) == mClusterCapacity) { + (*mLiveClusterPosition[channelIndex]) = 0; + } + mMutex.unlock(); } @@ -203,11 +313,15 @@ void CerebusTracesProvider::packageCallback(UINT32 /*instance*/, const cbSdkPktT case cbSdkPkt_CONTINUOUS: provider->processData(reinterpret_cast(data)); break; + case cbSdkPkt_SPIKE: + provider->processSpike(reinterpret_cast(data)); + break; case cbSdkPkt_GROUPINFO: provider->processConfig(reinterpret_cast(data)); break; case cbSdkPkt_PACKETLOST: // TODO: Take care of package lost here! + qWarning() << "Cerebus SDK: Package lost detected!"; break; } } @@ -265,7 +379,7 @@ void CerebusTracesProvider::retrieveData(long start, long end, QObject* initiato // Copy and convert data from buffer. for (int channel = 0; channel < this->nbChannels; channel++) { // Compute start in relation to current ringbuffer position - size_t offset = startInRecordingUnits + (*mViewPosition); + size_t offset = startInRecordingUnits + (*mViewTracePosition); // Determine unit data is saved in int unit_correction = 0; @@ -277,7 +391,7 @@ void CerebusTracesProvider::retrieveData(long start, long end, QObject* initiato unit_correction = 1000; } else { - qDebug() << "unknown unit for channel " << channel << ": " << unit_string; + qWarning() << "unknown unit for channel " << channel << ": " << unit_string; continue; } @@ -293,13 +407,13 @@ void CerebusTracesProvider::retrieveData(long start, long end, QObject* initiato for (int i = 0; i < lengthInRecordingUnits; i++) { size_t absolute_position = offset + i; // Wrap around in case we reach end of buffer - if (absolute_position >= mCapacity) { - absolute_position -= mCapacity; - offset -= mCapacity; + if (absolute_position >= mTraceCapacity) { + absolute_position -= mTraceCapacity; + offset -= mTraceCapacity; } // Scale data using channel scaling size_t index = (absolute_position * this->nbChannels) + channel; - result(i + 1, channel + 1) = static_cast((((static_cast(mViewData[index]) - min_digital) / range_digital) * range_analog + min_analog) * unit_correction); + result(i + 1, channel + 1) = static_cast((((static_cast(mViewTraceData[index]) - min_digital) / range_digital) * range_analog + min_analog) * unit_correction); } } mMutex.unlock(); @@ -315,31 +429,66 @@ void CerebusTracesProvider::computeRecordingLength(){ void CerebusTracesProvider::slotPagingStarted() { // We are already showing live data. - if(mLiveData == mViewData) + if(mLiveTraceData == mViewTraceData) return; mMutex.lock(); + // Delete view buffers with pause data + delete mViewTime; + + delete[] mViewTraceData; + delete mViewTracePosition; + + for(int i = 0; i < this->nbChannels; i++) { + delete[] mViewClusterTime[i]; + delete[] mViewClusterID[i]; + delete mViewClusterPosition[i]; + } + + delete[] mViewClusterTime; + delete[] mViewClusterID; + delete[] mViewClusterPosition; + + // Use the same buffer for new and displayed samples - delete[] mViewData; - delete mViewPosition; - mViewData = mLiveData; - mViewPosition = mLivePosition; + mViewTime = mLiveTime; + + mViewTraceData = mLiveTraceData; + mViewTracePosition = mLiveTracePosition; + + mViewClusterTime = mLiveClusterTime; + mViewClusterID = mLiveClusterID; + mViewClusterPosition = mLiveClusterPosition; mMutex.unlock(); } void CerebusTracesProvider::slotPagingStopped() { // Check if we are already paused. - if(mLiveData != mViewData) + if(mLiveTraceData != mViewTraceData) return; mMutex.lock(); // Write all new data to a new empty buffer. - mLiveData = new INT16[this->nbChannels * mCapacity]; - memset(mLiveData, 0, this->nbChannels * mCapacity * sizeof(INT16)); - mLivePosition = new size_t(0); + mLiveTime = new UINT32(*mViewTime); + + mLiveTraceData = new INT16[this->nbChannels * mTraceCapacity]; + memset(mLiveTraceData, 0, this->nbChannels * mTraceCapacity * sizeof(INT16)); + mLiveTracePosition = new size_t(0); + + mLiveClusterTime = new UINT32*[this->nbChannels]; + mLiveClusterID = new UINT8*[this->nbChannels]; + mLiveClusterPosition = new size_t*[this->nbChannels]; + + for(int i = 0; i < this->nbChannels; i++) { + mLiveClusterTime[i] = new UINT32[mClusterCapacity]; + memset(mLiveClusterTime[i], 0, mClusterCapacity * sizeof(UINT32)); + mLiveClusterID[i] = new UINT8[mClusterCapacity]; + memset(mLiveClusterID[i], 0, mClusterCapacity * sizeof(UINT8)); + mLiveClusterPosition[i] = new size_t(0); + } mMutex.unlock(); } @@ -423,3 +572,113 @@ std::string CerebusTracesProvider::getLastErrorMessage() { } return "Unknown error code."; } + +QList CerebusTracesProvider::getClusterProviders() { + QList list; + + if(mInitialized) { + // Return a ClustersProvider wrapper for each channel. + for(int i = 0; i < this->nbChannels; i++) { + list.append(new CerebusClustersProvider(this, i, this->samplingRate)); + } + } + + return list; +} + +Array* CerebusTracesProvider::getClusterData(unsigned int channel, long start, long end) { + // The arrays assignment operator is broken, so returning a pointer is a quick fix. + Array* result = new Array ; + + // Abort if not initalized + if(!mInitialized) + return result; + + // Determine start and end index + long startInRecordingUnits = cbSdk_TICKS_PER_SECOND * start / 1000.0; + long endInRecordingUnits = cbSdk_TICKS_PER_SECOND * end / 1000.0; + + long lengthInRecordingUnits = endInRecordingUnits - startInRecordingUnits; + + // Compute values in relation to end of window. + startInRecordingUnits -= mClusterCapacity; + endInRecordingUnits -= mClusterCapacity; + + Q_ASSERT(startInRecordingUnits <= 0); + Q_ASSERT(endInRecordingUnits <= 0); + + // Compute correction factor for timestamps to return + double clockToSampleRatio = cbSdk_TICKS_PER_SECOND / this->samplingRate; + + mMutex.lock(); + + // Correct start and end with current time stamp + startInRecordingUnits += (*mViewTime); + endInRecordingUnits += (*mViewTime); + + // In the beginning or on clock overflow, etc. make sure we stay positive. + if (startInRecordingUnits < 0) + startInRecordingUnits = 0; + + size_t endIndex = (*mViewClusterPosition[channel]); + do { + if(endIndex == 0) endIndex = mClusterCapacity; + endIndex--; + + } while(endIndex != (*mViewClusterPosition[channel]) && + mViewClusterTime[channel][endIndex] > endInRecordingUnits); + endIndex++; + + size_t startIndex = endIndex; + do { + if(startIndex == 0) startIndex = mClusterCapacity; + startIndex--; + } while(startIndex != (*mViewClusterPosition[channel]) && + mViewClusterTime[channel][startIndex] > startInRecordingUnits); + //startIndex++; + + // Adjust result to number of events found + size_t spikeCount = mClusterCapacity + endIndex - startIndex; + if(spikeCount > mClusterCapacity) + spikeCount -= mClusterCapacity; + + if(spikeCount == 0) { + mMutex.unlock(); + return result; + } + + result->setSize(2, spikeCount); + + size_t dataIndex = 0; + if(startIndex <= endIndex) { + // Adjust and copy timestamps + for(size_t i = startIndex; i < endIndex; i++) { + (*result)[dataIndex++] = (lengthInRecordingUnits + mViewClusterTime[channel][i] - endInRecordingUnits) / clockToSampleRatio; + } + // Copy cluster ids + for(size_t i = startIndex; i < endIndex; i++) { + (*result)[dataIndex++] = mViewClusterID[channel][i]; + } + } else { + // Adjust and copy timestamps + for(size_t i = startIndex; i < mClusterCapacity; i++) { + (*result)[dataIndex++] = (lengthInRecordingUnits + mViewClusterTime[channel][i] - endInRecordingUnits) / clockToSampleRatio; + } + for(size_t i = 0; i < endIndex; i++) { + (*result)[dataIndex++] = (lengthInRecordingUnits + mViewClusterTime[channel][i] - endInRecordingUnits) / clockToSampleRatio; + } + // Copy cluster ids + for(size_t i = startIndex; i < mClusterCapacity; i++) { + (*result)[dataIndex++] = mViewClusterID[channel][i]; + } + for(size_t i = 0; i < endIndex; i++) { + (*result)[dataIndex++] = mViewClusterID[channel][i]; + } + } + + Q_ASSERT(dataIndex == 2 * spikeCount); + + mMutex.unlock(); + + return result; +} diff --git a/src/cerebustraceprovider.h b/src/cerebustraceprovider.h index a95130a..54bca6f 100644 --- a/src/cerebustraceprovider.h +++ b/src/cerebustraceprovider.h @@ -25,8 +25,9 @@ #include // Include project files -#include "tracesprovider.h" #include "types.h" +#include "tracesprovider.h" +#include "clustersprovider.h" /** CerebusTracesProvider uses a Blackrock Cerebus NSP as data source. @@ -69,6 +70,11 @@ class CerebusTracesProvider : public TracesProvider { */ bool init(); + /** Returns true if initialized */ + bool isInitialized() { + return mInitialized; + } + /**Computes the number of samples between @p start and @p end. * @param start begining of the time frame from which the data have been retrieved, given in milisecond. * @param end end of the time frame from which to retrieve the data, given in milisecond. @@ -80,6 +86,9 @@ class CerebusTracesProvider : public TracesProvider { // Called by callback to add data to buffer. void processData(const cbPKT_GROUP* package); + // Called by callback to add spike event to buffer. + void processSpike(const cbPKT_SPK* package); + // Called by callback to process configuration changes. void processConfig(const cbPKT_GROUPINFO* package); @@ -130,11 +139,20 @@ class CerebusTracesProvider : public TracesProvider { virtual void slotPagingStarted(); /** Called when paging is stopped. - * Decouples the buffer that is viewed/returned from the one updated. - * This essentialy pauses the signal that is being displayed. - */ + * Decouples the buffer that is viewed/returned from the one updated. + * This essentialy pauses the signal that is being displayed. + */ virtual void slotPagingStopped(); + /** Create a cluster provider for each channel. + */ + QList getClusterProviders(); + + /** Get cluster data for specific channel. + */ + Array* getClusterData(unsigned int channel, long start, long end); + + Q_SIGNALS: /**Signals that the data have been retrieved. * @param data array of data in uV (number of channels X number of samples). @@ -147,7 +165,7 @@ class CerebusTracesProvider : public TracesProvider { static const int CEREBUS_RESOLUTION ; // Default instance id to use to talk to CB SDK static const unsigned int CEREBUS_INSTANCE; - // Length of buffer in seconds + // Length of buffer in seconds (for events it is assumed there is an event for every tick in that second) static const unsigned int BUFFER_SIZE; // Sampling rate of each sampling group static const unsigned int SAMPLING_RATES[6]; @@ -164,20 +182,33 @@ class CerebusTracesProvider : public TracesProvider { // List of NSP channel numbers we are listing to UINT32* mChannels; - // Capacity of buffer (see data and paused_data) - size_t mCapacity; - // Return value of last CBSDK library call int mLastResult; // Data storage mutex QMutex mMutex; - // Data storage - INT16* mLiveData; - size_t* mLivePosition; - INT16* mViewData; - size_t* mViewPosition; + // Latest NSP system clock value + UINT32* mLiveTime; + UINT32* mViewTime; + + // Capacity of buffers + size_t mTraceCapacity; + size_t mClusterCapacity; + + // Continous data storage + INT16* mLiveTraceData; + size_t* mLiveTracePosition; + INT16* mViewTraceData; + size_t* mViewTracePosition; + + // Spike event data storage + UINT32** mLiveClusterTime; + UINT8** mLiveClusterID; + size_t** mLiveClusterPosition; + UINT32** mViewClusterTime; + UINT8** mViewClusterID; + size_t** mViewClusterPosition; /**Retrieves the traces included in the time frame given by @p startTime and @p endTime. * @param startTime begining of the time frame from which to retrieve the data, given in milisecond. diff --git a/src/clustersprovider.h b/src/clustersprovider.h index 420a4af..b1ec5d7 100644 --- a/src/clustersprovider.h +++ b/src/clustersprovider.h @@ -58,7 +58,7 @@ class ClustersProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. */ - void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits); + virtual void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits); /**Looks up for the first of the clusters included in the list @p selectedIds existing after the time @p startTime. @@ -70,7 +70,7 @@ class ClustersProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits starting time, in recording units, for the look up. */ - void requestNextClusterData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator,long startTimeInRecordingUnits); + virtual void requestNextClusterData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator,long startTimeInRecordingUnits); /**Looks up for the first of the clusters included in the list @p selectedIds existing before the time @p endTime. @@ -82,12 +82,12 @@ class ClustersProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits starting time, in recording units, for the look up. */ - void requestPreviousClusterData(long startTime,long timeFrame,QList selectedIds,QObject* initiator,long startTimeInRecordingUnits); - + virtual void requestPreviousClusterData(long startTime,long timeFrame,QList selectedIds,QObject* initiator,long startTimeInRecordingUnits); + /**Loads the cluster ids and the corresponding spike time. * @return an loadReturnMessage enum giving the load status */ - int loadData(); + virtual int loadData(); /**Returns list of cluster Ids. * @return */ @@ -164,7 +164,7 @@ class ClustersProvider : public DataProvider { */ void previousClusterDataReady(Array& data,QObject* initiator,QString providerName,long startingTime,long startingTimeInRecordingUnits); -private: +protected: /**Provider's name.*/ QString name; diff --git a/src/neuroscope.cpp b/src/neuroscope.cpp index 22097ce..4057c55 100644 --- a/src/neuroscope.cpp +++ b/src/neuroscope.cpp @@ -1091,8 +1091,14 @@ void NeuroscopeApp::openDocumentStream() return; } - //update the spike and event browsing status - updateBrowsingStatus(); + // Set up cluster palette + int channelCount = this->doc->getChannelNb(); + createClusterPalette("1"); + for (int i = 2; i <= channelCount; i++) + addClusterFile(QString::number(i)); + + // Update the spike and event browsing status + updateBrowsingStatus(); // Start auto advance page(); diff --git a/src/neuroscopedoc.cpp b/src/neuroscopedoc.cpp index f1b0a33..bd1951e 100644 --- a/src/neuroscopedoc.cpp +++ b/src/neuroscopedoc.cpp @@ -680,6 +680,66 @@ bool NeuroscopeDoc::openStream() { false // show events in position view ); + // Get cluster providers + QList list = cerebusTracesProvider->getClusterProviders(); + + // Increase spike size + this->peakSampleIndex = 64; + this->nbSamples = 128; + + for(QList::iterator providerIterator = list.begin(); + providerIterator != list.end(); + providerIterator++) { + // Get all needed properties from cluster provider + ClustersProvider* clustersProvider = *providerIterator; + QString name = clustersProvider->getName(); + QList clusterList = clustersProvider->clusterIdList(); + + // Add cluster provider to internal structure + lastLoadedProvider = name; + providers.insert(name, clustersProvider); + providerUrls.insert(name, QString("cerebus.") + name + QString(".nev")); + + // Genereate cluster colors (based on color brewer) + ItemColors* clusterColors = new ItemColors(); + + // Unclassified + clusterColors->append(0, QColor::fromRgb(153, 153, 153)); // gray + // Classified + clusterColors->append(1, QColor::fromRgb(247, 129, 191)); // pink + clusterColors->append(2, QColor::fromRgb(166, 86, 40)); // brown + clusterColors->append(3, QColor::fromRgb(255, 255, 51)); // yellow + clusterColors->append(4, QColor::fromRgb(152, 78, 163)); // purple + clusterColors->append(5, QColor::fromRgb(77, 175, 74)); // green + // Artifacts + clusterColors->append(254, QColor::fromRgb(255, 127, 0)); // orange + // Noise + clusterColors->append(255, QColor::fromRgb(228, 26, 28)); // red + + providerItemColors.insert(name, clusterColors); + + // Compute which cluster files give data for a given anatomical group + computeClusterFilesMapping(); + + // Informs the views than there is a new cluster provider. + // There should be only one view, since we only created one display. + QList skipList; + for(int i = 0; i < this->viewList->count(); ++i) { + this->viewList->at(i)->setClusterProvider( + clustersProvider, + name, + clusterColors, + true, // active + clusterList, + &(this->displayGroupsClusterFile), + &(this->channelsSpikeGroups), + this->peakSampleIndex - 1, + this->nbSamples - peakSampleIndex, + skipList + ); + } + + } return true; }