init
This commit is contained in:
+165
@@ -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.
|
||||
+613
@@ -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;
|
||||
}
|
||||
+11
@@ -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);
|
||||
+267
@@ -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
|
||||
+106
@@ -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
|
||||
+317
@@ -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;
|
||||
}
|
||||
+12
@@ -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);
|
||||
+364
@@ -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
|
||||
+111
@@ -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
|
||||
+135
@@ -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
|
||||
+79
@@ -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
|
||||
+682
@@ -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
|
||||
+322
@@ -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
|
||||
+1318
File diff suppressed because it is too large
Load Diff
+274
@@ -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
|
||||
+283
@@ -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
|
||||
+106
@@ -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
|
||||
+70
@@ -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)
|
||||
+20
@@ -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()
|
||||
Reference in New Issue
Block a user