#pragma once #include #include #include #include #include #include "ovp_defines.h" #include #include namespace OpenViBE { namespace Plugins { namespace SimpleVisualization { class CSignalDisplayDrawable; /** * Abtract class of objects than can be updated by a CBufferDatabase */ class CSignalDisplayDrawable { public: virtual ~CSignalDisplayDrawable() = default; virtual void init() = 0; virtual void redraw() = 0; }; /** * This class is used to store information about the incoming signal stream. It can request a CSignalDisplayDrawable * object to redraw himself in case of some changes in its data. */ class CBufferDatabase { public: //! Number of channels int64_t m_NElectrodes = 0; //! Number of channels and number of samples per buffer std::array m_DimSizes; //! Channel labels, buffer labels std::array, 2> m_DimLabels; //! Flag set to true once first buffer is received bool m_HasFirstBuffer = false; //! Sampling frequency of the incoming stream size_t m_Sampling = 0; //! double-linked list of pointers to the samples buffers of the current time window std::deque m_SampleBuffers; //! stimulations to display. pair values are std::deque> m_Stimulations; //electrode spherical coordinates (in degrees) //CMatrix m_oElectrodesSphericalCoords; //flag set to true once channel lookup indices are determined bool m_ChannelLookupTableInitialized = false; //indices of electrodes in channel localisation database std::vector m_ChannelLookupIdxs; //electrode labels (standardized) //std::vector m_oElectrodesLabels; //! Number of buffer to display at the same time uint64_t m_NBufferToDisplay = 2; //! The global maximum value of the signal (up to now) double m_MaxValue = -DBL_MAX; //! The global minimum value of the signal (up to now) double m_MinValue = +DBL_MAX; //! Double-linked list of the start times of the current buffers std::deque m_StartTime; //! Double-linked list of the end times of the current buffers std::deque m_EndTime; //! Duration to display in seconds double m_TotalDuration = 0; /*! Duration to display in openvibe time units. Computed once every time the user changes the total duration to display, when the maximum number of buffers to store are received.*/ uint64_t m_TotalDurationOV = 0; /*! Duration of a single buffer. Computed once, but not constant when sampling frequency is not a multiple of buffer size!*/ uint64_t m_BufferDuration = 0; /*! Time step separating the start times of m_NBufferToDisplay+1 buffers. Recomputed once every time the user changes the total duration to display, but not constant when sampling frequency is not a multiple of buffer size!*/ uint64_t m_TotalStep = 0; /*! Time step separating the start times of 2 consecutive buffers. Computed once, but not constant when sampling frequency is not a multiple of buffer size!*/ uint64_t m_BufferStep = 0; // When did the last inserted buffer end uint64_t m_LastBufferEndTime = 0; // Did we print a warning about noncontinuity? bool m_WarningPrinted = false; //! Pointer to the drawable object to update (if needed) CSignalDisplayDrawable* m_Drawable = nullptr; std::vector>> m_LocalMinMaxValue; Toolkit::TBoxAlgorithm& m_ParentPlugin; bool m_Error = false; //! Redraws the associated SignalDisplayDrawable upon new data reception if true (default) bool m_RedrawOnNewData = true; protected: /* \name Channel localisation */ //@{ //channel localisation decoder Kernel::IAlgorithmProxy* m_decoder = nullptr; //flag set to true once channel localisation buffer is received bool m_channelLocalisationHeaderReceived = false; //dynamic channel localisation flag (e.g. localisation is constantly updated with MEG) bool m_dynamicChannelLocalisation = false; //channel labels database std::vector m_channelLocalisationLabels; //flag stating whether streamed coordinates are cartesian (as opposed to spherical) bool m_cartesianCoords = false; //! double-linked list of streamed channel coordinates (if cartesian, expressed in normalized space (X right Y front Z up)) std::deque> m_channelLocalisationCoords; //! double-linked list of channel coordinates (spherical if streamed coords aere cartesian and vice versa) //std::deque< std::pair > m_oChannelLocalisationAlternateCoords; //pointer to double linked list of cartesian coordinates //std::deque< std::pair > * m_pChannelLocalisationCartesianCoords = nullptr; //pointer to double linked list of spherical coordinates //std::deque< std::pair > * m_pChannelLocalisationSphericalCoords = nullptr; //! double-linked list of start/end times of channel coordinates std::deque> m_channelLocalisationTimes; //@} //! Redraw mode (shift or scan) CIdentifier m_displayMode = Scan; public: explicit CBufferDatabase(Toolkit::TBoxAlgorithm& parent); virtual ~CBufferDatabase(); /** * \brief Decode a channel localisation memory buffer * \param buffer Memory buffer to decode * \param startTime Start time of memory buffer * \param endTime End time of memory buffer * \return True if memory buffer could be properly decoded, false otherwise */ virtual bool decodeChannelLocalisationMemoryBuffer(const IMemoryBuffer* buffer, uint64_t startTime, uint64_t endTime); /** * \brief Callback called upon channel localisation buffer reception * \param index Index of newly received channel localisation buffer * \return True if buffer data was correctly processed, false otherwise */ virtual bool onChannelLocalisationBufferReceived(const size_t index); /** * \brief Sets the drawable object to update. * \param drawable drawable object to update. */ virtual void setDrawable(CSignalDisplayDrawable* drawable) { m_Drawable = drawable; } /** * \brief Get error status * \return Error status. If true, an error occurred. */ virtual bool getErrorStatus() { return m_Error; } /** * \brief Determines whether first buffer has been received yet * \return True if first buffer has been received already, false otherwise */ virtual bool hasFirstBuffer() { return m_HasFirstBuffer; } /** * \brief Determines whether first channel localisation buffer has been processed yet * When this condition is true, channel coordinates may be retrieved using the * corresponding methods in this class. * \return True if first chanloc buffer was processed */ virtual bool isFirstChannelLocalisationBufferProcessed(); /** * Compute the number of buffers needed to display the signal for a certain time period. * \param time the time window's width in seconds. */ virtual bool adjustNumberOfDisplayedBuffers(double time); /** * \brief Get time interval covered by data held in this object * \return Time interval in ms */ virtual double getDisplayedTimeIntervalWidth() const { return (m_NBufferToDisplay * ((m_DimSizes[1] * 1000.0) / m_Sampling)); } /** * \brief Determine whether time passed in parameter lies in displayed data interval * \param time Time to test * \return True if time lies in displayed time interval, false otherwise */ virtual bool isTimeInDisplayedInterval(const uint64_t& time) const; /** * \brief Get index of sample buffer which starts at a given time * \param time[in] Start time of buffer * \param index[out] Buffer index * \return True if buffer index could be determined, false otherwise */ virtual bool getIndexOfBufferStartingAtTime(const uint64_t& time, size_t& index) const; //! Returns the min/max values currently displayed for the given channel virtual void getDisplayedChannelLocalMinMaxValue(const size_t channel, double& min, double& max); //! Returns the min/max values currently displayed (all channels taken into account) virtual void getDisplayedGlobalMinMaxValue(double& min, double& max); virtual void getDisplayedChannelLocalMeanValue(const size_t /*channel*/, double& /*mean*/) {} //! Returns the min/max values of the last buffer arrived for the given channel virtual void getLastBufferChannelLocalMinMaxValue(const size_t channel, double& min, double& max) { min = m_LocalMinMaxValue[channel].back().first; max = m_LocalMinMaxValue[channel].back().second; } //! Returns the min/max values of the last buffer arrived (all channels taken into account) virtual void getLastBufferMinMaxValue(double& min, double& max) { min = +DBL_MAX; max = -DBL_MAX; for (auto& pair : m_LocalMinMaxValue) { min = (pair.back().first < min) ? pair.back().first : min; max = (pair.back().second > max) ? pair.back().second : max; } } /** * \brief Get number of eletrodes in database * \return Number of electrodes */ virtual size_t getElectrodeCount() { return m_channelLocalisationLabels.size(); } /** * \brief Get electrode normalized position * \remarks Position expressed in normalized cartesian frame where X is right, Y front, Z up * \param[in] index Index of electrode in database whose position is to be retrieved * \param[out] position Pointer to an array of 3 floats where to store coordinates * \return True if electrode position could be retrieved */ virtual bool getElectrodePosition(const size_t index, double* position); /** * \brief Get electrode normalized position * \remarks Position expressed in normalized cartesian frame where X is right, Y front, Z up * \param[in] label Label of electrode whose position is to be retrieved * \param[out] position Pointer to an array of 3 floats where to store coordinates * \return True if electrode position could be retrieved */ virtual bool getElectrodePosition(const CString& label, double* position); /** * \brief Get electrode label * \param[in] index Index of electrode in database whose label is to be retrieved * \param[out] label Electrode label * \return True if electrode label could be retrieved */ virtual bool getElectrodeLabel(const size_t index, CString& label); /** * \brief Get number of channels * \return Number of channels */ virtual size_t getChannelCount() const { return m_DimSizes[0]; } /** * \brief Get channel normalized position * \remarks Position expressed in normalized cartesian frame where X is right, Y front, Z up * \param[in] index Index of channel whose position is to be retrieved * \param[out] position Reference on a double pointer * \return True if channel position could be retrieved (position then points to an array of 3 floats) */ virtual bool getChannelPosition(const size_t index, double*& position); /** * \brief Get channel spherical coordinates in degrees * \param[in] index Index of channel whose coordinates are to be retrieved * \param[out] theta Reference on a float to be set with theta angle * \param[out] phi Reference on a float to be set with phi angle * \return True if channel coordinates could be retrieved */ virtual bool getChannelSphericalCoordinates(const size_t index, double& theta, double& phi); /** * \brief Get channel label * \param[in] index Index of channel whose label is to be retrieved * \param[out] label Channel label * \return True if channel label could be retrieved */ virtual bool getChannelLabel(const size_t index, CString& label); virtual void setMatrixDimensionCount(const size_t count); virtual void setMatrixDimensionSize(const size_t index, const size_t size); virtual void setMatrixDimensionLabel(const size_t idx1, const size_t idx2, const char* label); // Returns false on failure virtual bool setMatrixBuffer(const double* buffer, uint64_t startTime, uint64_t endTime); // Sets the sampling frequency. If this is not called, the frequency is estimated from the stream chunk properties. // Mainly used to force a warning if stream-specified rate differs from the chunk-estimated rate. virtual bool setSampling(const size_t sampling); virtual void setStimulationCount(const size_t /*count*/) {} virtual void setStimulation(const size_t index, uint64_t identifier, uint64_t date); /** * \brief Set display mode * \remarks Used by signal display and time ruler to determine how they should be updated * \param mode New display mode */ virtual void setDisplayMode(const CIdentifier& mode) { m_displayMode = mode; } /** * \brief Get current display mode * \return Current display mode */ virtual CIdentifier getDisplayMode() { return m_displayMode; } /** * \brief Set flag stating whether to redraw associated SignalDisplayDrawable objet when new data is available * \param set Value to set flag with */ virtual void setRedrawOnNewData(const bool set) { m_RedrawOnNewData = set; } protected: /** * \brief Initialize table storing indices of electrodes in channel localisation database * \return True if table could be initialized */ virtual bool fillChannelLookupTable(); /** * \brief Convert a cartesian coordinates triplet to spherical coordinates * \param[in] cartesian Pointer to cartesian coordinates triplet * \param[out] theta Equivalent theta angle * \param[out] phi Equivalent phi angle * \return True if coordinates were successfully converted */ bool convertCartesianToSpherical(const double* cartesian, double& theta, double& phi) const; }; } // namespace SimpleVisualization } // namespace Plugins } // namespace OpenViBE