This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,33 @@
PROJECT(openvibe-plugins-designer-simple-visualization)
MESSAGE(STATUS "Now building ${PROJECT_NAME} ${PROJECT_VERSION} (${OV_PROJECT_BRANCH}~${OV_PROJECT_COMMITHASH})" )
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.hpp src/*.inl)
INCLUDE("FindSourceRCProperties")
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
ADD_DEFINITIONS(-D_USE_MATH_DEFINES) # Definition for constant math as M_PI
INCLUDE("AddOpenViBESDKComponents")
INCLUDE("FindOpenViBEVisualizationToolkit")
INCLUDE("FindThirdPartyGTK")
# ---------------------------------
# Target macros
# Defines target operating system, architecture and compiler
# ---------------------------------
SET_BUILD_PLATFORM()
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
INSTALL(DIRECTORY share/ DESTINATION ${DIST_DATADIR}/openvibe/plugins/simple-visualization)
@@ -0,0 +1,117 @@
/**
* \page BoxAlgorithm_2DTopographicMap 2D topographic map
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Description|
The 2D Topographic Map plugin is one possible way to combine signal measures and topographical information. It interpolates measured potentials
and maps them over the scalp surface using a color scale, making it easy to locate areas where brain activity is the most intense at any given time.
Since this is a 2D plugin, one cannot freely move the camera around the head, as is the case with the 3D Topographic Map plugin
(see \ref BoxAlgorithm_3DTopographicMap). However, the user may switch between several views which, when combined, cover the whole of the skull area
over which potentials may be mapped.
This plugin uses a spherical spline algorithm to interpolate potentials measured at electrode locations over an area of interest (which varies depending on the active view).
One can choose to interpolate potentials (which is done by interpolating along a spline whose control points lie at electrode locations) or current densities (which are
computed using the spline laplacian).
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Inputs|
Note: the channel names specified on the 'Signal' and 'Channel Localization' input streams should match. Mismatches may not be reported by the box. For example, if electrode localisation file reader provides the positions of the electrodes from some configuration file, the channel names in that file should correspend to the names of the signal channels delivered in the Signal stream.
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Inputs|
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Input1|
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Input1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Settings|
Offline options include interpolation type (direct spline interpolation for potentials, or spline laplacian to map currents) and delay applied when mapping data (0 by default). The latter
can prove useful in the case of e.g. neurofeedback experiments. Indeed, subjects may find it easier to observe their mental activity and the effect mental tasks can have on it
when it is displayed with a small delay (on the order of a few hundreds of milliseconds).
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Settings|
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Setting1|
Spline or laplacian interpolation.
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Setting1|
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Setting2|
Delay to apply to displayed data, in seconds
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Setting2|
__________________________________________________________________
Online visualisation settings
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_OnlineVisualizationSettings|
Online options include :
- Radial/Axial Projection : these settings control the projection mode. Radial projection displays a greater mapping area, but localisation is less intuitive than
with axial projection.
- Map Potentials/Currents : these settings control the nature of mapped values. While potentials are computed from spline values, currents computation uses the spline laplacian.
- Toggle Electrodes : toggle electrodes on/off.
- Top/Left/Right/Back View : these buttons enable view switching.
- Delay : this cursor defines the delay to apply to the values that are drawn.
\image html topographicmap2ddisplay_toolbar.png "2D Topographic Map toolbar."
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_OnlineVisualizationSettings|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Examples|
Practical example :
This example is based on the following scenario file :
\code
box-tutorials/topographic_map.xml
\endcode
To see this plugin in action, one should first locate a file corresponding to a prerecorded EEG or MEG session and play it back using e. g. a GDF file reader or some other suitable plugin.
The GDF file used by default in this scenario is the following :
\code
signals/real-hand-movements.gdf
\endcode
Signal data read by this box can be forwarded to the 2D Topographic Map plugin, which has a single input connector. However, interpolating unprocessed data will not deliver very meaningful
results. To make it easier to analyse, one can restrict signals to a frequency band of interest. Also, it can be desirable to average out signals using DSPs (a typical formula
consists in visualising log(1+X*X) where X represents incoming signals) and epoch average boxes. This should result in smoothed out signals where peak activity areas will move gradually
between frames, thus making them easily identifiable on the map.
Let's use a temporal filter box (found under 'Signal Processing > Filters') to restrict incoming signals to a frequency band of our choice, e.g. the Beta range, about 16-24 Hz.
Appropriate settings could go like this : 'Butterworth' filter type, 'Band Pass' filtering, 16Hz low pass band edge, 24Hz high pass band edge).
Next, one can average out signals by applying the typical log(1+X*X) formula. A 'Simple DSP' (found under 'Signal Processing > Basic') can compute the square of the signal ('Equation'
should read 'X*X'), its output is averaged using a Signal Average box (found under 'Signal Processing > Basic'), and the decimal log of its output is obtained using another
Simple DSP box (formula : 'log(X)')).
Finally, once data has been restricted to a given frequency band and averaged out, it can be further smoothed out in time using an Epoch Average box ('Signal Processing > Averaging').
This allows to average data over a number of epochs. re information.
Here is what an EEG recording may look like using the scenario that was just described :
\image html topographicmap2ddisplay_online.png "Top view mapping of potentials interpolated from 9 EEG electrodes."
\image latex topographicmap2ddisplay_online.png "Top view mapping of potentials interpolated from 9 EEG electrodes." width=10cm
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_2DTopographicMap_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_2DTopographicMap_Miscellaneous|
*/
@@ -0,0 +1,81 @@
/**
* \page BoxAlgorithm_MatrixDisplay Matrix display
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Description|
This box displays an input matrix in a table of gradient-colored squares.
<b> WARNING : The color gradient is centered (50%) at the value 0. </b>
The limits of colors match the minimum and maximum values, ever received or in real time if the corresponding setting is set.
These values can evolve symmetrically, using the maximum absolute value.
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Inputs|
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Inputs|
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Input1|
The matrix to display.
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Input1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Settings|
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Settings|
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Setting1|
The color gradient used.
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Setting1|
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Setting2|
The number of color steps used in the gradient. The more you have steps, the smoother the gradient will be .
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Setting2|
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Setting3|
This parameter determines if the minimum and maximum values, matching the 0% and 100% color in the gradient,
should change symmetrically using the maximum absolute value and its opposite.
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Setting3|
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Setting4|
This parameter determines if the minimum and maximum values, matching the 0% and 100% color in the gradient,
should be recomputed for each matrix received.
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Setting4|
__________________________________________________________________
Online visualisation settings
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_OnlineVisualizationSettings|
Online settings :
- Setting1 : Show/hide values
- Setting2 : Show/hide colors
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_OnlineVisualizationSettings|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Examples|
Here is an example using a \ref BoxAlgorithm_ConfusionMatrix box that computes confusion matrix from classifier results.
\image html "matrix_display_online.png" "Confusion matrix displayed using the Matrix Display"
\image latex "matrix_display_online.png" "Confusion matrix displayed using the Matrix Display" width=10cm
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_MatrixDisplay_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_MatrixDisplay_Miscellaneous|
*/
@@ -0,0 +1,129 @@
.. _Doc_BoxAlgorithm_2DTopographicMap:
2D topographic map
==================
.. container:: attribution
:Author:
Vincent Delannoy
:Company:
INRIA/IRISA
.. image:: images/Doc_BoxAlgorithm_2DTopographicMap.png
The 2D Topographic Map plugin is one possible way to combine signal measures and topographical information. It interpolates measured potentials
and maps them over the scalp surface using a color scale, making it easy to locate areas where brain activity is the most intense at any given time.
Since this is a 2D plugin, one cannot freely move the camera around the head. However, the user may switch between several views which,
when combined, cover the whole of the skull area over which potentials may be mapped.
This plugin uses a spherical spline algorithm to interpolate potentials measured at electrode locations over an area of interest (which varies depending on the active view).
One can choose to interpolate potentials (which is done by interpolating along a spline whose control points lie at electrode locations) or current densities (which are
computed using the spline laplacian).
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Signal", "Streamed matrix"
"Channel localization", "Channel localisation"
Note: the channel names specified on the 'Signal' and 'Channel Localization' input streams should match. Mismatches may not be reported by the box. For example, if electrode localisation file reader provides the positions of the electrodes from some configuration file, the channel names in that file should correspend to the names of the signal channels delivered in the Signal stream.
.. _Doc_BoxAlgorithm_2DTopographicMap_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Interpolation type", "Spherical linear interpolation type", "1"
"Delay (in s)", "Float", "0"
Offline options include interpolation type (direct spline interpolation for potentials, or spline laplacian to map currents) and delay applied when mapping data (0 by default). The latter
can prove useful in the case of e.g. neurofeedback experiments. Indeed, subjects may find it easier to observe their mental activity and the effect mental tasks can have on it
when it is displayed with a small delay (on the order of a few hundreds of milliseconds).
Interpolation type
~~~~~~~~~~~~~~~~~~
Spline or laplacian interpolation.
Delay (in s)
~~~~~~~~~~~~
Delay to apply to displayed data, in seconds
.. _Doc_BoxAlgorithm_2DTopographicMap_VizSettings:
Visualization Settings
----------------------
Online options include :
- Radial/Axial Projection : these settings control the projection mode. Radial projection displays a greater mapping area, but localisation is less intuitive than
with axial projection.
- Map Potentials/Currents : these settings control the nature of mapped values. While potentials are computed from spline values, currents computation uses the spline laplacian.
- Toggle Electrodes : toggle electrodes on/off.
- Top/Left/Right/Back View : these buttons enable view switching.
- Delay : this cursor defines the delay to apply to the values that are drawn.
.. figure:: images/topographicmap2ddisplay_toolbar.png
:alt: 2D Topographic Map toolbar.
:align: center
2D Topographic Map toolbar.
.. _Doc_BoxAlgorithm_2DTopographicMap_Examples:
Examples
--------
Practical example :
This example is based on the following scenario file :
.. code::
box-tutorials/topographic_map.xml
To see this plugin in action, one should first locate a file corresponding to a prerecorded EEG or MEG session and play it back using e. g. a GDF file reader or some other suitable plugin.
The GDF file used by default in this scenario is the following :
.. code::
signals/real-hand-movements.gdf
Signal data read by this box can be forwarded to the 2D Topographic Map plugin, which has a single input connector. However, interpolating unprocessed data will not deliver very meaningful
results. To make it easier to analyse, one can restrict signals to a frequency band of interest. Also, it can be desirable to average out signals using DSPs (a typical formula
consists in visualising log(1+X\*X) where X represents incoming signals) and epoch average boxes. This should result in smoothed out signals where peak activity areas will move gradually
between frames, thus making them easily identifiable on the map.
Let's use a temporal filter box (found under 'Signal Processing > Filters') to restrict incoming signals to a frequency band of our choice, e.g. the Beta range, about 16-24 Hz.
Appropriate settings could go like this : 'Butterworth' filter type, 'Band Pass' filtering, 16Hz low pass band edge, 24Hz high pass band edge).
Next, one can average out signals by applying the typical log(1+X\*X) formula. A 'Simple DSP' (found under 'Signal Processing > Basic') can compute the square of the signal ('Equation'
should read 'X\*X'), its output is averaged using a Signal Average box (found under 'Signal Processing > Basic'), and the decimal log of its output is obtained using another
Simple DSP box (formula : 'log(X)')).
Finally, once data has been restricted to a given frequency band and averaged out, it can be further smoothed out in time using an Epoch Average box ('Signal Processing > Averaging').
This allows to average data over a number of epochs. re information.
Here is what an EEG recording may look like using the scenario that was just described :
.. figure:: images/topographicmap2ddisplay_online.png
:alt: Top view mapping of potentials interpolated from 9 EEG electrodes.
:align: center
Top view mapping of potentials interpolated from 9 EEG electrodes.
@@ -0,0 +1,94 @@
.. _Doc_BoxAlgorithm_MatrixDisplay:
Matrix Display
==============
.. container:: attribution
:Author:
Laurent Bonnet
:Company:
INRIA/IRISA
.. image:: images/Doc_BoxAlgorithm_MatrixDisplay.png
The streamed matrix can be visualized using a table of values and/or a color gradient.
This box displays an input matrix in a table of gradient-colored squares.
**WARNING : The color gradient is centered (50%) at the value 0.**
The limits of colors match the minimum and maximum values, ever received or in real time if the corresponding setting is set.
These values can evolve symmetrically, using the maximum absolute value.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Matrix", "Streamed matrix"
Matrix
~~~~~~
The matrix to display.
.. _Doc_BoxAlgorithm_MatrixDisplay_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Color gradient", "Color Gradient", "0:2,36,58; 50:100,100,100; 100:83,17,20"
"Steps", "Integer", "100"
"Symetric min/max", "Boolean", "false"
"Real time min/max", "Boolean", "false"
Color gradient
~~~~~~~~~~~~~~
The color gradient used.
Steps
~~~~~
The number of color steps used in the gradient. The more you have steps, the smoother the gradient will be .
Symetric min/max
~~~~~~~~~~~~~~~~
This parameter determines if the minimum and maximum values, matching the 0% and 100% color in the gradient,
should change symmetrically using the maximum absolute value and its opposite.
Real time min/max
~~~~~~~~~~~~~~~~~
This parameter determines if the minimum and maximum values, matching the 0% and 100% color in the gradient,
should be recomputed for each matrix received.
.. _Doc_BoxAlgorithm_MatrixDisplay_VizSettings:
Visualization Settings
----------------------
Online settings :
- Setting1 : Show/hide values
- Setting2 : Show/hide colors
.. _Doc_BoxAlgorithm_MatrixDisplay_Examples:
Examples
--------
Here is an example using a :ref:`Doc_BoxAlgorithm_ConfusionMatrix` box that computes confusion matrix from classifier results.
.. figure:: images/matrix_display_online.png
:alt: Confusion matrix displayed using the Matrix Display
:align: center
Confusion matrix displayed using the Matrix Display
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,80 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.6 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="matrix-display">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkTable" id="matrix-display-table">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">8</property>
<property name="column_spacing">8</property>
<property name="row_spacing">8</property>
<property name="homogeneous">True</property>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="matrix-display-toolbar">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="title" translatable="yes">Matrix Display</property>
<property name="type_hint">dialog</property>
<child>
<object class="GtkToolbar" id="matrix-display-settings">
<property name="visible">True</property>
<property name="show_arrow">False</property>
<child>
<object class="GtkToggleToolButton" id="show-values-toggle-button">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">show values</property>
<property name="stock_id">gtk-refresh</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton" id="show-colors-toggle-button">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">show colors</property>
<property name="stock_id">gtk-select-color</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="dummy-window">
<child>
<object class="GtkEventBox" id="matrix-value-eventbox">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="matrix-value-label">
<property name="visible">True</property>
<property name="label" translatable="yes">X</property>
</object>
</child>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,208 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="upper">2</property>
<property name="step_increment">0.10000000000000001</property>
<property name="page_increment">0.5</property>
<property name="page_size">10</property>
</object>
<object class="GtkWindow" id="Toolbar">
<property name="title" translatable="yes">2D Topographic Map Toolbar</property>
<property name="resizable">False</property>
<property name="window_position">mouse</property>
<property name="type_hint">dialog</property>
<child>
<object class="GtkToolbar" id="DisplayToolbar">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="toolbar_style">both</property>
<property name="show_arrow">False</property>
<child>
<object class="GtkRadioToolButton" id="RadialProjection">
<property name="visible">True</property>
<property name="label" translatable="yes">Radial Projection</property>
<property name="stock_id">gtk-convert</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="AxialProjection">
<property name="visible">True</property>
<property name="label" translatable="yes">Axial Projection</property>
<property name="stock_id">gtk-convert</property>
<property name="group">RadialProjection</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="MapPotentials">
<property name="visible">True</property>
<property name="label" translatable="yes">Map Potentials</property>
<property name="stock_id">gtk-preferences</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="MapCurrents">
<property name="visible">True</property>
<property name="label" translatable="yes">Map Currents</property>
<property name="stock_id">gtk-preferences</property>
<property name="group">MapPotentials</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton" id="ToggleElectrodes">
<property name="visible">True</property>
<property name="label" translatable="yes">Toggle Electrodes</property>
<property name="stock_id">gtk-info</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="separator2">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="TopView">
<property name="visible">True</property>
<property name="label" translatable="yes">Top View</property>
<property name="stock_id">gtk-orientation-portrait</property>
<property name="active">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="LeftView">
<property name="visible">True</property>
<property name="label" translatable="yes">Left View</property>
<property name="stock_id">gtk-orientation-portrait</property>
<property name="group">TopView</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="RightView">
<property name="visible">True</property>
<property name="label" translatable="yes">Right View</property>
<property name="stock_id">gtk-orientation-portrait</property>
<property name="group">TopView</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkRadioToolButton" id="BackView">
<property name="visible">True</property>
<property name="label" translatable="yes">Back View</property>
<property name="stock_id">gtk-orientation-portrait</property>
<property name="group">TopView</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem" id="sep">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<object class="GtkToolItem" id="toolbutton2">
<property name="visible">True</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<child>
<object class="GtkHScale" id="DelayScale">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="lower_stepper_sensitivity">on</property>
<property name="upper_stepper_sensitivity">on</property>
<property name="restrict_to_fill_level">False</property>
<property name="fill_level">0</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Delay (s)</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="Window">
<child>
<object class="GtkDrawingArea" id="TopographicMap2DDrawingArea">
<property name="visible">True</property>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
@@ -0,0 +1,613 @@
/*****************************************************************************/
/** **/
/** linpack : matrix computation **/
/** **/
/** from Linpack User's guide **/
/** J.J. Dongarra, J.R. Brunch, C.B. Moler, G.W. Stewart **/
/** SIAM Philadelphia 1979 **/
/** **/
/** Routines are translated to C from original FORTRAN code **/
/** **/
/*****************************************************************************/
/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
/*& Libraries to include &*/
/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
#include <cmath>
#include <cstdlib>
#define dmax(x,y) (((x) > (y)) ? (x) : (y))
void saxpy(const int* n, const double* sa, const double* sx, const int* incx, double* sy, const int* incy);
double sdot(const int* n, const double* sx, const int* incx, const double* sy, const int* incy);
void sswap(const int* n, double* sx, const int* incx, double* sy, const int* incy);
int isamax(const int* n, const double* sx, const int* incx);
/************************************************************************/
/* sspfa */
/* */
/* Factorization of a symmetric matrix A (n x n) */
/* by elimination with symmetric pivoting */
/* */
/* to solve A * X = B, follow sspfa by sspl */
/* */
/* IN */
/* ap : 1-D array (double) */
/* the packed form of the symmetric matrix A */
/* the columns of the upper triangle are stored sequentially */
/* in a 1-dimensional array of length n*(n+1)/2 */
/* n : order of matrix A (integer) */
/* */
/* OUT */
/* ap : 1-D array (double) */
/* a block diagonal matrix and the multipliers which were used */
/* to obtain it stored in packed form. */
/* the factorization can be written A = U * D * trans(U) */
/* where U is a product of permutation and unit upper triangular*/
/* matrices, trans(U) is the transpose of U, and D is block */
/* diagonal with 1 by 1 and 2 by 2 blocks. */
/* kpvt : 1-D array (integer) */
/* vector of pivot indices */
/* info : return code (integer) */
/* = 0 normal value */
/* = k if the k-th pivot block is singular */
/* */
/************************************************************************/
void sspfa(double* ap, const int* n, int* kpvt, int* info)
{
double alpha, ak, bk, denom, t, tulk, tulkm1, absakk;
double akm1, bkm1, colmax, rowmax;
int ij, ik, ikm1, im = 0, imj, imk, imax, imaxp1, imim;
int j, jj, jk, jkm1, jmax, jmim;
int k, kk, km1, km2, km1K, km1Km1;
int kstep, swap;
int one, itmp;
one = 1;
alpha = 0.6404;
*info = 0;
k = *n;
ik = (*n * (*n - 1)) / 2;
begin_dspfa:;
if (k == 0) { goto end_dspfa; }
if (k <= 1)
{
*kpvt = 1;
if (*ap == 0.0) { *info = 1; }
goto end_dspfa;
}
km1 = k - 1;
kk = ik + k;
absakk = fabs(*(ap + kk - 1));
itmp = k - 1;
imax = isamax(&itmp, (ap + ik), &one);
imk = ik + imax;
colmax = fabs(*(ap + imk - 1));
if (absakk >= (alpha * colmax))
{
kstep = 1;
swap = 0;
}
else
{
rowmax = 0.0;
imaxp1 = imax + 1;
im = (imax * (imax - 1)) / 2;
imj = im + 2 * imax;
for (j = imaxp1; j <= k; ++j)
{
rowmax = dmax(rowmax, fabs(*(ap + imj - 1)));
imj += j;
}
if (imax != 1)
{
itmp = imax - 1;
jmax = isamax(&itmp, (ap + im), &one);
jmim = jmax + im;
rowmax = dmax(rowmax, fabs(*(ap + jmim - 1)));
}
imim = imax + im;
if (fabs(*(ap + imim - 1)) >= (alpha * rowmax))
{
kstep = 1;
swap = 1;
}
else
{
if (absakk >= (alpha * colmax * (colmax / rowmax)))
{
kstep = 1;
swap = 0;
}
else
{
kstep = 2;
swap = (imax != km1);
}
}
}
if (dmax(absakk, colmax) == 0.0)
{
*(kpvt + k - 1) = k;
*info = k;
}
else
{
if (kstep != 2)
{
if (swap)
{
sswap(&imax, (ap + im), &one, (ap + ik), &one);
imj = ik + imax;
for (jj = imax; jj <= k; ++jj)
{
j = k + imax - jj;
jk = ik + j;
t = *(ap + jk - 1);
*(ap + jk - 1) = *(ap + imj - 1);
*(ap + imj - 1) = t;
imj -= j - 1;
}
}
ij = ik - (k - 1);
for (jj = 1; jj <= km1; ++jj)
{
j = k - jj;
jk = ik + j;
tulk = -(*(ap + jk - 1)) / (*(ap + kk - 1));
t = tulk;
saxpy(&j, &t, (ap + ik), &one, (ap + ij), &one);
// FIXME is it necessary to keep next line uncomment ?
//ijj = ij + j;
*(ap + jk - 1) = tulk;
ij -= j - 1;
}
*(kpvt + k - 1) = k;
if (swap) { *(kpvt + k - 1) = imax; }
}
else
{
km1K = ik + k - 1;
ikm1 = ik - (k - 1);
if (swap)
{
sswap(&imax, (ap + im), &one, (ap + ikm1), &one);
imj = ikm1 + imax;
for (jj = imax; jj <= km1; ++jj)
{
j = km1 + imax - jj;
jkm1 = ikm1 + j;
t = *(ap + jkm1 - 1);
*(ap + jkm1 - 1) = *(ap + imj - 1);
*(ap + imj - 1) = t;
imj -= j - 1;
}
t = *(ap + km1K - 1);
*(ap + km1K - 1) = *(ap + imk - 1);
*(ap + imk - 1) = t;
}
km2 = k - 2;
if (km2 != 0)
{
ak = *(ap + kk - 1) / (*(ap + km1K - 1));
km1Km1 = ikm1 + k - 1;
akm1 = *(ap + km1Km1 - 1) / (*(ap + km1K - 1));
denom = 1.0 - ak * akm1;
ij = ik - (k - 1) - (k - 2);
for (jj = 1; jj <= km2; ++jj)
{
j = km1 - jj;
jk = ik + j;
bk = *(ap + jk - 1) / (*(ap + km1K - 1));
jkm1 = ikm1 + j;
bkm1 = *(ap + jkm1 - 1) / (*(ap + km1K - 1));
tulk = (akm1 * bk - bkm1) / denom;
tulkm1 = (ak * bkm1 - bk) / denom;
t = tulk;
saxpy(&j, &t, (ap + ik), &one, (ap + ij), &one);
t = tulkm1;
saxpy(&j, &t, (ap + ikm1), &one, (ap + ij), &one);
*(ap + jk - 1) = tulk;
*(ap + jkm1 - 1) = tulkm1;
// FIXME is it necessary to keep next line uncomment ?
//ijj = ij + j;
ij -= j - 1;
}
}
*(kpvt + k - 1) = 1 - k;
if (swap) { *(kpvt + k - 1) = -imax; }
*(kpvt + k - 2) = *(kpvt + k - 1);
}
}
ik -= k - 1;
if (kstep == 2) { ik -= k - 2; }
k -= kstep;
goto begin_dspfa;
end_dspfa:;
}
/************************************************************************/
/* sspsl */
/* */
/* Solving a symmetric system A * X = B using the factorization */
/* computed by sspfa (with return code = 0) */
/* */
/* IN */
/* ap : 1-D array (double) */
/* the output array from sspfa */
/* n : order of matrix A (integer) */
/* kpvt : 1-D array (integer) */
/* the pivot vector from sspfa */
/* b : 1-D array (double) */
/* the right hand side vector */
/* */
/* OUT */
/* b : 1-D array (double) */
/* the solution vector X */
/* */
/************************************************************************/
void sspsl(double* ap, const int* n, const int* kpvt, double* b)
{
double temp;
int kp;
int one, oneb, itmp;
one = 1;
oneb = 1;
int k = *n;
int ik = (*n * (*n - 1)) / 2;
while (k > 0)
{
int kk = ik + k;
if (*(kpvt + k - 1) >= 0)
{
if (k != 1)
{
kp = *(kpvt + k - 1);
if (kp != k)
{
temp = *(b + k - 1);
*(b + k - 1) = *(b + kp - 1);
*(b + kp - 1) = temp;
}
itmp = k - 1;
saxpy(&itmp, (b + k - 1), (ap + ik), &one, b, &oneb);
}
*(b + k - 1) /= *(ap + kk - 1);
k--;
ik -= k;
}
else
{
const int ikm1 = ik - (k - 1);
if (ik != 2)
{
kp = abs(*(kpvt + k - 1));
if (kp != (k - 1))
{
temp = *(b + k - 2);
*(b + k - 2) = *(b + kp - 1);
*(b + kp - 1) = temp;
}
itmp = k - 2;
saxpy(&itmp, (b + k - 1), (ap + ik), &one, b, &oneb);
saxpy(&itmp, (b + k - 2), (ap + ikm1), &one, b, &oneb);
}
const int km1k = ik + k - 1;
kk = ik + k;
const double ak = *(ap + kk - 1) / (*(ap + km1k - 1));
const int km1km1 = ikm1 + k - 1;
const double akm1 = *(ap + km1km1 - 1) / (*(ap + km1k - 1));
const double bk = *(b + k - 1) / (*(ap + km1k - 1));
const double bkm1 = *(b + k - 2) / (*(ap + km1k - 1));
const double denom = ak * akm1 - 1.0;
*(b + k - 1) = (akm1 * bk - bkm1) / denom;
*(b + k - 2) = (ak * bkm1 - bk) / denom;
k -= 2;
ik -= (2 * k + 1);
}
}
{
k = 1;
ik = 0;
while (k <= *n)
{
if (*(kpvt + k - 1) >= 0)
{
if (k != 1)
{
itmp = k - 1;
*(b + k - 1) += sdot(&itmp, (ap + ik), &one, b, &oneb);
kp = *(kpvt + k - 1);
if (kp != k)
{
temp = *(b + k - 1);
*(b + k - 1) = *(b + kp - 1);
*(b + kp - 1) = temp;
}
}
ik += k;
k++;
}
else
{
if (k != 1)
{
itmp = k - 1;
*(b + k - 1) += sdot(&itmp, (ap + ik), &one, b, &oneb);
const int ikp1 = ik + k;
*(b + k) += sdot(&itmp, (ap + ikp1), &one, b, &oneb);
kp = abs(*(kpvt + k - 1));
if (kp != k)
{
temp = *(b + k - 1);
*(b + k - 1) = *(b + kp - 1);
*(b + kp - 1) = temp;
}
}
ik += (2 * k + 1);
k += 2;
}
}
}
}
/************************************************************************/
/* saxpy */
/* */
/* Constant times a vector plus a vector */
/* Y = A*X + Y */
/* uses enrolled loops for increments equal to 1 */
/* */
/* IN */
/* n : length of vector X and Y (integer) */
/* sa : real constant A (double) */
/* sx : vector X (double) */
/* incx : increment for X (integer) */
/* sy : vector Y (double) */
/* incy : increment for Y (integer) */
/* */
/* OUT */
/* sy : the result vector A*X + Y (double) */
/* */
/************************************************************************/
void saxpy(const int* n, const double* sa, const double* sx, const int* incx, double* sy, const int* incy)
{
int i;
if (*n > 0)
{
if (*sa != 0)
{
if ((*incx != 1) || (*incy != 1))
{
int ix = 1;
int iy = 1;
if (*incx < 0) { ix = (1 - *n) * (*incx) + 1; }
if (*incy < 0) { iy = (1 - *n) * (*incy) + 1; }
for (i = 1; i <= *n; ++i)
{
*(sy + iy - 1) += *sa * (*(sx + ix - 1));
ix += *incx;
iy += *incy;
}
}
else
{
int ok = 1;
int m = int(fmod(double(*n), 4.0));
if (m != 0)
{
for (i = 1; i <= m; ++i) { *(sy + i - 1) += *sa * (*(sx + i - 1)); }
if (*n < 4) { ok = 0; }
}
if (ok == 1)
{
for (i = ++m; i <= *n; i += 4)
{
*(sy + i - 1) += *sa * (*(sx + i - 1));
*(sy + i) += *sa * (*(sx + i));
*(sy + i + 1) += *sa * (*(sx + i + 1));
*(sy + i + 2) += *sa * (*(sx + i + 2));
}
}
}
}
}
}
/************************************************************************/
/* sdot */
/* */
/* Forms the dot product of 2 vectors */
/* = X.Y */
/* uses enrolled loops for increments equal to 1 */
/* */
/* IN */
/* n : length of vector X and Y (integer) */
/* sx : vector X (double) */
/* incx : increment for X (integer) */
/* sy : vector Y (double) */
/* incy : increment for Y (integer) */
/* */
/* OUT */
/* function value : the result value X.Y (double) */
/* */
/************************************************************************/
double sdot(const int* n, const double* sx, const int* incx, const double* sy, const int* incy)
{
int i;
double stemp = 0.0;
if (*n > 0)
{
if ((*incx != 1) || (*incy != 1))
{
int ix = 1;
int iy = 1;
if (*incx < 0) { ix = (1 - *n) * (*incx) + 1; }
if (*incy < 0) { iy = (1 - *n) * (*incy) + 1; }
for (i = 1; i <= *n; ++i)
{
stemp += *(sx + ix - 1) * (*(sy + iy - 1));
ix += *incx;
iy += *incy;
}
}
else
{
int ok = 1;
int m = int(fmod(double(*n), 5.0));
if (m != 0)
{
for (i = 1; i <= m; ++i) { stemp += *(sx + i - 1) * (*(sy + i - 1)); }
if (*n < 5) { ok = 0; }
}
if (ok == 1)
{
for (i = ++m; i <= *n; i += 5)
{
stemp += *(sx + i - 1) * (*(sy + i - 1)) + *(sx + i) * (*(sy + i))
+ *(sx + i + 1) * (*(sy + i + 1)) + *(sx + i + 2) * (*(sy + i + 2))
+ *(sx + i + 3) * (*(sy + i + 3));
}
}
}
}
return stemp;
}
/************************************************************************/
/* sswap */
/* */
/* Interchanges 2 vectors X and Y */
/* uses enrolled loops for increments equal to 1 */
/* */
/* IN */
/* n : length of vector X and Y (integer) */
/* sx : vector X (double) */
/* incx : increment for X (integer) */
/* sy : vector Y (double) */
/* incy : increment for Y (integer) */
/* */
/* OUT */
/* sx : vector Y (double) */
/* sy : vector X (double) */
/* */
/************************************************************************/
void sswap(const int* n, double* sx, const int* incx, double* sy, const int* incy)
{
double stemp;
int i;
if (*n > 0)
{
if ((*incx != 1) || (*incy != 1))
{
int ix = 1;
int iy = 1;
if (*incx < 0) { ix = (1 - *n) * (*incx) + 1; }
if (*incy < 0) { iy = (1 - *n) * (*incy) + 1; }
for (i = 1; i <= *n; ++i)
{
stemp = *(sx + ix - 1);
*(sx + ix - 1) = *(sy + iy - 1);
*(sy + iy - 1) = stemp;
ix += *incx;
iy += *incy;
}
}
else
{
int ok = 1;
int m = int(fmod(double(*n), 3.0));
if (m != 0)
{
for (i = 1; i <= m; ++i)
{
stemp = *(sx + i - 1);
*(sx + i - 1) = *(sy + i - 1);
*(sy + i - 1) = stemp;
}
if (*n < 3) { ok = 0; }
}
if (ok == 1)
{
for (i = ++m; i <= *n; i += 3)
{
stemp = *(sx + i - 1);
*(sx + i - 1) = *(sy + i - 1);
*(sy + i - 1) = stemp;
stemp = *(sx + i);
*(sx + i) = *(sy + i);
*(sy + i) = stemp;
stemp = *(sx + i + 1);
*(sx + i + 1) = *(sy + i + 1);
*(sy + i + 1) = stemp;
}
}
}
}
}
/************************************************************************/
/* isamax */
/* */
/* Finds the index of element having max absolute value in vector X */
/* */
/* IN */
/* n : length of vector X (integer) */
/* sx : vector X (double) */
/* incx : increment for X (integer) */
/* */
/* OUT */
/* function value : index of max absolute value of X (integer) */
/* */
/************************************************************************/
int isamax(const int* n, const double* sx, const int* incx)
{
double smax;
int i;
int ida = 0;
if (*n < 1) { return ida; }
ida = 1;
if (*n == 1) { return ida; }
if (*incx != 1)
{
int ix = 1;
smax = fabs(*sx);
ix += *incx;
for (i = 2; i <= *n; ++i)
{
if (fabs(*(sx + ix - 1)) > smax)
{
ida = i;
smax = fabs(*(sx + ix - 1));
}
ix += *incx;
}
}
else
{
smax = fabs(*sx);
for (i = 2; i <= *n; ++i)
{
if (fabs(*(sx + i - 1)) > smax)
{
ida = i;
smax = fabs(*(sx + i - 1));
}
}
}
return ida;
}
@@ -0,0 +1,11 @@
/*************************************************************************************/
/* linpack.h */
/*************************************************************************************/
/*************************************************************************************/
/* Linpack subroutines */
/*************************************************************************************/
#pragma once
void sspfa(double* ap, const int* n, int* kpvt, int* info);
void sspsl(double* ap, const int* n, const int* kpvt, double* b);
@@ -0,0 +1,267 @@
#include "ovpCAlgorithmSphericalSplineInterpolation.h"
//INSERM lib
#include "spline_sph.h"
#include <cfloat> //DBL_MAX
#include <cstdio>
#include <cstring>
#include <sstream>
namespace OpenViBE {
namespace Plugins {
namespace Test {
bool CAlgorithmSphericalSplineInterpolation::initialize()
{
m_firstProcess = true;
m_coords.clear();
m_coordsPtr.clear();
m_splineCoefs.clear();
m_laplacianCoefs.clear();
ip_splineOrder.initialize(getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SplineOrder));
ip_nControlPoints.initialize(getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCount));
ip_controlPointsCoords.initialize(getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCoordinates));
ip_controlPointsValues.initialize(getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsValues));
ip_samplePointsCoords.initialize(getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SamplePointsCoordinates));
op_samplePointsValues.initialize(getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_SamplePointsValues));
op_samplePointsValues->setDimensionCount(1);
op_minSamplePointValue.initialize(getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MinSamplePointValue));
op_maxSamplePointValue.initialize(getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MaxSamplePointValue));
return true;
}
bool CAlgorithmSphericalSplineInterpolation::uninitialize()
{
ip_splineOrder.uninitialize();
ip_nControlPoints.uninitialize();
ip_controlPointsCoords.uninitialize();
ip_controlPointsValues.uninitialize();
ip_samplePointsCoords.uninitialize();
op_samplePointsValues.uninitialize();
op_minSamplePointValue.uninitialize();
op_maxSamplePointValue.uninitialize();
m_coords.clear();
m_coordsPtr.clear();
m_splineCoefs.clear();
m_laplacianCoefs.clear();
return true;
}
bool CAlgorithmSphericalSplineInterpolation::process()
{
if (m_firstProcess)
{
//store coords as doubles
m_coords.resize(3 * size_t(ip_nControlPoints));
//set up matrix of pointers to double coords matrix
m_coordsPtr.resize(size_t(ip_nControlPoints));
//fill both matrices
for (size_t i = 0; i < size_t(ip_nControlPoints); ++i)
{
const size_t id = 3 * i;
m_coords[id] = double((*ip_controlPointsCoords)[id]);
m_coords[id + 1] = double((*ip_controlPointsCoords)[id + 1]);
m_coords[id + 2] = double((*ip_controlPointsCoords)[id + 2]);
m_coordsPtr[i] = id + m_coords.data();
}
m_firstProcess = false;
}
//do we want to precompute tables?
if (isInputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_PrecomputeTables))
{
//compute cos/sin values used in spline polynomias
const int result = SplineTables(int(ip_splineOrder), m_pot.data(), m_scd.data());
if (result != 0)
{
getLogManager() << Kernel::LogLevel_ImportantWarning << "Spline tables precomputation failed!\n";
activateOutputTrigger(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error, true);
}
}
if (isInputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeSplineCoefs))
{
if (m_splineCoefs.empty() && size_t(ip_nControlPoints) != 0) { m_splineCoefs.resize(size_t(ip_nControlPoints) + 1); }
//compute spline ponderation coefficients using spline values
//FIXME : have a working copy of control points values stored as doubles?
const int result = SplineCoef(int(ip_nControlPoints), m_coordsPtr.data(), ip_controlPointsValues->getBuffer(), m_pot.data(), m_splineCoefs.data());
if (result != 0)
{
getLogManager() << Kernel::LogLevel_ImportantWarning << "Spline coefficients computation failed!\n";
const Kernel::ELogLevel level = Kernel::LogLevel_Debug;
getLogManager() << level << "CtrlPointsCount = " << int(ip_nControlPoints) << "\n";
const auto size = size_t(ip_nControlPoints);
std::stringstream ss;
ss.fill('0');
ss.precision(1);
ss << "CtrlPointsCoords = ";
for (size_t i = 0; i < size; ++i) { ss << std::fixed << "[" << m_coordsPtr[i][0] << " " << m_coordsPtr[i][1] << " " << m_coordsPtr[i][2] << "] "; }
ss << "\n";
getLogManager() << level << ss.str();
ss.str("CtrlPointsValues = ");
for (size_t i = 0; i < size; ++i) { ss << std::fixed << *(ip_controlPointsValues->getBuffer() + i) << " "; }
ss << "\n";
getLogManager() << level << ss.str();
ss.str("Spline Coeffs = ");
for (size_t i = 0; i <= size; ++i) { ss << std::fixed << m_splineCoefs[i] << " "; }
ss << "\n";
getLogManager() << level << ss.str();
ss.str("PotTable coeffs = ");
for (size_t i = 0; i < 10; ++i) { ss << std::fixed << m_pot[i] << " "; }
ss << " ... ";
for (size_t i = 2001; i < 2004; ++i) { ss << std::fixed << m_pot[i] << " "; }
ss << "\n";
getLogManager() << level << ss.str();
activateOutputTrigger(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error, true);
}
}
if (isInputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeLaplacianCoefs))
{
if (m_laplacianCoefs.empty() && size_t(ip_nControlPoints) != 0) { m_laplacianCoefs.resize(size_t(ip_nControlPoints) + 1); }
//compute spline ponderation coefficients using spline values
//FIXME : have a working copy of control points values stored as doubles?
const int result = SplineCoef(int(ip_nControlPoints), m_coordsPtr.data(), ip_controlPointsValues->getBuffer(), m_pot.data(),
m_laplacianCoefs.data());
m_laplacianCoefs[int(ip_nControlPoints)] = 0;
if (result != 0)
{
getLogManager() << Kernel::LogLevel_ImportantWarning << "Laplacian coefficients computation failed!\n";
activateOutputTrigger(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error, true);
}
}
if (isInputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateSpline))
{
bool ok = true;
//ensure we got enough storage space for interpolated values
if (op_samplePointsValues->getDimensionSize(0) != ip_samplePointsCoords->getDimensionSize(0))
{
op_samplePointsValues->setDimensionSize(0, ip_samplePointsCoords->getDimensionSize(0));
}
//compute interpolated values using spline
double* sampleValue = static_cast<double*>(op_samplePointsValues->getBuffer());
op_minSamplePointValue = +DBL_MAX;
op_maxSamplePointValue = -DBL_MAX;
for (size_t i = 0; i < ip_samplePointsCoords->getDimensionSize(0); i++, sampleValue++)
{
#if defined TARGET_OS_Windows
#ifndef NDEBUG
if (_finite(*(ip_samplePointsCoords->getBuffer() + 3 * i)) == 0 ||
_finite(*(ip_samplePointsCoords->getBuffer() + 3 * i + 1)) == 0 ||
_finite(*(ip_samplePointsCoords->getBuffer() + 3 * i + 2)) == 0) //tests whether a double is infinite or a NaN
{
getLogManager() << Kernel::LogLevel_ImportantWarning << "Bad interpolation point coordinates !\n";
getLogManager() << Kernel::LogLevel_ImportantWarning << *(ip_samplePointsCoords->getBuffer() + 3 * i) << "\n";
getLogManager() << Kernel::LogLevel_ImportantWarning << *(ip_samplePointsCoords->getBuffer() + 3 * i + 1) << "\n";
getLogManager() << Kernel::LogLevel_ImportantWarning << *(ip_samplePointsCoords->getBuffer() + 3 * i + 2) << "\n";
ok = false;
}
#endif
#endif
*sampleValue = SplineInterp(int(ip_nControlPoints), //number of fixed values
m_coordsPtr.data(), //coordinates of fixed values
m_pot.data(), //sin/cos table for spline
m_splineCoefs.data(), //spline coefficients
*(ip_samplePointsCoords->getBuffer() + 3 * i),
*(ip_samplePointsCoords->getBuffer() + 3 * i + 1),
*(ip_samplePointsCoords->getBuffer() + 3 * i + 2) //coordinate where to interpolate
);
#if defined TARGET_OS_Windows
#ifndef NDEBUG
if (_finite(*sampleValue) == 0) //tests whether a double is infinite or a NaN
{
getLogManager() << Kernel::LogLevel_ImportantWarning << "Interpolation fails !\n";
getLogManager() << Kernel::LogLevel_ImportantWarning << *(ip_samplePointsCoords->getBuffer() + 3 * i) << "\n";
getLogManager() << Kernel::LogLevel_ImportantWarning << *(ip_samplePointsCoords->getBuffer() + 3 * i + 1) << "\n";
getLogManager() << Kernel::LogLevel_ImportantWarning << *(ip_samplePointsCoords->getBuffer() + 3 * i + 2) << "\n";
ok = false;
break;
}
#endif
#endif
if (*sampleValue < op_minSamplePointValue) { op_minSamplePointValue = *sampleValue; }
if (*sampleValue > op_maxSamplePointValue) { op_maxSamplePointValue = *sampleValue; }
}
if (!ok) { activateOutputTrigger(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error, true); }
}
else if (isInputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateLaplacian))
{
const bool ok = true;
//ensure we got enough storage space for interpolated values
if (op_samplePointsValues->getDimensionSize(0) != ip_samplePointsCoords->getDimensionSize(0))
{
op_samplePointsValues->setDimensionSize(0, ip_samplePointsCoords->getDimensionSize(0));
}
//compute interpolated values using spline
auto* sampleValue = static_cast<double*>(op_samplePointsValues->getBuffer());
op_minSamplePointValue = +DBL_MAX;
op_maxSamplePointValue = -DBL_MAX;
for (size_t i = 0; i < ip_samplePointsCoords->getDimensionSize(0); i++, sampleValue++)
{
*sampleValue = SplineInterp(int(ip_nControlPoints), //number of fixed values
m_coordsPtr.data(), //coordinates of fixed values
m_scd.data(), //sin/cos table for laplacian
m_laplacianCoefs.data(), //laplacian coefficients
*(ip_samplePointsCoords->getBuffer() + 3 * i),
*(ip_samplePointsCoords->getBuffer() + 3 * i + 1),
*(ip_samplePointsCoords->getBuffer() + 3 * i + 2)); //coordinate where to interpolate
/***************************************************************************/
/*** Units : potential values being very often expressed as micro-Volts ***/
/*** SCD values should be multiplied by a scaling factor ***/
/*** to get nano-Amperes/m3 ***/
/*** Since the output of SplineInterp corresponds to the ***/
/*** Laplacian operator only, SCD are obtained with a scaling ***/
/*** factor equal to 10-3 * sigma / (R*R) ***/
/*** with sigma = conductivity of the scalp = 0.45 Siemens/m ***/
/*** and R = radius of the spherical head = 0.09 m ***/
/***************************************************************************/
*sampleValue = *sampleValue * (0.001 * 0.45 / 0.09 / 0.09);
if (*sampleValue < op_minSamplePointValue) { op_minSamplePointValue = *sampleValue; }
if (*sampleValue > op_maxSamplePointValue) { op_maxSamplePointValue = *sampleValue; }
}
if (!ok) { activateOutputTrigger(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error, true); }
}
return true;
}
} // namespace Test
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,106 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <array>
namespace OpenViBE {
namespace Plugins {
namespace Test {
class CAlgorithmSphericalSplineInterpolation final : public Toolkit::TAlgorithm<IAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_SphericalSplineInterpolation)
protected:
//input parameters
//----------------
Kernel::TParameterHandler<int64_t> ip_splineOrder;
Kernel::TParameterHandler<int64_t> ip_nControlPoints;
Kernel::TParameterHandler<CMatrix*> ip_controlPointsCoords;
Kernel::TParameterHandler<CMatrix*> ip_controlPointsValues;
Kernel::TParameterHandler<CMatrix*> ip_samplePointsCoords;
//output parameters
//-----------------
Kernel::TParameterHandler<CMatrix*> op_samplePointsValues;
Kernel::TParameterHandler<double> op_minSamplePointValue;
Kernel::TParameterHandler<double> op_maxSamplePointValue;
//internal data
//-------------
bool m_firstProcess = true;
std::vector<double> m_coords;
std::vector<double*> m_coordsPtr;
std::vector<double> m_splineCoefs;
std::vector<double> m_laplacianCoefs;
std::array<double, 2004> m_scd{};
std::array<double, 2004> m_pot{};
};
class CAlgorithmSphericalSplineInterpolationDesc final : public IAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Spherical spline interpolation"); }
CString getAuthorName() const override { return CString("Vincent Delannoy"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Interpolates potentials/laplacians using a spherical spline"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Algorithm/Signal processing"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-designer"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_SphericalSplineInterpolation; }
IPluginObject* create() override { return new CAlgorithmSphericalSplineInterpolation(); }
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
{
//input parameters
prototype.addInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SplineOrder, "Spline order", Kernel::ParameterType_Integer);
prototype.addInputParameter(
OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCount, "Number of values", Kernel::ParameterType_Integer);
prototype.addInputParameter(
OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCoordinates, "Values coordinates", Kernel::ParameterType_Matrix);
prototype.addInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsValues, "Values", Kernel::ParameterType_Matrix);
prototype.addInputParameter(
OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SamplePointsCoordinates, "Coordinates where to interpolate values",
Kernel::ParameterType_Matrix);
//input triggers
prototype.addInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_PrecomputeTables, CString("Precomputation"));
prototype.addInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeSplineCoefs, CString("Spline coefficients computation"));
prototype.addInputTrigger(
OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeLaplacianCoefs, CString("Laplacian coefficients computation"));
prototype.addInputTrigger(
OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateSpline, CString("Interpolation using spline coefficients"));
prototype.addInputTrigger(
OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateLaplacian, CString("Interpolation using laplacian coefficients"));
//output parameters
prototype.addOutputParameter(
OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_SamplePointsValues, "Interpolated values", Kernel::ParameterType_Matrix);
prototype.addOutputParameter(
OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MinSamplePointValue, "Min interpolated value", Kernel::ParameterType_Float);
prototype.addOutputParameter(
OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MaxSamplePointValue, "Max interpolated value", Kernel::ParameterType_Float);
return true;
}
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_SphericalSplineInterpolationDesc)
};
} // namespace Test
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,317 @@
/*****************************************************************************/
/*****************************************************************************/
/** **/
/** spline_sph : **/
/** **/
/** SplineTables **/
/** SplineCoef **/
/** SplineInterp **/
/** **/
/** note : SplineCoef calls linpack routines (linpack.c) **/
/** **/
/*****************************************************************************/
/*****************************************************************************/
/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
/*& Libraries to include &*/
/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include "linpack.h"
#include <iostream>
/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
/*& Define &*/
/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
#define imin(x,y) (((x) < (y)) ? (x) : (y))
/**********************************************************************************/
/* SplineTables */
/* */
/* Computes the tabulated functions km(cos(gamma)) and hm(cos(gamma)) */
/* for cos(gamma) varying from -1 to 1 (see Patent, columns 2 and 6) */
/* km and hm consist in series of Legendre polynomials */
/* km and hm are used for potential and SCD interpolation respectively */
/* m is the order of the spline interpolation function */
/* */
/* IN */
/* order : spline order m (integer) */
/* */
/* OUT */
/* pot_table : vector of tabulated km (array of 2004 double) */
/* scd_table : vector of tabulated hm (array of 2004 double) */
/* function value : return code (integer) */
/* = 0 normal value */
/* = -1 error */
/* */
/* Note : this function should be called once at the beginning of the program */
/* */
/**********************************************************************************/
int SplineTables(const int order, double* pot, double* scd)
{
if (order <= 2)
{
std::cout << "spline_table error : spline order <= 2\n";
return -1;
}
double cnpn, fn;
int j, n;
/*===========================================================*/
/* Estimate the number of terms for the Legendre series */
/* to have an error lower than 1e-10 */
/*===========================================================*/
double dexp = 10. / float(2 * order - 2);
const int kv = imin(400, int(pow(10.0, dexp) - 1.0));
double fsv = 1.0;
if (int(fmod(double(kv), 2.0)) == 1) { fsv = -1.0; }
dexp = 10. / float(2 * order - 4);
const int kc = imin(400, int(pow(10.0, dexp) - 1.0));
double fsc = 1.0;
if (int(fmod(double(kc), 2.0)) == 1) { fsc = -1.0; }
double* c = static_cast<double*>(malloc(sizeof(double) * kc));
double* p = static_cast<double*>(malloc(sizeof(double) * kc));
/*=========================*/
/* Coefficient computation */
/*=========================*/
double cn = 1.0;
for (j = 1; j < order; ++j) { cn /= 2.0; }
c[0] = cn * 3.0;
for (n = 2; n <= kc; ++n)
{
fn = double(n);
const double cx = (fn - 1.0) / (fn + 1.0);
for (j = 1; j < order; ++j) { cn *= cx; }
c[n - 1] = (2.0 * fn + 1.0) * cn;
}
/*========================*/
/* Table generation */
/*========================*/
for (int ig = 0; ig <= 1000; ++ig)
{
/*-------------------------*/
/* Pn polynomial */
/*-------------------------*/
double gamma = double(ig) / 1000.0;
gamma = 1.0 - gamma;
double p0 = 1.0;
double p1 = gamma;
p[0] = p1;
for (n = 2; n <= kc; ++n)
{
fn = double(n);
const double usfn = 1.0 / fn;
const double pn = (2.0 - usfn) * gamma * p1 - (1.0 - usfn) * p0;
p0 = p1;
p1 = pn;
p[n - 1] = pn;
}
/*-----------------------*/
/* pot_table computation */
/*-----------------------*/
double s1 = 0.0;
double s2 = 0.0;
double fs = fsv;
for (n = kv; n >= 1; n--)
{
fn = double(n);
cnpn = c[n - 1] * p[n - 1] / (fn * (fn + 1.0));
s1 += cnpn;
s2 += fs * cnpn;
fs = -fs;
}
*(pot + 2001 - ig) = s1 * 1000.0;
*(pot + 1 + ig) = s2 * 1000.0;
/*-----------------------*/
/* scd_table computation */
/*-----------------------*/
s1 = 0.0;
s2 = 0.0;
fs = fsc;
for (n = kc; n >= 1; n--)
{
cnpn = c[n - 1] * p[n - 1];
s1 += cnpn;
s2 += fs * cnpn;
fs = -fs;
}
*(scd + 2001 - ig) = s1 * 1000.0;
*(scd + 1 + ig) = s2 * 1000.0;
}
*(pot + 2002) = *(pot + 2001);
*(scd + 2002) = *(scd + 2001);
*(pot + 2003) = *(pot + 2002);
*(scd + 2003) = *(scd + 2002);
*pot = *(pot + 1);
*scd = *(scd + 1);
free(c);
free(p);
return 0;
}
/**********************************************************************************/
/* SplineCoef */
/* */
/* Computes the interpolation coefficients P=(p1,p2,...,pn) and q */
/* (see Patent, columns 2 and 6) */
/* */
/* IN */
/* nb_value : number of electrodes and related potential values (integer) */
/* xyz : array[nb_value] of array[3] of double */
/* X, Y, Z electrode coordinates on a spherical surface */
/* values : array[nb_value] of double */
/* potential values at the electrode locations */
/* table : array[2004] of double */
/* tabulated function km (array pot_table computed by SplineTables */
/* */
/* OUT */
/* coef : array[nb_value + 1] of double */
/* spline coefficients P=(p1,p2,...,pn) and q */
/* function value : return code (integer) */
/* = 0 normal value */
/* = -1 error */
/* */
/* Note : this function should be called once for a given set of electrodes */
/* and a set of potential values */
/* */
/**********************************************************************************/
int SplineCoef(const int n, double** xyz, const double* values, const double* table, double* coef)
{
int i, info, itmp;
/*=========================================*/
/* allocation of temporary arrays */
/*=========================================*/
double* p_mat_a = static_cast<double*>(malloc(sizeof(double) * ((n + 1) * (n + 2)) / 2));
if (p_mat_a == nullptr)
{
std::cout << "SplineCoef error : allocation p_mat_a\n";
return (-1);
}
int* p_iwork = static_cast<int*>(malloc(sizeof(int) * (n + 1)));
if (p_iwork == nullptr)
{
std::cout << "SplineCoef error : allocation p_iwork\n";
return (-1);
}
/*================================*/
/* Initialization of matrix A */
/*================================*/
const int l0 = ((n + 1) * (n)) / 2;
for (i = l0; i < l0 + n; ++i) { *(p_mat_a + i) = 1.0; }
*(p_mat_a + i) = 0.0;
/*=========================*/
/* computation of matrix A */
/*=========================*/
int ih = 0;
for (int j = 0; j < n; ++j)
{
const double xj = xyz[j][0];
const double yj = xyz[j][1];
const double zj = xyz[j][2];
for (i = 0; i < j; ++i)
{
const double t1 = xyz[i][0] - xj;
const double t2 = xyz[i][1] - yj;
const double t3 = xyz[i][2] - zj;
const double tp = (t1 * t1 + t2 * t2 + t3 * t3) / 2.0;
double fgam = (1.0 - tp) * 1000.0 + 1002.0;
const int igam = int(fgam);
fgam -= float(igam);
const double v1 = *(table + igam - 1);
const double v2 = *(table + igam) - v1;
*(p_mat_a + ih++) = v2 * fgam + v1;
}
*(p_mat_a + ih++) = *(table + 2001);
}
/*=================================*/
/* Triangularization of matrix A */
/*=================================*/
itmp = n + 1;
sspfa(p_mat_a, &itmp, p_iwork, &info);
if (info != 0)
{
printf("SplineCoef error : triangularization of matrix a (sspfa : %d) \n", info);
return (-1);
}
/*=======================================================*/
/* Coefficient computation (solving a triangular system) */
/*=======================================================*/
for (i = 0; i < n; ++i) { coef[i] = values[i]; }
coef[n] = 0.0;
sspsl(p_mat_a, &itmp, p_iwork, coef);
free(p_mat_a);
free(p_iwork);
return 0;
}
/**********************************************************************************/
/* SplineInterp */
/* */
/* Computes the interpolated potential or SCD value at a location on the sphere */
/* (see Patent, columns 2, 4, 7 and 9) */
/* */
/* IN */
/* nb_value : number of electrodes and related potential values (integer) */
/* xyz : array[nb_value] of array[3] of double */
/* X, Y, Z electrode coordinates on a spherical surface (radius = 1) */
/* table : array[2004] of double */
/* tabulated function computed by SplineTables */
/* use pot_table (km) for potential interpolation */
/* use scd_table (hm) for SCD interpolation */
/* coef : array[nb_value + 1] of double */
/* spline coefficients P=(p1,p2,...,pn) and q */
/* array coef computed by SplineCoef */
/* IMPORTANT : coef[nb_value] should be set to 0.0 for computing */
/* the interpolated SCD (this corresponds to q=0) */
/* xx, yy, zz : double */
/* X, Y, Z coordinates of a point on a spherical surface (radius = 1)*/
/* where to compute the interpolated value */
/* */
/* OUT */
/* function value : interpolated potential or SCD value (double) */
/* */
/* Note : this function should be called for every point of the spherical surface */
/* where the potential or the SCD value should be estimated */
/* */
/**********************************************************************************/
double SplineInterp(const int n, double** xyz, const double* table, const double* coef, const double xx, const double yy, const double zz)
{
double ffn = coef[n];
int k = 0;
for (int i = 0; i < n; ++i)
{
const double t1 = xx - xyz[i][0];
const double t2 = yy - xyz[i][1];
const double t3 = zz - xyz[i][2];
const double t123 = (t1 * t1 + t2 * t2 + t3 * t3) / 2.;
double fgam = (1.0 - t123) * 1000. + 1002.;
const int igam = int(fgam);
fgam -= double(igam);
const double v1 = table[igam - 1];
const double v2 = table[igam] - v1;
ffn += coef[k] * (v2 * fgam + v1);
k++;
}
return ffn;
}
@@ -0,0 +1,12 @@
/*************************************************************************************/
/* spline_sph.h */
/*************************************************************************************/
/*************************************************************************************/
/* spline subroutines */
/*************************************************************************************/
#pragma once
int SplineTables(int order, double* pot, double* scd);
int SplineCoef(int n, double** xyz, const double* values, const double* table, double* coef);
double SplineInterp(int n, double** xyz, const double* table, const double* coef, double xx, double yy, double zz);
@@ -0,0 +1,364 @@
#include "ovpCBoxAlgorithmMatrixDisplay.h"
#include <string>
#include <sstream>
#include <iomanip>
#include <cstdlib>
#include <cmath>
#include <visualization-toolkit/ovvizColorGradient.h>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
static void ShowValuesToggleButtonCB(GtkToggleToolButton* button, gpointer data)
{
auto* display = reinterpret_cast<CBoxAlgorithmMatrixDisplay*>(data);
display->m_ShowValues = (gtk_toggle_tool_button_get_active(button) != 0);
}
static void ShowColorsToggleButtonCB(GtkToggleToolButton* button, gpointer data)
{
auto* display = reinterpret_cast<CBoxAlgorithmMatrixDisplay*>(data);
display->m_ShowColors = (gtk_toggle_tool_button_get_active(button) != 0);
display->resetColors();
}
bool CBoxAlgorithmMatrixDisplay::resetColors()
{
if (m_ShowColors)
{
//we take colors from cache and re-put it in the table
for (auto it = m_eventBoxCache.begin(); it != m_eventBoxCache.end(); ++it) { gtk_widget_modify_bg((*it).first, GTK_STATE_NORMAL, &(*it).second); }
}
else
{
for (auto it = m_eventBoxCache.begin(); it != m_eventBoxCache.end(); ++it)
{
GdkColor white;
white.red = 65535;
white.green = 65535;
white.blue = 65535;
gtk_widget_modify_bg((*it).first, GTK_STATE_NORMAL, &white);
}
}
return true;
}
bool CBoxAlgorithmMatrixDisplay::initialize()
{
//targets decoder
iMatrix = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
iMatrix->initialize();
//IO for the targets MemoryBuffer -> StreamedMatrix
ip_buffer.initialize(iMatrix->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
op_matrix.initialize(iMatrix->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix));
//widgets
m_mainWidgetInterface = gtk_builder_new();
m_toolbarWidgetInterface = gtk_builder_new();
// glade_xml_new(Directories::getDataDir() + "/plugins/simple-visualization/openvibe-simple-visualization-MatrixDisplay.ui", "matrix-display-table", nullptr);
// glade_xml_new(Directories::getDataDir() + "/plugins/simple-visualization/openvibe-simple-visualization-MatrixDisplay.ui", "matrix-display-toolbar", nullptr);
gtk_builder_add_from_file(m_mainWidgetInterface, Directories::getDataDir() + "/plugins/simple-visualization/openvibe-simple-visualization-MatrixDisplay.ui",
nullptr);
gtk_builder_add_from_file(m_toolbarWidgetInterface,
Directories::getDataDir() + "/plugins/simple-visualization/openvibe-simple-visualization-MatrixDisplay.ui", nullptr);
gtk_builder_connect_signals(m_mainWidgetInterface, nullptr);
gtk_builder_connect_signals(m_toolbarWidgetInterface, nullptr);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "show-values-toggle-button")), "toggled",
G_CALLBACK(ShowValuesToggleButtonCB), this);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "show-colors-toggle-button")), "toggled",
G_CALLBACK(ShowColorsToggleButtonCB), this);
g_signal_connect(G_OBJECT(gtk_builder_get_object(m_toolbarWidgetInterface, "matrix-display-toolbar")), "delete_event", G_CALLBACK(gtk_widget_hide),
nullptr);
m_mainWidget = GTK_WIDGET(gtk_builder_get_object(m_mainWidgetInterface, "matrix-display-table"));
m_toolbarWidget = GTK_WIDGET(gtk_builder_get_object(m_toolbarWidgetInterface, "matrix-display-toolbar"));
if (!this->canCreatePluginObject(OVP_ClassId_Plugin_VisualizationCtx))
{
this->getLogManager() << Kernel::LogLevel_Error << "Visualization framework is not loaded" << "\n";
return false;
}
m_visualizationCtx = dynamic_cast<VisualizationToolkit::IVisualizationContext*>(this->createPluginObject(OVP_ClassId_Plugin_VisualizationCtx));
m_visualizationCtx->setWidget(*this, m_mainWidget);
m_visualizationCtx->setToolbar(*this, m_toolbarWidget);
m_ShowValues = (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(gtk_builder_get_object(m_toolbarWidgetInterface, "show-values-toggle-button"))) !=
0);
m_ShowColors = (gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(gtk_builder_get_object(m_toolbarWidgetInterface, "show-colors-toggle-button"))) !=
0);
CString gradientSetting;
getBoxAlgorithmContext()->getStaticBoxContext()->getSettingValue(0, gradientSetting);
VisualizationToolkit::ColorGradient::parse(m_colorGradient, gradientSetting);
CString gradientStepsSetting;
getBoxAlgorithmContext()->getStaticBoxContext()->getSettingValue(1, gradientStepsSetting);
m_gradientSteps = strtol(gradientStepsSetting, nullptr, 10);
VisualizationToolkit::ColorGradient::interpolate(m_interpolatedColorGardient, m_colorGradient, m_gradientSteps);
m_max = 0;
m_min = 0;
CString symetricMinMaxSetting;
getBoxAlgorithmContext()->getStaticBoxContext()->getSettingValue(2, symetricMinMaxSetting);
m_symetricMinMax = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
CString realTimeMinMaxSetting;
getBoxAlgorithmContext()->getStaticBoxContext()->getSettingValue(3, realTimeMinMaxSetting);
m_realTimeMinMax = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
return true;
}
bool CBoxAlgorithmMatrixDisplay::uninitialize()
{
op_matrix.uninitialize();
ip_buffer.uninitialize();
//decoders
iMatrix->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*iMatrix);
//widgets
g_object_unref(m_toolbarWidgetInterface);
m_toolbarWidgetInterface = nullptr;
g_object_unref(m_mainWidgetInterface);
m_mainWidgetInterface = nullptr;
this->releasePluginObject(m_visualizationCtx);
return true;
}
bool CBoxAlgorithmMatrixDisplay::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmMatrixDisplay::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
iMatrix->process();
if (iMatrix->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedHeader))
{
//header received
//adding the event to the window
GtkTable* table = GTK_TABLE(gtk_builder_get_object(m_mainWidgetInterface, "matrix-display-table"));
guint nRow, nCol;
if (op_matrix->getDimensionCount() == 1)
{
//getLogManager() << Kernel::LogLevel_Warning<< "The streamed matrix received has 1 dimensions (found "<< op_matrix->getDimensionCount() <<" dimensions)\n";
nRow = 1;
nCol = guint(op_matrix->getDimensionSize(0));
//return false;
}
else if (op_matrix->getDimensionCount() != 2)
{
getLogManager() << Kernel::LogLevel_Error << "The streamed matrix received has more than 2 dimensions (found " << op_matrix->getDimensionCount()
<<
" dimensions)\n";
return false;
}
else
{
nRow = guint(op_matrix->getDimensionSize(0));
nCol = guint(op_matrix->getDimensionSize(1));
}
gtk_table_resize(table, nRow + 1, nCol + 1);
//first line : labels
guint row = 0;
for (guint c = 1; c < nCol + 1; ++c)
{
GtkWidget* label = gtk_label_new("");
gtk_widget_set_visible(label, 1);
gtk_table_attach(table, label, c, c + 1, row, row + 1, GtkAttachOptions(GTK_EXPAND | GTK_FILL), GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
//g_object_unref(l_pGtkBuilderLabel);
const char* cstr = std::to_string(c).c_str();
gtk_label_set_label(GTK_LABEL(label), cstr);
m_columnLabelCache.emplace_back(GTK_LABEL(label), cstr);
}
//first column : labels
guint col = 0;
for (guint r = 1; r < nRow + 1; ++r)
{
GtkWidget* label = gtk_label_new("");
gtk_widget_set_visible(label, 1);
gtk_table_attach(table, label, col, col + 1, r, r + 1, GtkAttachOptions(GTK_EXPAND | GTK_FILL), GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
std::stringstream ss;
ss << char(r - 1 + int('A'));
gtk_label_set_label(GTK_LABEL(label), ss.str().c_str());
m_rowLabelCache.emplace_back(GTK_LABEL(label), ss.str().c_str());
}
for (guint r = 1; r < nRow + 1; ++r)
{
for (guint c = 1; c < nCol + 1; ++c)
{
GtkWidget* eventBox = gtk_event_box_new();
gtk_widget_set_visible(eventBox, 1);
GtkWidget* label = gtk_label_new("");
gtk_widget_set_visible(label, 1);
gtk_container_add(GTK_CONTAINER(eventBox), label);
gtk_table_attach(table, eventBox, c, c + 1, r, r + 1, GtkAttachOptions(GTK_EXPAND | GTK_FILL), GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0,
0);
GdkColor white;
white.red = 65535;
white.green = 65535;
white.blue = 65535;
gtk_widget_modify_bg(eventBox, GTK_STATE_NORMAL, &white);
m_eventBoxCache.emplace_back(eventBox, white);
gtk_label_set_label(GTK_LABEL(label), "X");
m_labelCache.emplace_back(GTK_LABEL(label), "X");
}
}
}
if (iMatrix->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
{
//buffer received
//2-dimension-matrix values
size_t nRow, nCol;
if (op_matrix->getDimensionCount() == 1)
{
nRow = 1;
nCol = op_matrix->getDimensionSize(0);
}
else
{
nRow = op_matrix->getDimensionSize(0);
nCol = op_matrix->getDimensionSize(1);
}
if (m_realTimeMinMax || // we need recompute the min max at each loop call
(m_max == 0 && m_min == 0)) // we have never computed the min max values.
{
if (op_matrix->getBufferElementCount() != 0) // if the matrix is not empty.
{
m_max = op_matrix->getBuffer()[0];
m_min = op_matrix->getBuffer()[0];
}
}
// MIN-MAX computation
for (size_t r = 0; r < nRow; ++r)
{
for (size_t c = 0; c < nCol; ++c)
{
double value = op_matrix->getBuffer()[r * nCol + c];
m_max = (value > m_max ? value : m_max);
m_min = (value < m_min ? value : m_min);
if (m_symetricMinMax)
{
double maxAbsValue = (fabs(m_max) > fabs(m_min) ? fabs(m_max) : fabs(m_min));
m_max = maxAbsValue;
m_min = -maxAbsValue;
}
}
}
for (size_t r = 0; r < nRow; ++r)
{
for (size_t c = 0; c < nCol; ++c)
{
double value = op_matrix->getBuffer()[r * nCol + c];
if (m_max != 0 || m_min != 0) // if the first value ever sent is 0, both are 0, and we dont want to divide by 0 :)
{
const size_t step = size_t(((value - m_min) / (m_max - m_min)) * (m_gradientSteps - 1));
// gtk_widget_modify_bg uses 16bit colors, the interpolated gradients gives 8bits colors.
GdkColor color;
color.red = uint16_t(m_interpolatedColorGardient[step * 4 + 1] * 65535. / 100.);
color.green = uint16_t(m_interpolatedColorGardient[step * 4 + 2] * 65535. / 100.);
color.blue = uint16_t(m_interpolatedColorGardient[step * 4 + 3] * 65535. / 100.);
if (memcmp(&(m_eventBoxCache[r * nCol + c].second), &color, sizeof(GdkColor)) != 0 && m_ShowColors)
{
gtk_widget_modify_bg(m_eventBoxCache[r * nCol + c].first, GTK_STATE_NORMAL, &color);
}
m_eventBoxCache[r * nCol + c].second = color;
std::stringstream ss;
ss << std::fixed;
ss << std::setprecision(2);
if (m_ShowValues) { ss << value; }
if (ss.str() != m_labelCache[r * nCol + c].second) { gtk_label_set_label(m_labelCache[r * nCol + c].first, ss.str().c_str()); }
m_labelCache[r * nCol + c].second = ss.str();
}
}
}
if (op_matrix->getDimensionCount() != 1)
{
//first line : labels
for (size_t c = 0; c < nCol; ++c)
{
if (m_columnLabelCache[c].second != op_matrix->getDimensionLabel(1, c) && !std::string(op_matrix->getDimensionLabel(1, c)).empty())
{
gtk_label_set_label(GTK_LABEL(m_columnLabelCache[c].first), op_matrix->getDimensionLabel(1, c));
m_columnLabelCache[c].second = op_matrix->getDimensionLabel(1, c);
}
}
//first column : labels
for (size_t r = 0; r < nRow; ++r)
{
if (m_rowLabelCache[r].second != op_matrix->getDimensionLabel(0, r) && !std::string(op_matrix->getDimensionLabel(0, r)).empty())
{
gtk_label_set_label(GTK_LABEL(m_rowLabelCache[r].first), op_matrix->getDimensionLabel(0, r));
m_rowLabelCache[r].second = op_matrix->getDimensionLabel(0, r);
}
}
}
else
{
//first line : labels
for (size_t c = 0; c < nCol; ++c)
{
if (m_columnLabelCache[c].second != op_matrix->getDimensionLabel(0, c) && !std::string(op_matrix->getDimensionLabel(0, c)).empty())
{
gtk_label_set_label(GTK_LABEL(m_columnLabelCache[c].first), op_matrix->getDimensionLabel(0, c));
m_columnLabelCache[c].second = op_matrix->getDimensionLabel(0, c);
}
}
}
}
/*if(iMatrix->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedEnd)) { }*/
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,111 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <visualization-toolkit/ovviz_all.h>
#include <gtk/gtk.h>
#include <map>
#include <string>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
class CBoxAlgorithmMatrixDisplay final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_MatrixDisplay)
protected:
// we need an algorithm to decode the EBML stream (memory buffer) into a Streamed Matrix
// for the TARGET
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmMatrixDisplay> m_decoder;
Kernel::IAlgorithmProxy* iMatrix = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<CMatrix*> op_matrix;
// Outputs: visualization in a gtk window
GtkBuilder* m_mainWidgetInterface = nullptr;
GtkBuilder* m_toolbarWidgetInterface = nullptr;
GtkWidget* m_mainWidget = nullptr;
GtkWidget* m_toolbarWidget = nullptr;
std::vector<std::pair<GtkWidget*, GdkColor>> m_eventBoxCache;
std::vector<std::pair<GtkLabel*, std::string>> m_labelCache;
std::vector<std::pair<GtkLabel*, std::string>> m_rowLabelCache;
std::vector<std::pair<GtkLabel*, std::string>> m_columnLabelCache;
CMatrix m_interpolatedColorGardient;
CMatrix m_colorGradient;
size_t m_gradientSteps = 0;
double m_max = 0;
double m_min = 0;
bool m_symetricMinMax = false;
bool m_realTimeMinMax = false;
VisualizationToolkit::IVisualizationContext* m_visualizationCtx{};
public:
bool m_ShowValues = false;
bool m_ShowColors = false;
bool resetColors();
};
class CBoxAlgorithmMatrixDisplayDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Matrix Display"); }
CString getAuthorName() const override { return CString("Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Display a streamed matrix"); }
CString getDetailedDescription() const override
{
return CString("The streamed matrix can be visualized using a table of values and/or a color gradient.");
}
CString getCategory() const override { return CString("Visualization/Basic"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-select-color"); }
CString getSoftwareComponent() const override { return CString("openvibe-designer"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_MatrixDisplay; }
IPluginObject* create() override { return new CBoxAlgorithmMatrixDisplay; }
bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Color gradient", OV_TypeId_ColorGradient, "0:2,36,58; 50:100,100,100; 100:83,17,20");
prototype.addSetting("Steps", OV_TypeId_Integer, "100");
prototype.addSetting("Symetric min/max", OV_TypeId_Boolean, "false");
prototype.addSetting("Real time min/max", OV_TypeId_Boolean, "false");
prototype.addInput("Matrix", OV_TypeId_StreamedMatrix);
// prototype.addFlag (Kernel::BoxFlag_IsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_MatrixDisplayDesc)
};
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,135 @@
#include "ovpCBoxAlgorithmTopographicMap2DDisplay.h"
#include "../algorithms/ovpCAlgorithmSphericalSplineInterpolation.h"
#include <cstdlib>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
bool CBoxAlgorithmTopographicMap2DDisplay::initialize()
{
m_hasFirstBuffer = false;
m_decoder.initialize(*this, 0);
m_interpolation = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_SphericalSplineInterpolation));
m_interpolation->initialize();
//create topographic map database
m_database = new CTopographicMapDatabase(*this, *m_interpolation);
//retrieve settings
CString interpolationValue;
getStaticBoxContext().getSettingValue(0, interpolationValue);
CString delayValue;
getStaticBoxContext().getSettingValue(1, delayValue);
//create topographic map view (handling GUI interaction)
m_view = new CTopographicMap2DView(
*m_database, EInterpolationType(getTypeManager().getEnumerationEntryValueFromName(OVP_TypeId_SphericalLinearInterpolationType, interpolationValue)),
strtod(delayValue, nullptr));
//have database notify us when new data is available
m_database->setDrawable(m_view);
//ask not to be notified when new data is available (refresh is handled separately)
m_database->setRedrawOnNewData(false);
//send widget pointers to visualisation context for parenting
GtkWidget* widget = nullptr;
GtkWidget* toolbarWidget = nullptr;
dynamic_cast<CTopographicMap2DView*>(m_view)->getWidgets(widget, toolbarWidget);
if (!this->canCreatePluginObject(OVP_ClassId_Plugin_VisualizationCtx))
{
this->getLogManager() << Kernel::LogLevel_Error << "Visualization framework is not loaded" << "\n";
return false;
}
m_visualizationCtx = dynamic_cast<VisualizationToolkit::IVisualizationContext*>(this->createPluginObject(OVP_ClassId_Plugin_VisualizationCtx));
m_visualizationCtx->setWidget(*this, widget);
m_visualizationCtx->setToolbar(*this, toolbarWidget);
return true;
}
bool CBoxAlgorithmTopographicMap2DDisplay::uninitialize()
{
m_decoder.uninitialize();
delete m_view;
m_view = nullptr;
delete m_database;
m_database = nullptr;
m_interpolation->uninitialize();
getAlgorithmManager().releaseAlgorithm(*m_interpolation);
return true;
}
bool CBoxAlgorithmTopographicMap2DDisplay::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmTopographicMap2DDisplay::processClock(Kernel::CMessageClock& /*msg*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmTopographicMap2DDisplay::process()
{
IDynamicBoxContext* context = getBoxAlgorithmContext()->getDynamicBoxContext();
//decode signal data
for (size_t i = 0; i < context->getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
if (m_decoder.isBufferReceived())
{
CMatrix* iMatrix = m_decoder.getOutputMatrix();
//do we need to recopy this for each chunk?
if (!m_hasFirstBuffer)
{
m_database->setMatrixDimensionCount(iMatrix->getDimensionCount());
for (size_t dimension = 0; dimension < iMatrix->getDimensionCount(); ++dimension)
{
m_database->setMatrixDimensionSize(dimension, iMatrix->getDimensionSize(dimension));
for (size_t entryIndex = 0; entryIndex < iMatrix->getDimensionSize(dimension); ++entryIndex)
{
m_database->setMatrixDimensionLabel(dimension, entryIndex, iMatrix->getDimensionLabel(dimension, entryIndex));
}
}
m_hasFirstBuffer = true;
}
//
if (!m_database->setMatrixBuffer(iMatrix->getBuffer(), context->getInputChunkStartTime(0, i), context->getInputChunkEndTime(0, i)))
{
return false;
}
}
}
//decode channel localisation data
for (size_t i = 0; i < context->getInputChunkCount(1); ++i)
{
const IMemoryBuffer* buf = context->getInputChunk(1, i);
m_database->decodeChannelLocalisationMemoryBuffer(buf, context->getInputChunkStartTime(1, i), context->getInputChunkEndTime(1, i));
context->markInputAsDeprecated(1, i);
}
const bool processValues = m_database->processValues();
//disable plugin upon errors
return processValues;
}
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,79 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <visualization-toolkit/ovviz_all.h>
#include "topographicMap2DDisplay/ovpCTopographicMapDatabase.h"
#include "topographicMap2DDisplay/ovpCTopographicMap2DView.h"
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
class CBoxAlgorithmTopographicMap2DDisplay final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CBoxAlgorithmTopographicMap2DDisplay() = default;
void release() override { delete this; }
uint64_t getClockFrequency() override { return uint64_t(1LL) << 37; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool processClock(Kernel::CMessageClock& msg) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_TopographicMap2DDisplay)
protected:
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTopographicMap2DDisplay> m_decoder;
VisualizationToolkit::IVisualizationContext* m_visualizationCtx = nullptr;
Kernel::IAlgorithmProxy* m_interpolation = nullptr;
CTopographicMapDatabase* m_database = nullptr;
CSignalDisplayDrawable* m_view = nullptr; //main object used for the display (contains all the GUI code)
bool m_hasFirstBuffer = false;
};
class CBoxAlgorithmTopographicMap2DDisplayDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("2D topographic map"); }
CString getAuthorName() const override { return CString("Vincent Delannoy"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("This box demonstrates how to perform spherical spline interpolation"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Visualization/Topography"); }
CString getVersion() const override { return CString("2.0"); }
CString getStockItemName() const override { return CString(GTK_STOCK_EXECUTE); }
CString getSoftwareComponent() const override { return CString("openvibe-designer"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_TopographicMap2DDisplay; }
IPluginObject* create() override { return new CBoxAlgorithmTopographicMap2DDisplay(); }
bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("Interpolation type", OVP_TypeId_SphericalLinearInterpolationType, "1");
prototype.addSetting("Delay (in s)", OV_TypeId_Float, "0");
prototype.addInput("Signal", OV_TypeId_StreamedMatrix);
prototype.addInput("Channel localization", OV_TypeId_ChannelLocalisation);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_TopographicMap2DDisplayDesc)
};
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,682 @@
#include "ovpCBufferDatabase.h"
#include <algorithm>
#include <cmath>
#include <cstring>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
CBufferDatabase::CBufferDatabase(Toolkit::TBoxAlgorithm<IBoxAlgorithm>& plugin) : m_ParentPlugin(plugin)
{
m_decoder = &m_ParentPlugin.getAlgorithmManager().getAlgorithm(
m_ParentPlugin.getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_ChannelLocalisationDecoder));
m_decoder->initialize();
m_DimSizes.fill(0);
}
CBufferDatabase::~CBufferDatabase()
{
m_decoder->uninitialize();
m_ParentPlugin.getAlgorithmManager().releaseAlgorithm(*m_decoder);
//delete all the remaining buffers
while (!m_SampleBuffers.empty())
{
delete[] m_SampleBuffers.front();
m_SampleBuffers.pop_front();
}
//delete channel localisation matrices
while (!m_channelLocalisationCoords.empty())
{
delete m_channelLocalisationCoords.front().first;
m_channelLocalisationCoords.pop_front();
}
/*while(m_oChannelLocalisationAlternateCoords.size() > 0)
{
delete[] m_oChannelLocalisationAlternateCoords.front().first;
m_oChannelLocalisationAlternateCoords.pop_front();
}*/
}
bool CBufferDatabase::decodeChannelLocalisationMemoryBuffer(const IMemoryBuffer* buffer, uint64_t startTime, uint64_t endTime)
{
//feed memory buffer to decoder
m_decoder->getInputParameter(OVP_GD_Algorithm_ChannelLocalisationDecoder_InputParameterId_MemoryBufferToDecode)->setReferenceTarget(&buffer);
//process buffer
m_decoder->process();
//copy header if needed
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_ChannelLocalisationDecoder_OutputTriggerId_ReceivedHeader))
{
//retrieve matrix header
Kernel::TParameterHandler<CMatrix*> matrix;
matrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_ChannelLocalisationDecoder_OutputParameterId_Matrix));
//copy channel labels
m_channelLocalisationLabels.resize(matrix->getDimensionSize(0));
for (size_t i = 0; i < m_channelLocalisationLabels.size(); ++i) { m_channelLocalisationLabels[i] = matrix->getDimensionLabel(0, i); }
//retrieve dynamic flag
Kernel::TParameterHandler<bool> dynamic;
dynamic.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_ChannelLocalisationDecoder_OutputParameterId_Dynamic));
m_dynamicChannelLocalisation = dynamic;
if (matrix->getDimensionSize(1) == 3)
{
m_cartesianCoords = true;
/*m_channelLocalisationCartesianCoords = &m_channelLocalisationCoords;
m_channelLocalisationSphericalCoords = &m_oChannelLocalisationAlternateCoords;*/
}
else if (matrix->getDimensionSize(1) == 2)
{
m_cartesianCoords = false;
/*m_channelLocalisationCartesianCoords = &m_oChannelLocalisationAlternateCoords;
m_channelLocalisationSphericalCoords = &m_channelLocalisationCoords;*/
}
else
{
m_ParentPlugin.getLogManager() << Kernel::LogLevel_Error
<< "Wrong size found for dimension 1 of Channel localisation header! Can't process header!\n";
return false;
}
//header information received
m_channelLocalisationHeaderReceived = true;
}
//has a chanloc buffer been received?
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_ChannelLocalisationDecoder_OutputTriggerId_ReceivedBuffer))
{
//number of buffers required to cover displayed time range
size_t maxNBuffer = 1;
//resize channel localisation queue if necessary
if (m_dynamicChannelLocalisation)
{
const uint64_t bufferDuration = endTime - startTime;
if (bufferDuration != 0)
{
maxNBuffer = size_t(ceil(m_TotalDuration / bufferDuration));
if (maxNBuffer == 0) { maxNBuffer = 1; }
}
//if new number of buffers decreased, resize list and destroy useless buffers
while (m_channelLocalisationCoords.size() > maxNBuffer)
{
delete[] m_channelLocalisationCoords.front().first;
m_channelLocalisationCoords.pop_front();
// delete[] m_oChannelLocalisationAlternateCoords.front().first;
// m_oChannelLocalisationAlternateCoords.pop_front();
m_channelLocalisationTimes.pop_front();
}
}
//retrieve coordinates matrix
Kernel::TParameterHandler<CMatrix*> matrix;
matrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_ChannelLocalisationDecoder_OutputParameterId_Matrix));
//get pointer to destination matrix
CMatrix* channelLocalisation;
//CMatrix* alternateChannelLocalisation = nullptr;
if (m_channelLocalisationCoords.size() < maxNBuffer)
{
//create a new matrix and resize it
channelLocalisation = new CMatrix();
channelLocalisation->copyDescription(*matrix);
// alternateChannelLocalisation = new CMatrix();
// TODO : resize it appropriately depending on whether it is spherical or cartesian
}
else //m_channelLocalisationCoords.size() == maxNBuffer
{
channelLocalisation = m_channelLocalisationCoords.front().first;
m_channelLocalisationCoords.pop_front();
// alternateChannelLocalisation = m_oChannelLocalisationAlternateCoords.front().first;
// m_oChannelLocalisationAlternateCoords.pop_front();
m_channelLocalisationTimes.pop_front();
}
if (channelLocalisation)
{
//copy coordinates and times
channelLocalisation->copyContent(*matrix);
m_channelLocalisationCoords.emplace_back(channelLocalisation, true);
//m_oChannelLocalisationAlternateCoords.push_back(std::pair<CMatrix*, bool>(alternateChannelLocalisation, true));
m_channelLocalisationTimes.emplace_back(startTime, endTime);
}
}
return true;
}
bool CBufferDatabase::onChannelLocalisationBufferReceived(const size_t index)
{
m_channelLocalisationCoords[index].second = false;
return true;
}
bool CBufferDatabase::isFirstChannelLocalisationBufferProcessed()
{
//at least one chanloc buffer must have been received and processed
return (!m_channelLocalisationCoords.empty()) && (!m_channelLocalisationCoords[0].second);
}
bool CBufferDatabase::adjustNumberOfDisplayedBuffers(const double time)
{
bool nBufferToDisplayChanged = false;
if (time > 0)
{
m_TotalDuration = time;
m_ovTotalDuration = 0;
m_TotalStep = 0;
}
//return if buffer length is not known yet
if (m_DimSizes[1] == 0) { return false; }
size_t newNbufferToDisplay = size_t(ceil((m_TotalDuration * m_Sampling) / m_DimSizes[1]));
//displays at least one buffer
newNbufferToDisplay = (newNbufferToDisplay == 0) ? 1 : newNbufferToDisplay;
if (newNbufferToDisplay != m_NBufferToDisplay || time <= 0)
{
m_NBufferToDisplay = newNbufferToDisplay;
nBufferToDisplayChanged = true;
//if new number of buffers decreased, resize lists and destroy useless buffers
while (m_NBufferToDisplay < m_SampleBuffers.size())
{
delete[] m_SampleBuffers.front();
m_SampleBuffers.pop_front();
m_StartTime.pop_front();
m_EndTime.pop_front();
//suppress the corresponding minmax values
for (size_t c = 0; c < m_DimSizes[0]; ++c) { m_LocalMinMaxValue[c].pop_front(); }
}
}
return nBufferToDisplayChanged;
}
size_t CBufferDatabase::getChannelCount() const { return m_DimSizes[0]; }
double CBufferDatabase::getDisplayedTimeIntervalWidth() const { return (m_NBufferToDisplay * ((m_DimSizes[1] * 1000.0) / m_Sampling)); }
void CBufferDatabase::setMatrixDimensionCount(const size_t n)
{
if (n != 2)
{
m_Error = true;
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error << "Caller tried to set a " << n
<< "-dimensional matrix. Only 2-dimensional matrices are supported (e.g. [rows X cols]).\n";
}
if (n == 1)
{
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error
<< "Note: For 1-dimensional matrices, you may try Matrix Transpose box to upgrade the stream to [N X 1] first.\n";
}
}
void CBufferDatabase::setMatrixDimensionSize(const size_t index, const size_t size)
{
if (index >= 2)
{
m_Error = true;
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error << "Tried to access dimension "
<< index << ", only 0 and 1 supported\n";
return;
}
if (m_DimSizes[index] != 0 && m_DimSizes[index] != size)
{
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error
<< "Upstream tried to change the data chunk size after the first header, this is not supported.\n";
m_Error = true;
return;
}
m_DimSizes[index] = size;
m_DimLabels[index].resize(size);
if (index == 0)
{
m_NElectrodes = m_DimSizes[index];
//resize min/max values vector
m_LocalMinMaxValue.resize(size_t(m_NElectrodes));
}
}
void CBufferDatabase::setMatrixDimensionLabel(const size_t idx1, const size_t idx2, const char* label)
{
if (m_Error) { return; }
if (idx1 >= 2)
{
m_Error = true;
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error << "Tried to access dimension " << idx1
<< ", only 0 and 1 supported\n";
return;
}
m_DimLabels[idx1][idx2] = label;
}
bool CBufferDatabase::setMatrixBuffer(const double* buffer, const uint64_t startTime, const uint64_t endTime)
{
//if an error has occurred, do nothing
if (m_Error) { return false; }
// Check for time-continuity
if (startTime < m_LastBufferEndTime && !m_WarningPrinted)
{
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Warning
<< "Your signal does not appear to be continuous in time. "
<< "Previously inserted buffer ended at " << CTime(m_LastBufferEndTime).toSeconds()
<< "s, the current starts at " << CTime(startTime).toSeconds()
<< "s. The display may be incorrect.\n";
m_WarningPrinted = true;
}
m_LastBufferEndTime = endTime;
//if this the first buffer, perform some precomputations
if (!m_HasFirstBuffer)
{
m_BufferDuration = endTime - startTime;
//test if it is equal to zero : Error
if (m_BufferDuration == 0)
{
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Warning <<
"Error : buffer start time and end time are equal : " << startTime << "\n";
m_Error = true;
return false;
}
//computes the sampling frequency for sanity checking or if the setter has not been called
const uint64_t duration = (uint64_t(1) << 32) * m_DimSizes[1];
size_t sampling = size_t(duration / m_BufferDuration);
if (sampling == 0)
{
// Complain if estimate is bad
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Warning
<< "The integer sampling frequency was estimated from the chunk size to be 0"
<< " (nSamples " << m_DimSizes[1] << " / bufferLength " << CTime(m_BufferDuration).toSeconds()
<< "s = 0). This is not supported. Forcing the rate to 1. This may lead to problems.\n";
sampling = 1;
}
if (m_Sampling == 0)
{
// use chunking duration estimate if setter hasn't been used
m_Sampling = sampling;
}
if (m_Sampling != sampling)
{
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Warning
<< "Sampling rate [" << sampling << "] suggested by chunk properties differs from stream-specified rate ["
<< m_Sampling << "]. There may be a problem with an upstream box. Trying to use the estimated rate.\n";
m_Sampling = sampling;
}
//computes the number of buffer necessary to display the interval
adjustNumberOfDisplayedBuffers(-1);
m_Drawable->init();
m_HasFirstBuffer = true;
}
if (!m_ChannelLookupTableInitialized)
{
fillChannelLookupTable(); //to retrieve the unrecognized electrode warning
// The above call will fail if no electrode localisation data...
// m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error << "Unable to fill lookup table\n";
// return false;
}
else
{
//look for chanloc buffers recently received
for (size_t i = 0; i < m_channelLocalisationCoords.size(); ++i)
{
//if a new set of coordinates was received
if (m_channelLocalisationCoords[i].second) { onChannelLocalisationBufferReceived(i); }
}
}
double* bufferToWrite = nullptr;
const size_t nSamplesPerBuffer = m_DimSizes[0] * m_DimSizes[1];
//if old buffers need to be removed
if (m_SampleBuffers.size() == m_NBufferToDisplay)
{
if (m_ovTotalDuration == 0) { m_ovTotalDuration = (m_StartTime.back() - m_StartTime.front()) + (m_EndTime.back() - m_StartTime.back()); }
if (m_BufferStep == 0)
{
if (m_StartTime.size() <= 1) { m_BufferStep = size_t(m_ovTotalDuration); }
else { m_BufferStep = m_StartTime[1] - m_StartTime[0]; }
}
if (m_TotalStep == 0) { m_TotalStep = (m_StartTime.back() - m_StartTime.front()) + m_BufferStep; }
//save first buffer pointer
bufferToWrite = m_SampleBuffers.front();
//pop first element from queues
m_SampleBuffers.pop_front();
m_StartTime.pop_front();
m_EndTime.pop_front();
for (size_t c = 0; c < size_t(m_DimSizes[0]); ++c) { m_LocalMinMaxValue[c].pop_front(); }
}
//do we need to allocate a new buffer?
if (bufferToWrite == nullptr) { bufferToWrite = new double[nSamplesPerBuffer]; }
//copy new buffer into internal buffer
if (nSamplesPerBuffer != 0) { memcpy(bufferToWrite, buffer, nSamplesPerBuffer * sizeof(double)); }
//push new buffer and its timestamps
m_SampleBuffers.push_back(bufferToWrite);
m_StartTime.push_back(startTime);
m_EndTime.push_back(endTime);
//compute and push min and max values of new buffer
size_t currentSample = 0;
//for each channel
for (size_t c = 0; c < size_t(m_DimSizes[0]); ++c)
{
double localMin = DBL_MAX;
double localMax = -DBL_MAX;
//for each sample
for (size_t i = 0; i < m_DimSizes[1]; i++, currentSample++)
{
//get channel local min/max
if (buffer[currentSample] < localMin) { localMin = buffer[currentSample]; }
if (buffer[currentSample] > localMax) { localMax = buffer[currentSample]; }
}
//adds the minmax pair to the corresponding channel's list
m_LocalMinMaxValue[c].emplace_back(localMin, localMax);
if (localMax > m_MaxValue) { m_MaxValue = localMax; }
if (localMin < m_MinValue) { m_MinValue = localMin; }
}
//tells the drawable to redraw himself since the signal information has been updated
if (m_RedrawOnNewData) { m_Drawable->redraw(); }
return true;
}
bool CBufferDatabase::setSampling(const size_t sampling)
{
m_Sampling = sampling;
return true;
}
void CBufferDatabase::getDisplayedChannelLocalMinMaxValue(const size_t channel, double& min, double& max)
{
min = +DBL_MAX;
max = -DBL_MAX;
for (size_t i = 0; i < m_LocalMinMaxValue[channel].size(); ++i)
{
if (min > m_LocalMinMaxValue[channel][i].first) { min = m_LocalMinMaxValue[channel][i].first; }
if (max < m_LocalMinMaxValue[channel][i].second) { max = m_LocalMinMaxValue[channel][i].second; }
}
}
bool CBufferDatabase::isTimeInDisplayedInterval(const uint64_t& time) const
{
if (m_StartTime.empty()) { return false; }
return time >= m_StartTime.front() && time <= m_EndTime.back();
}
bool CBufferDatabase::getIndexOfBufferStartingAtTime(const uint64_t& time, size_t& index) const
{
index = 0;
if (m_SampleBuffers.empty() || time < m_StartTime.front() || time > m_StartTime.back()) { return false; }
for (size_t i = 0; i < m_StartTime.size(); ++i)
{
if (m_StartTime[i] == time)
{
index = i;
return true;
}
}
return false;
}
void CBufferDatabase::getDisplayedGlobalMinMaxValue(double& min, double& max)
{
min = +DBL_MAX;
max = -DBL_MAX;
for (auto& pairs : m_LocalMinMaxValue)
{
for (const auto& pair : pairs)
{
if (min > pair.first) { min = pair.first; }
if (max < pair.second) { max = pair.second; }
}
}
}
void CBufferDatabase::getLastBufferChannelLocalMinMaxValue(const size_t channel, double& min, double& max)
{
min = m_LocalMinMaxValue[channel].back().first;
max = m_LocalMinMaxValue[channel].back().second;
}
void CBufferDatabase::getLastBufferMinMaxValue(double& min, double& max)
{
min = +DBL_MAX;
max = -DBL_MAX;
for (auto& localValue : m_LocalMinMaxValue)
{
min = (localValue.back().first < min) ? localValue.back().first : min;
max = (localValue.back().second > max) ? localValue.back().second : max;
}
}
bool CBufferDatabase::getElectrodePosition(const size_t index, double* position)
{
//TODO : add time parameter and look for coordinates closest to that time!
if (index < m_channelLocalisationLabels.size())
{
//if(m_cartesianCoords == true)
//{
*position = *(m_channelLocalisationCoords[0].first->getBuffer() + 3 * index);
*(position + 1) = *(m_channelLocalisationCoords[0].first->getBuffer() + 3 * index + 1);
*(position + 2) = *(m_channelLocalisationCoords[0].first->getBuffer() + 3 * index + 2);
//}
return true;
}
return false;
}
bool CBufferDatabase::getElectrodePosition(const CString& label, double* position)
{
//TODO : add time parameter and look for coordinates closest to that time!
for (size_t i = 0; i < m_channelLocalisationLabels.size(); ++i)
{
if (strcmp(label.toASCIIString(), m_channelLocalisationLabels[i].toASCIIString()) == 0)
{
//if(m_cartesianCoords == true)
//{
*position = *(m_channelLocalisationCoords[0].first->getBuffer() + 3 * i);
*(position + 1) = *(m_channelLocalisationCoords[0].first->getBuffer() + 3 * i + 1);
*(position + 2) = *(m_channelLocalisationCoords[0].first->getBuffer() + 3 * i + 2);
//}
return true;
}
}
return false;
}
bool CBufferDatabase::getElectrodeLabel(const size_t index, CString& label)
{
if (index >= m_channelLocalisationLabels.size()) { return false; }
label = m_channelLocalisationLabels[index].toASCIIString();
return true;
}
bool CBufferDatabase::getChannelPosition(const size_t index, double*& position)
{
//TODO : add time parameter and look for coordinates closest to that time!
if (index >= 0 && index < m_ChannelLookupIndices.size())
{
if (m_cartesianCoords) { position = m_channelLocalisationCoords[0].first->getBuffer() + 3 * m_ChannelLookupIndices[index]; }
// else { } //TODO
return true;
}
return false;
}
bool CBufferDatabase::getChannelSphericalCoordinates(const size_t index, double& theta, double& phi)
{
//TODO : add time parameter and look for coordinates closest to that time!
if (index >= 0 && index < m_ChannelLookupIndices.size())
{
if (m_cartesianCoords)
{
//get cartesian coords
double* coords = m_channelLocalisationCoords[0].first->getBuffer() + 3 * m_ChannelLookupIndices[index];
//convert to spherical coords
return convertCartesianToSpherical(coords, theta, phi);
}
//streamed coordinates are spherical already
//TODO
return false;
}
return false;
}
bool CBufferDatabase::getChannelLabel(const size_t index, CString& label)
{
if (index >= 0 && index < m_ChannelLookupIndices.size())
{
label = m_channelLocalisationLabels[m_ChannelLookupIndices[index]];
return true;
}
label = "";
return false;
}
void CBufferDatabase::setStimulation(const size_t /*index*/, const uint64_t id, const uint64_t date)
{
m_Stimulations.emplace_back(date, id);
if (!m_StartTime.empty())
{
while (m_Stimulations.begin() != m_Stimulations.end() && m_Stimulations.begin()->first < m_StartTime.front()) { m_Stimulations.pop_front(); }
}
}
bool CBufferDatabase::fillChannelLookupTable()
{
if (!m_HasFirstBuffer || !m_channelLocalisationHeaderReceived) { return false; }
bool res = true;
//resize lookup array and initialize lookup indices to 0
m_ChannelLookupIndices.resize(size_t(m_NElectrodes), 0);
//for all channels
for (size_t i = 0; i < m_DimSizes[0]; ++i)
{
//trim leading spaces
size_t firstNonWhitespaceChar = 0;
for (; firstNonWhitespaceChar < m_DimLabels[0][i].size(); ++firstNonWhitespaceChar)
{
if (isspace(m_DimLabels[0][i][firstNonWhitespaceChar]) == 0) { break; }
}
//trim trailing spaces
size_t lastNonWhitespaceChar = 0;
if (!m_DimLabels[0][i].empty())
{
for (lastNonWhitespaceChar = m_DimLabels[0][i].size() - 1; lastNonWhitespaceChar >= 0; lastNonWhitespaceChar--)
{
if (isspace(m_DimLabels[0][i][lastNonWhitespaceChar]) == 0) { break; }
}
}
//look for label in channel localisation labels database
bool labelRecognized = false;
if (firstNonWhitespaceChar < lastNonWhitespaceChar)
{
std::string channelLabel(m_DimLabels[0][i].substr(firstNonWhitespaceChar, lastNonWhitespaceChar - firstNonWhitespaceChar + 1));
for (size_t j = 0; j < m_channelLocalisationLabels.size(); ++j)
{
if (strcmp(channelLabel.c_str(), m_channelLocalisationLabels[j].toASCIIString()) == 0)
{
labelRecognized = true;
m_ChannelLookupIndices[i] = j;
break;
}
}
}
//unrecognized electrode!
if (!labelRecognized)
{
m_ParentPlugin.getLogManager() << Kernel::LogLevel_Warning << "Unrecognized electrode name (index=" << i
<< ", name=" << m_DimLabels[0][i] << ")!\n";
res = false;
}
}
m_ParentPlugin.getLogManager() << Kernel::LogLevel_Trace << "Electrodes list : ";
for (size_t i = 0; i < size_t(m_DimSizes[0]); ++i)
{
m_ParentPlugin.getLogManager() << m_DimLabels[0][i].c_str();
if (i < m_DimSizes[0] - 1) { m_ParentPlugin.getLogManager() << ", "; }
else { m_ParentPlugin.getLogManager() << "\n"; }
}
if (res) { m_ChannelLookupTableInitialized = true; }
return res;
}
bool CBufferDatabase::convertCartesianToSpherical(const double* cartesian, double& theta, double& phi) const
{
const double threshold = 1e-3;
const double radToDeg = 57.2957795131; // 180 / pi
//compute theta
theta = acos(cartesian[2]) * radToDeg;
//compute phi so that it lies in [0, 360]
if (fabs(cartesian[0]) < threshold) { phi = (cartesian[1] > 0) ? 90 : 270; }
else
{
phi = atan(cartesian[1] / cartesian[0]) * radToDeg;
if (cartesian[0] < 0) { phi += 180; }
else if (cartesian[1] < 0) { phi += 360; }
}
return true;
}
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,322 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <cfloat>
#include <deque>
#include <string>
#include <vector>
#include <array>
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:
int64_t m_NElectrodes = 0; ///< Number of channels
std::array<size_t, 2> m_DimSizes; ///< Number of channels and number of samples per buffer
std::vector<std::string> m_DimLabels[2]; ///< Channel labels, buffer labels
bool m_HasFirstBuffer = false; ///< Flag set to true once first buffer is received
size_t m_Sampling = 0; ///< Sampling frequency of the incoming stream
std::deque<double*> m_SampleBuffers; ///< double-linked list of pointers to the samples buffers of the current time window
std::deque<std::pair<uint64_t, uint64_t>> m_Stimulations; ///< stimulations to display. pair values are <date, stimcode>
bool m_ChannelLookupTableInitialized = false; ///< flag set to true once channel lookup indices are determined
std::vector<size_t> m_ChannelLookupIndices; ///< indices of electrodes in channel localisation database
//CMatrix m_electrodesSphericalCoords; ///< electrode spherical coordinates (in degrees)
//std::vector<CString> m_oElectrodesLabels; ///< electrode labels (standardized)
size_t m_NBufferToDisplay = 2; ///< Number of buffer to display at the same time
double m_MaxValue = -DBL_MAX; ///< The global maximum value of the signal (up to now)
double m_MinValue = +DBL_MAX; ///< The global minimum value of the signal (up to now)
std::deque<uint64_t> m_StartTime; ///< Double-linked list of the start times of the current buffers
std::deque<uint64_t> m_EndTime; ///< Double-linked list of the end times of the current buffers
double m_TotalDuration = 0; ///< Duration to display in seconds
/*! 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_ovTotalDuration = 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!*/
size_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!*/
size_t m_BufferStep = 0;
uint64_t m_LastBufferEndTime = 0; ///< When did the last inserted buffer end
bool m_WarningPrinted = false; ///< Did we print a warning about noncontinuity?
CSignalDisplayDrawable* m_Drawable = nullptr;///< Pointer to the drawable object to update (if needed)
std::vector<std::deque<std::pair<double, double>>> m_LocalMinMaxValue;
Toolkit::TBoxAlgorithm<IBoxAlgorithm>& 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<CString> 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<std::pair<CMatrix*, bool>> m_channelLocalisationCoords;
//! double-linked list of channel coordinates (spherical if streamed coords aere cartesian and vice versa)
//std::deque< std::pair<CMatrix*, bool> > m_oChannelLocalisationAlternateCoords;
//pointer to double linked list of cartesian coordinates
//std::deque< std::pair<CMatrix*, bool> > * m_channelLocalisationCartesianCoords;
//pointer to double linked list of spherical coordinates
//std::deque< std::pair<CMatrix*, bool> > * m_channelLocalisationSphericalCoords;
//! double-linked list of start/end times of channel coordinates
std::deque<std::pair<uint64_t, uint64_t>> m_channelLocalisationTimes;
//@}
//! Redraw mode (shift or scan)
ESignalDisplayMode m_displayMode = ESignalDisplayMode::Scan;
public:
explicit CBufferDatabase(Toolkit::TBoxAlgorithm<IBoxAlgorithm>& plugin);
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(const double time);
/**
* \brief Get time interval covered by data held in this object
* \return Time interval in ms
*/
virtual double getDisplayedTimeIntervalWidth() const;
/**
* \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(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);
//! Returns the min/max values of the last buffer arrived (all channels taken into account)
virtual void getLastBufferMinMaxValue(double& min, double& 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;
/**
* \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 (rChannelPosition 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 n);
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, const uint64_t startTime, const 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 /*n*/) { }
virtual void setStimulation(const size_t index, const uint64_t id, const 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 ESignalDisplayMode mode) { m_displayMode = mode; }
/**
* \brief Get current display mode
* \return Current display mode
*/
virtual ESignalDisplayMode getDisplayMode() { return m_displayMode; }
/**
* \brief Set flag stating whether to redraw associated SignalDisplayDrawable objet when new data is available
* \param redraw Value to set flag with
*/
virtual void setRedrawOnNewData(const bool redraw) { m_RedrawOnNewData = redraw; }
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
@@ -0,0 +1,274 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <gtk/gtk.h>
#include "ovpCTopographicMapDatabase.h"
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
/**
* This class contains everything necessary to setup a GTK window and display
* a 2D topographic map
*/
class CTopographicMap2DView final : public CTopographicMapDrawable
{
public:
enum class EProjection { Axial, Radial, NumProjection };
enum class EView { Top, Left, Right, Back };
/**
* \brief Constructor
* \param mapDatabase Datastore
* \param interpolation Interpolation mode
* \param delay Delay to apply to displayed data
*/
CTopographicMap2DView(CTopographicMapDatabase& mapDatabase, EInterpolationType interpolation, double delay);
/**
* \brief Destructor
*/
~CTopographicMap2DView() override;
/** \name CSignalDisplayDrawable implementation */
//@{
/**
* \brief Initialize widgets
*/
void init() override;
/**
* \brief Redraw map
*/
void redraw() override;
//@}
/** \name CTopographicMapDrawable implementation */
//@{
/**
* \brief Get matrix of sample points coordinates (places where to interpolate values)
* \return Pointer to matrix of sample points coordinates
*/
CMatrix* getSampleCoordinatesMatrix() override;
/**
* \brief Set matrix of sample points values (values interpolated at places specified in sample coordinates matrix)
* \param [in] matrix Pointer to matrix of sample points values
* \return True if values were successfully set, false otherwise
*/
bool setSampleValuesMatrix(CMatrix* matrix) override;
//@}
/**
* \brief Get pointers to plugin main widget and (optional) toolbar widget
* \param [out] widget Pointer to main widget
* \param [out] toolbar Pointer to (optional) toolbar widget
*/
void getWidgets(GtkWidget*& widget, GtkWidget*& toolbar) const;
/**
* \brief Get ID of current view
* \return ID of current view
*/
EView getCurrentView() const { return m_currentView; }
/** \name Callbacks */
//@{
void resizeCB(size_t /*width*/, size_t /*height*/) { m_needResize = true; }
void toggleElectrodesCB();
void setProjectionCB(GtkWidget* widget);
void setViewCB(GtkWidget* widget);
void setInterpolationCB(GtkWidget* widget);
void setDelayCB(const double delay) const { m_mapDatabase.setDelay(delay); }
//@}
private:
//draw color palette
void drawPalette(size_t x, size_t y, size_t width, size_t height) const;
//draw face (ears, nose, neck)
void drawFace(size_t x, size_t y, size_t width, size_t height) const;
//draw head
void drawHead() const;
//draw RGB buffer
void drawPotentials() const;
//draw electrodes corresponding to visible channels as rings
void drawElectrodes() const;
/**
* \brief Get channel position in 2D
* \param index[in] Index of channel which position is to be retrieved
* \param x[out] X coordinate of channel location, if channel is visible
* \param y[out] Y coordinate of channel location, if channel is visible
* \return True if channel is visible in current view, false otherwise
*/
bool getChannel2DPosition(size_t index, gint& x, gint& y) const;
//update RGB buffer with interpolated values
void refreshPotentials();
//draw a box in RGB buffer
void drawBoxToBuffer(size_t x, size_t y, size_t width, size_t height, uint8_t red, uint8_t green, uint8_t blue) const;
void enableElectrodeButtonSignals(bool enable);
void enableProjectionButtonSignals(bool enable);
void enableViewButtonSignals(bool enable);
void enableInterpolationButtonSignals(bool enable);
/**
* \brief Compute normalized coordinates of 2D samples
* \remarks This method should first be called with bComputeCoordinates = false, allowing caller
* to resize data structures appropriately, and then it may be called with bComputeCoordinates = true
* \param all If false, this method only computes the number of visible samples
* \return Number of visible samples (samples lying within the actual skull area)
*/
size_t computeSamplesNormalizedCoordinates(bool all);
void resizeData();
void redrawClipmask();
double getThetaFromCartesianCoordinates(const std::array<double, 3>& cartesian) const;
double getPhiFromCartesianCoordinates(const std::array<double, 3>& cartesian) const;
bool compute2DCoordinates(double theta, double phi, size_t skullCenterX, size_t skullCenterY, gint& x, gint& y) const;
//! The database that contains the information to use to draw the signals
CTopographicMapDatabase& m_mapDatabase;
//Maximum delay that can be applied to displayed data
double m_maxDelay = 2.0;
GtkBuilder* m_builderInterface = nullptr;
GtkWidget* m_drawingArea = nullptr;
GdkBitmap* m_clipmask = nullptr; //origin (m_skullX, m_skullY)
size_t m_clipmaskWidth = 0;
size_t m_clipmaskHeight = 0;
GdkGC* m_clipmaskGC = nullptr;
GdkRegion* m_visibleRegion = nullptr; //reallocated whenever clipmask changes
GdkColor m_bgColor;
//! Active projection
EProjection m_currentProjection = EProjection::Radial;
//! Projection radio buttons
GtkRadioToolButton* m_axialProjectionButton = nullptr;
GtkRadioToolButton* m_radialProjectionButton = nullptr;
//! Active view
EView m_currentView = EView::Top;
//! View radio buttons
GtkRadioToolButton* m_topViewButton = nullptr;
GtkRadioToolButton* m_leftViewButton = nullptr;
GtkRadioToolButton* m_rightViewButton = nullptr;
GtkRadioToolButton* m_backViewButton = nullptr;
//! Interpolation type
EInterpolationType m_currentInterpolation = EInterpolationType::Laplacian;
GtkRadioToolButton* m_mapPotentials = nullptr;
GtkRadioToolButton* m_mapCurrents = nullptr;
//! Electrodes toggle button
GtkToggleToolButton* m_electrodesToggleButton = nullptr;
//! Electrodes toggle state
bool m_electrodesToggledOn = true;
bool m_needResize = true;
size_t m_gridSize = 0, m_cellSize = 0;
CMatrix m_sampleCoordinatesMatrix;
std::vector<size_t> m_sampleValues;
std::vector<std::pair<size_t, size_t>> m_sample2DCoordinates; //in skull coords
size_t m_minPaletteBarHeight = 10, m_maxPaletteBarHeight = 30;
size_t m_headWindowWidth = 00, m_headWindowHeight = 00;
size_t m_paletteWindowWidth = 00, m_paletteWindowHeight = 00;
size_t m_skullX = 0, m_skullY = 0, m_skullDiameter = 0;
//angles relative to 3 o'clock position, CCW, in degrees
float m_skullOutlineStartAngle = 0.0, m_skullOutlineEndAngle = 0.0;
float m_skullFillStartAngle = 0.0, m_skullFillEndAngle = 0.0;
//determined from m_skullOutlineEndAngle
size_t m_skullOutlineLeftPointX = 0, m_skullOutlineLeftPointY = 0;
//determined from m_skullOutlineStartAngle
size_t m_skullOutlineRightPointX = 0, m_skullOutlineRightPointY = 0;
//determined from m_skullFillEndAngle
size_t m_skullFillLeftPointX = 0, m_skullFillLeftPointY = 0;
//determined from m_skullFillStartAngle
size_t m_skullFillRightPointX = 0, m_skullFillRightPointY = 0;
size_t m_skullFillBottomPointX = 0, m_skullFillBottomPointY = 0;
/////////////////////////////
// TOP VIEW
/////////////////////////////
size_t m_noseY = 0;
/////////////////////////////
// BOTTOM VIEW
/////////////////////////////
size_t m_leftNeckX = 0, m_leftNeckY = 0;
size_t m_rightNeckX = 0, m_rightNeckY = 0;
//////////////////////////////////
// LEFT/RIGHT VIEWS
//////////////////////////////////
/*
+ A
/
/
/
+ B
| C
+----+ D
|
+ E
*/
size_t m_noseTopX = 0, m_noseTopY = 0; //A
size_t m_noseBumpX = 0, m_noseBumpY = 0; //B
size_t m_noseTipX = 0, m_noseTipY = 0; //C
size_t m_noseBaseX = 0, m_noseBaseY = 0; //D
size_t m_noseBottomX = 0, m_noseBottomY = 0; //E
/**
* \brief Main pixmap
* \remarks This pixmap is 32-bit aligned. Each row is m_rowStride wide, and the pixmap has the height of the DrawingArea's
* window. It is pasted into the DrawingArea's window upon redraw
*/
//TODO
//GdkPixmap* m_pixmap;
/**
* \brief Skull pixmap
* \remarks This pixmap is 32-bit aligned. Each row is m_rowStride wide, and the pixmap has m_skullDiameter rows.
* It is pasted into the main pixmap everytime changes happen (window resizing, display options toggled on/off, etc)
*/
std::vector<guchar> m_skullRGBBuffer;
size_t m_rowStride = 0;
};
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,283 @@
#include "ovpCTopographicMapDatabase.h"
#include <cmath>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
CTopographicMapDatabase::CTopographicMapDatabase(Toolkit::TBoxAlgorithm<IBoxAlgorithm>& plugin, Kernel::IAlgorithmProxy& interpolation)
: CBufferDatabase(plugin), m_interpolation(interpolation)
{
//map input parameters
//--------------------
//spline order
m_interpolation.getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SplineOrder)->setReferenceTarget(&m_splineOrder);
//number of channels (or electrodes)
m_interpolation.getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCount)->setReferenceTarget(&m_NElectrodes);
//matrix of pointers to electrode coordinates
m_pElectrodeCoords = &m_electrodeCoords;
m_interpolation.getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCoordinates)->setReferenceTarget(
&m_pElectrodeCoords);
//matrix of potentials measured at each electrode
m_pElectrodePotentials = &m_electrodePotentials;
m_interpolation.getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsValues)->setReferenceTarget(
&m_pElectrodePotentials);
//matrix holding sample coordinates mapped at runtime (its size is not known a priori and may vary)
//
//map output parameters
//---------------------
m_minSamplePointValue.initialize(m_interpolation.getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MinSamplePointValue));
m_maxSamplePointValue.initialize(m_interpolation.getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MaxSamplePointValue));
}
void CTopographicMapDatabase::setMatrixDimensionSize(const size_t index, const size_t size)
{
CBufferDatabase::setMatrixDimensionSize(index, size);
if (index == 0) { m_electrodePotentials.resize(size_t(m_NElectrodes)); }
}
bool CTopographicMapDatabase::onChannelLocalisationBufferReceived(const size_t bufferIndex)
{
CBufferDatabase::onChannelLocalisationBufferReceived(bufferIndex);
if (!m_ChannelLookupTableInitialized || m_channelLocalisationCoords.empty() || m_NElectrodes == 0)
{
m_ParentPlugin.getLogManager() << Kernel::LogLevel_Warning
<< "Channel localisation buffer received before channel lookup table was initialized! Can't process buffer!\n";
}
//static electrode coordinates
if (!m_dynamicChannelLocalisation)
{
//if streamed coordinates are cartesian
if (m_cartesianCoords)
{
//fill electrode coordinates matrix
m_electrodeCoords.resize(size_t(3 * m_NElectrodes));
const double* coords = m_channelLocalisationCoords[0].first->getBuffer();
for (size_t i = 0; i < size_t(m_NElectrodes); ++i)
{
const size_t lookupIdx = m_ChannelLookupIndices[i];
m_electrodeCoords[3 * i] = *(coords + 3 * lookupIdx);
m_electrodeCoords[3 * i + 1] = *(coords + 3 * lookupIdx + 1);
m_electrodeCoords[3 * i + 2] = *(coords + 3 * lookupIdx + 2);
}
//electrode coordinates initialized : it is now possible to interpolate potentials
m_electrodeCoordsInitialized = true;
}
}
return true;
}
void CTopographicMapDatabase::getLastBufferInterpolatedMinMaxValue(double& min, double& max) const
{
min = m_minSamplePointValue;
max = m_maxSamplePointValue;
}
bool CTopographicMapDatabase::processValues()
{
//wait for electrode coordinates
if (!m_electrodeCoordsInitialized) { return true; }
if (m_firstProcess)
{
//done in CBufferDatabase::setMatrixBuffer
//initialize the drawable object
//m_Drawable->init();
if (!checkElectrodeCoordinates()) { return false; }
//precompute sin/cos tables
m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_PrecomputeTables, true);
m_firstProcess = false;
}
//retrieve electrode values
//determine what buffer to use from delay
size_t bufferIdx = 0;
const uint64_t currentTime = m_ParentPlugin.getPlayerContext().getCurrentTime();
const uint64_t displayTime = currentTime - m_delay;
getBufferIndexFromTime(displayTime, bufferIdx);
//determine what sample to use
size_t sampleIdx;
if (displayTime <= m_StartTime[bufferIdx]) { sampleIdx = 0; }
else if (displayTime >= m_EndTime[bufferIdx]) { sampleIdx = m_DimSizes[1] - 1; }
else { sampleIdx = size_t(double(displayTime - m_StartTime[bufferIdx]) / double(m_BufferDuration) * m_DimSizes[1]); }
for (int64_t i = 0; i < m_NElectrodes; ++i) { *(m_electrodePotentials.getBuffer() + i) = m_SampleBuffers[bufferIdx][i * m_DimSizes[1] + sampleIdx]; }
//interpolate spline values (potentials)
if (m_interpolationType == EInterpolationType::Spline)
{
m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeSplineCoefs, true);
}
else //interpolate spline laplacian (currents)
{
m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeLaplacianCoefs, true);
}
//retrieve up-to-date pointer to sample matrix
m_samplePointCoords = dynamic_cast<CTopographicMapDrawable*>(m_Drawable)->getSampleCoordinatesMatrix();
if (m_samplePointCoords != nullptr)
{
//map pointer to input parameter
m_interpolation.getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SamplePointsCoordinates)->
setReferenceTarget(&m_samplePointCoords);
if (m_interpolationType == EInterpolationType::Spline)
{
m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateSpline, true);
}
else { m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateLaplacian, true); }
}
m_interpolation.process();
bool process = true;
if (m_interpolation.isOutputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error))
{
m_ParentPlugin.getLogManager() << Kernel::LogLevel_Warning << "An error occurred while interpolating potentials!\n";
process = false;
}
else
{
if (m_samplePointCoords != nullptr)
{
//retrieve interpolation results
Kernel::TParameterHandler<CMatrix*> sampleValuesMatrix;
sampleValuesMatrix.initialize(
m_interpolation.getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_SamplePointsValues));
dynamic_cast<CTopographicMapDrawable*>(m_Drawable)->setSampleValuesMatrix(sampleValuesMatrix);
//tells the drawable to redraw itself since the signal information has been updated
m_Drawable->redraw();
}
}
return process;
}
bool CTopographicMapDatabase::setDelay(const double delay)
{
if (delay > m_TotalDuration) { return false; }
//convert delay to 32:32 format
m_delay = int64_t(delay * (1LL << 32)); // $$$ Casted in (int64_t) because of Ubuntu 7.10 crash !
return true;
}
bool CTopographicMapDatabase::interpolateValues()
{
//can't interpolate before first buffer has been received
if (m_firstProcess) { return false; }
//retrieve up-to-date pointer to sample matrix
m_samplePointCoords = dynamic_cast<CTopographicMapDrawable*>(m_Drawable)->getSampleCoordinatesMatrix();
if (m_samplePointCoords != nullptr)
{
//map pointer to input parameter
m_interpolation.getInputParameter(OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SamplePointsCoordinates)->
setReferenceTarget(&m_samplePointCoords);
//interpolate using spline or laplacian coefficients depending on interpolation mode
if (m_interpolationType == EInterpolationType::Spline)
{
m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateSpline, true);
}
else { m_interpolation.activateInputTrigger(OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateLaplacian, true); }
}
m_interpolation.process();
if (m_interpolation.isOutputTriggerActive(OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error))
{
m_ParentPlugin.getLogManager() << Kernel::LogLevel_Warning << "An error occurred while interpolating potentials!\n";
}
else
{
if (m_samplePointCoords != nullptr)
{
//retrieve interpolation results
Kernel::TParameterHandler<CMatrix*> sampleValuesMatrix;
sampleValuesMatrix.initialize(m_interpolation.getOutputParameter(OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_SamplePointsValues));
dynamic_cast<CTopographicMapDrawable*>(m_Drawable)->setSampleValuesMatrix(sampleValuesMatrix);
}
}
return true;
}
bool CTopographicMapDatabase::getBufferIndexFromTime(const uint64_t time, size_t& bufferIndex)
{
if (m_SampleBuffers.empty()) { return false; }
if (time < m_StartTime[0])
{
bufferIndex = 0;
return false;
}
if (time > m_EndTime.back())
{
bufferIndex = size_t(m_SampleBuffers.size() - 1);
return false;
}
for (size_t i = 0; i < m_SampleBuffers.size(); ++i)
{
if (time <= m_EndTime[i])
{
bufferIndex = i;
break;
}
}
return true;
}
bool CTopographicMapDatabase::checkElectrodeCoordinates()
{
const size_t nChannel = getChannelCount();
for (size_t i = 0; i < nChannel; ++i)
{
double* normalizedChannelCoords = nullptr;
if (!getChannelPosition(i, normalizedChannelCoords))
{
CString channelLabel;
getChannelLabel(i, channelLabel);
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error
<< "Couldn't retrieve coordinates of electrode #" << i
<< "(" << channelLabel << "), aborting model frame electrode coordinates computation\n";
return false;
}
#define MY_THRESHOLD 0.01
if (fabs(normalizedChannelCoords[0] * normalizedChannelCoords[0] +
normalizedChannelCoords[1] * normalizedChannelCoords[1] +
normalizedChannelCoords[2] * normalizedChannelCoords[2] - 1.) > MY_THRESHOLD)
#undef MY_THRESHOLD
{
CString channelLabel;
getChannelLabel(i, channelLabel);
m_ParentPlugin.getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Error
<< "Coordinates of electrode #" << i << "(" << channelLabel
<< "), are not normalized, aborting model frame electrode coordinates computation\n";
return false;
}
}
return true;
}
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,106 @@
#pragma once
#include "../../ovp_defines.h"
#include "ovpCBufferDatabase.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SimpleVisualization {
class CTopographicMapDrawable : public CSignalDisplayDrawable
{
public:
~CTopographicMapDrawable() override = default;
virtual CMatrix* getSampleCoordinatesMatrix() = 0;
virtual bool setSampleValuesMatrix(CMatrix* pSampleValuesMatrix) = 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 CTopographicMapDatabase : public CBufferDatabase
{
public:
CTopographicMapDatabase(Toolkit::TBoxAlgorithm<IBoxAlgorithm>& plugin, Kernel::IAlgorithmProxy& interpolation);
~CTopographicMapDatabase() override = default;
void setMatrixDimensionSize(const size_t index, const size_t size) override;
/**
* \brief Callback called upon channel localisation buffer reception
* \param bufferIndex Index of newly received channel localisation buffer
* \return True if buffer data was correctly processed, false otherwise
*/
bool onChannelLocalisationBufferReceived(const size_t bufferIndex) override;
bool setDelay(double delay);
/**
* \brief Set interpolation type
* Spline values (potentials) can be interpolated directly, but the spline laplacian (currents) may
* be used as well
* \sa OVP_TypeId_SphericalLinearInterpolationType enumeration
*/
void setInterpolationType(const EInterpolationType type) { m_interpolationType = type; }
bool processValues();
bool interpolateValues();
//! Returns min/max interpolated values using the last buffer arrived (all channels taken into account)
void getLastBufferInterpolatedMinMaxValue(double& min, double& max) const;
private:
/**
* \brief Looks for buffer whose timeframe contains time passed as parameter
* \param time [in] Time of buffer to be retrieved
* \param bufferIndex [out] Index of buffer closest to time passed as parameter
* \return True if time passed as parameter lies within a buffer's timeframe, false otherwise
*/
bool getBufferIndexFromTime(uint64_t time, size_t& bufferIndex);
/**
* \brief Ensure electrode coordinates are normalized
* \return True if all electrode coordinates are normalized, false otherwise
*/
bool checkElectrodeCoordinates();
//true until process() is called for the first time
bool m_firstProcess = true;
//spherical spline interpolation
Kernel::IAlgorithmProxy& m_interpolation;
//order of spherical spline used for interpolation - mapped to OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SplineOrder
int64_t m_splineOrder = 4;
/**
* \brief Type of interpolation
* \sa OVP_TypeId_SphericalLinearInterpolationType enumeration
*/
EInterpolationType m_interpolationType = EInterpolationType::Spline;
//number of electrodes (see CBufferDatabase) - mapped to OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCount
//int64_t m_NElectrodes = 0;
//flag set to true once electrode coordinates have been initialized
bool m_electrodeCoordsInitialized = false;
//electrode cartesian coordinates, in normalized space (X right Y front Z up)
CMatrix m_electrodeCoords;
//pointer to electrode coordinates matrix - mapped to OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCoordinates
CMatrix* m_pElectrodeCoords = nullptr;
//electrode potentials
CMatrix m_electrodePotentials;
//pointer to electrode potentials matrix - mapped to OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsValues
CMatrix* m_pElectrodePotentials = nullptr;
//pointer to sample points coordinates matrix - mapped to OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SamplePointsCoordinates
CMatrix* m_samplePointCoords = nullptr;
//minimum interpolated value
Kernel::TParameterHandler<double> m_minSamplePointValue;
//maximum interpolated value
Kernel::TParameterHandler<double> m_maxSamplePointValue;
//delay to apply to interpolated values
uint64_t m_delay = 0;
};
} // namespace SimpleVisualization
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,70 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_LevelMeasure OpenViBE::CIdentifier(0x657138E4, 0x46D6586F)
#define OVP_ClassId_BoxAlgorithm_LevelMeasureDesc OpenViBE::CIdentifier(0x4D061428, 0x11B02233)
#define OVP_ClassId_Algorithm_LevelMeasure OpenViBE::CIdentifier(0x63C71764, 0x34A9717F)
#define OVP_ClassId_Algorithm_LevelMeasureDesc OpenViBE::CIdentifier(0x3EB6754F, 0x22FB1722)
#define OVP_ClassId_Algorithm_SphericalSplineInterpolation OpenViBE::CIdentifier(0x4F112803, 0x661D4029)
#define OVP_ClassId_Algorithm_SphericalSplineInterpolationDesc OpenViBE::CIdentifier(0x00D67A20, 0x3D3D4729)
#define OVP_ClassId_SignalDisplay OpenViBE::CIdentifier(0x0055BE5F, 0x087BDD12)
#define OVP_ClassId_SignalDisplayDesc OpenViBE::CIdentifier(0x00C4F2D5, 0x58810276)
#define OVP_ClassId_GrazVisualization OpenViBE::CIdentifier(0x00DD290D, 0x5F142820)
#define OVP_ClassId_GrazVisualizationDesc OpenViBE::CIdentifier(0x00F1955D, 0x38813A6A)
#define OVP_ClassId_PowerSpectrumDisplay OpenViBE::CIdentifier(0x004C0EA4, 0x713EC6D9)
#define OVP_ClassId_PowerSpectrumDisplayDesc OpenViBE::CIdentifier(0x00116B40, 0x69E1B00D)
#define OVP_ClassId_TopographicMap2DDisplay OpenViBE::CIdentifier(0x0B104632, 0x451C265F)
#define OVP_ClassId_TopographicMap2DDisplayDesc OpenViBE::CIdentifier(0x7154037A, 0x4BC52A9F)
#define OVP_ClassId_Simple3DDisplay OpenViBE::CIdentifier(0x31A00483, 0x35924E6B)
#define OVP_ClassId_Simple3DDisplayDesc OpenViBE::CIdentifier(0x443E145F, 0x77205DA0)
#define OVP_ClassId_TopographicMap3DDisplay OpenViBE::CIdentifier(0x36F95BE4, 0x0EF06290)
#define OVP_ClassId_TopographicMap3DDisplayDesc OpenViBE::CIdentifier(0x6AD52C48, 0x6E1C1746)
#define OVP_ClassId_VoxelDisplay OpenViBE::CIdentifier(0x76E42EA2, 0x66FB5265)
#define OVP_ClassId_VoxelDisplayDesc OpenViBE::CIdentifier(0x79321659, 0x642D3D0C)
#define OVP_ClassId_TimeFrequencyMapDisplay OpenViBE::CIdentifier(0x3AE63330, 0x76532117)
#define OVP_ClassId_TimeFrequencyMapDisplayDesc OpenViBE::CIdentifier(0x1BAE74F3, 0x20FB7C89)
#define OVP_ClassId_BoxAlgorithm_P300IdentifierCardVisualisation OpenViBE::CIdentifier(0x3AF7FF20, 0xA68745DB)
#define OVP_ClassId_BoxAlgorithm_P300IdentifierCardVisualisationDesc OpenViBE::CIdentifier(0x84F146EF, 0x4AA712A4)
#define OVP_ClassId_BoxAlgorithm_MatrixDisplay OpenViBE::CIdentifier(0x54F0796D, 0x3EDE2CC0)
#define OVP_ClassId_BoxAlgorithm_MatrixDisplayDesc OpenViBE::CIdentifier(0x63AB4BA7, 0x022C1524)
// Type definitions
//---------------------------------------------------------------------------------------------------
#define OVP_TypeId_SphericalLinearInterpolationType OpenViBE::CIdentifier(0x44B76D9E, 0x618229BC)
#define OVP_TypeId_SignalDisplayMode OpenViBE::CIdentifier(0x5DE046A6, 0x086340AA)
enum class EInterpolationType { Spline = 1, Laplacian = 2 };
enum class ESignalDisplayMode { Scroll, Scan };
enum class EDisplayMode { ZoomIn, ZoomOut, GlobalBestFit };
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OVP_Algorithm_LevelMeasure_InputParameterId_Matrix OpenViBE::CIdentifier(0x59430053, 0x67C23A83)
#define OVP_Algorithm_LevelMeasure_OutputParameterId_MainWidget OpenViBE::CIdentifier(0x101C4641, 0x466C71E3)
#define OVP_Algorithm_LevelMeasure_OutputParameterId_ToolbarWidget OpenViBE::CIdentifier(0x14905FFC, 0x6FE425B2)
#define OVP_Algorithm_LevelMeasure_InputTriggerId_Reset OpenViBE::CIdentifier(0x3EAF36C5, 0x74490C56)
#define OVP_Algorithm_LevelMeasure_InputTriggerId_Refresh OpenViBE::CIdentifier(0x71356FE4, 0x3E8F62DC)
#define OVP_Algorithm_LevelMeasure_OutputTriggerId_Refreshed OpenViBE::CIdentifier(0x3C3C1B06, 0x360305D9)
#define OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SplineOrder OpenViBE::CIdentifier(0x3B8200F6, 0x205162C7)
#define OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCount OpenViBE::CIdentifier(0x2ABF11FC, 0x174A2CFE)
#define OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsCoordinates OpenViBE::CIdentifier(0x36F743FE, 0x37897AB9)
#define OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_ControlPointsValues OpenViBE::CIdentifier(0x4EA55599, 0x670274A7)
#define OVP_Algorithm_SphericalSplineInterpolation_InputParameterId_SamplePointsCoordinates OpenViBE::CIdentifier(0x280A531D, 0x339C18AA)
#define OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_SamplePointsValues OpenViBE::CIdentifier(0x12D0319C, 0x51ED4D8B)
#define OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MinSamplePointValue OpenViBE::CIdentifier(0x0CEE2041, 0x79455EED)
#define OVP_Algorithm_SphericalSplineInterpolation_OutputParameterId_MaxSamplePointValue OpenViBE::CIdentifier(0x1ECB03E3, 0x40EF757F)
#define OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_PrecomputeTables OpenViBE::CIdentifier(0x42A650DA, 0x62B35F76)
#define OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeSplineCoefs OpenViBE::CIdentifier(0x5B353712, 0x069F3D3B)
#define OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_ComputeLaplacianCoefs OpenViBE::CIdentifier(0x7D8C545E, 0x7C086660)
#define OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateSpline OpenViBE::CIdentifier(0x1241610E, 0x03CB1AD9)
#define OVP_Algorithm_SphericalSplineInterpolation_InputTriggerId_InterpolateLaplacian OpenViBE::CIdentifier(0x11CE0AC3, 0x0FD85469)
#define OVP_Algorithm_SphericalSplineInterpolation_OutputTriggerId_Error OpenViBE::CIdentifier(0x08CB0679, 0x3A6F3C3A)
@@ -0,0 +1,20 @@
#include "ovp_defines.h"
#include "algorithms/ovpCAlgorithmSphericalSplineInterpolation.h"
#include "box-algorithms/ovpCBoxAlgorithmTopographicMap2DDisplay.h"
#include "box-algorithms/ovpCBoxAlgorithmMatrixDisplay.h"
OVP_Declare_Begin()
context.getTypeManager().registerEnumerationType(OVP_TypeId_SphericalLinearInterpolationType, "Spherical linear interpolation type");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_SphericalLinearInterpolationType, "Spline (potentials)", size_t(EInterpolationType::Spline));
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_SphericalLinearInterpolationType, "Spline laplacian (currents)",
size_t(EInterpolationType::Laplacian));
OVP_Declare_New(OpenViBE::Plugins::Test::CAlgorithmSphericalSplineInterpolationDesc)
OVP_Declare_New(OpenViBE::Plugins::SimpleVisualization::CBoxAlgorithmTopographicMap2DDisplayDesc)
OVP_Declare_New(OpenViBE::Plugins::SimpleVisualization::CBoxAlgorithmMatrixDisplayDesc)
OVP_Declare_End()