This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,44 @@
PROJECT(openvibe-plugins-sdk-signal-processing)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION_MINOR ${OV_GLOBAL_VERSION_MINOR})
SET(PROJECT_VERSION_PATCH ${OV_GLOBAL_VERSION_PATCH})
SET(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.hpp src/*.inl)
INCLUDE("FindSourceDependencyWavelib")
INCLUDE("FindSourceRCProperties")
INCLUDE("FindSourceDependencyDSPFilters")
INCLUDE("FindSourceDependencyR8Brain")
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindOpenViBEModuleSystem")
INCLUDE("FindOpenViBEModuleFS")
INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyEigen")
# ---------------------------------
# Target macros
# Defines target operating system, architecture and compiler
# ---------------------------------
SET_BUILD_PLATFORM()
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
INSTALL(DIRECTORY box-tutorials/ DESTINATION ${DIST_DATADIR}/openvibe/scenarios/box-tutorials)
@@ -0,0 +1,472 @@
<OpenViBE-Scenario>
<FormatVersion>1</FormatVersion>
<Creator>OpenVIBE</Creator>
<CreatorVersion>0.2.99</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x0000478e, 0x000023b0)</Identifier>
<Name>Continuous Oscilloscope - All Channels</Name>
<AlgorithmClassIdentifier>(0x0842bcd1, 0xd53c1c89)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Matrix</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Markers</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Channel Localisation</Name>
<DefaultValue>${AdvancedViz_ChannelLocalisation}</DefaultValue>
<Value>${AdvancedViz_ChannelLocalisation}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x8f02e3f6, 0xffb00f4b)</TypeIdentifier>
<Name>Temporal Coherence</Name>
<DefaultValue>Time Locked</DefaultValue>
<Value>Time Locked</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Time Scale</Name>
<DefaultValue>20</DefaultValue>
<Value>20</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Matrix Count</Name>
<DefaultValue>50</DefaultValue>
<Value>50</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Positive Data Only ?</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Gain</Name>
<DefaultValue>1</DefaultValue>
<Value>0.1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Caption</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Translucency</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x7f45a2a9, 0x7db12219)</TypeIdentifier>
<Name>Color</Name>
<DefaultValue>${AdvancedViz_DefaultColor}</DefaultValue>
<Value>${AdvancedViz_DefaultColor}</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>192</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>176</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x35390ab5, 0x7b926078)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>2</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x0000478e, 0x000023b1)</Identifier>
<Name>Continuous Oscilloscope - 1st and 4th</Name>
<AlgorithmClassIdentifier>(0x0842bcd1, 0xd53c1c89)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Matrix</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Markers</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Channel Localisation</Name>
<DefaultValue>${AdvancedViz_ChannelLocalisation}</DefaultValue>
<Value>${AdvancedViz_ChannelLocalisation}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x8f02e3f6, 0xffb00f4b)</TypeIdentifier>
<Name>Temporal Coherence</Name>
<DefaultValue>Time Locked</DefaultValue>
<Value>Time Locked</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Time Scale</Name>
<DefaultValue>20</DefaultValue>
<Value>20</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Matrix Count</Name>
<DefaultValue>50</DefaultValue>
<Value>50</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Positive Data Only ?</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Gain</Name>
<DefaultValue>1</DefaultValue>
<Value>0.1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Caption</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Translucency</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x7f45a2a9, 0x7db12219)</TypeIdentifier>
<Name>Color</Name>
<DefaultValue>${AdvancedViz_DefaultColor}</DefaultValue>
<Value>${AdvancedViz_DefaultColor}</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>272</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>368</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x35390ab5, 0x7b926078)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>2</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x2fd52ae0, 0x1b328bca)</Identifier>
<Name>Sinus oscillator</Name>
<AlgorithmClassIdentifier>(0x7e33bdb8, 0x68194a4a)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Generated signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Channel count</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Sampling frequency</Name>
<DefaultValue>512</DefaultValue>
<Value>512</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Generated epoch sample count</Name>
<DefaultValue>32</DefaultValue>
<Value>32</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>304</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x0b214ed8, 0x1f9ad83a)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
<Value>(0x00000000, 0x00f4f93b)</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x7f7581af, 0x105dc85a)</Identifier>
<Name>Channel Selector</Name>
<AlgorithmClassIdentifier>(0x361722e8, 0x311574e8)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Input signal</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Channel List</Name>
<DefaultValue>-</DefaultValue>
<Value>1;4</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x3bcf9e67, 0x0c23994d)</TypeIdentifier>
<Name>Action</Name>
<DefaultValue>Select</DefaultValue>
<Value>Select</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x666f25e9, 0x3e5738d6)</TypeIdentifier>
<Name>Channel Matching Method</Name>
<DefaultValue>Smart</DefaultValue>
<Value>Smart</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>192</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>368</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x277826e1, 0xa30a3bd0)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>3</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00001b0a, 0x0000298c)</Identifier>
<Source>
<BoxIdentifier>(0x2fd52ae0, 0x1b328bca)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x0000478e, 0x000023b0)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x0000411f, 0x00002266)</Identifier>
<Source>
<BoxIdentifier>(0x7f7581af, 0x105dc85a)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x0000478e, 0x000023b1)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x0aedc529, 0x0f063868)</Identifier>
<Source>
<BoxIdentifier>(0x2fd52ae0, 0x1b328bca)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x7f7581af, 0x105dc85a)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments>
<Comment>
<Identifier>(0x13d53040, 0x7cf98040)</Identifier>
<Text>The &lt;i&gt;&lt;b&gt;Channel Selector&lt;/b&gt;&lt;/i&gt; box takes only
the &lt;b&gt;first&lt;/b&gt; and &lt;b&gt;fourth&lt;/b&gt; channel</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>608</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>192</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x36543bdd, 0x0ac76412)</Identifier>
<Text>Finally, the right &lt;i&gt;Continuous Oscilloscope&lt;/i&gt; box
displays the 2 selected channels</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>624</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>272</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x3ce87297, 0x1174114b)</Identifier>
<Text>Those 4 channels are displayed in the left
&lt;i&gt;Continuous Oscilloscope&lt;/i&gt; box</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>608</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>112</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x49fe67ab, 0x300e3f94)</Identifier>
<Text>The &lt;i&gt;Sinus Oscillator&lt;/i&gt; box generates
a 4 channels sinusoidal signal</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>592</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>32</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x5d643115, 0x6ebb4527)</Identifier>
<Text>You can browse each box' documentation by selecting the box and pressing &lt;b&gt;F1&lt;/b&gt;</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>464</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>352</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<Metadata>
<Entry>
<Identifier>(0x0000775c, 0x000078ff)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":678,"identifier":"(0x02b6d4f0, 0x103dc3fd)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":950},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x1722ecef, 0x1695d0b2)","index":0,"name":"Default tab","parentIdentifier":"(0x02b6d4f0, 0x103dc3fd)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":2,"dividerPosition":463,"identifier":"(0x00004bd5, 0x00001389)","index":0,"maxDividerPosition":930,"name":"Horizontal split","parentIdentifier":"(0x1722ecef, 0x1695d0b2)","type":5},{"boxIdentifier":"(0x0000478e, 0x000023b0)","childCount":0,"identifier":"(0x00002c70, 0x000006ef)","index":0,"parentIdentifier":"(0x00004bd5, 0x00001389)","type":3},{"boxIdentifier":"(0x0000478e, 0x000023b1)","childCount":0,"identifier":"(0x000048d3, 0x0000443a)","index":1,"parentIdentifier":"(0x00004bd5, 0x00001389)","type":3}]</Data>
</Entry>
</Metadata>
<Attributes>
<Attribute>
<Identifier>(0x4c536d0a, 0xb23dc545)</Identifier>
<Value>channel-selector.mxs</Value>
</Attribute>
</Attributes>
</OpenViBE-Scenario>
@@ -0,0 +1,418 @@
<OpenViBE-Scenario>
<FormatVersion>1</FormatVersion>
<Creator>OpenVIBE</Creator>
<CreatorVersion>0.0.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x00004a0e, 0x000044ab)</Identifier>
<Name>Continuous Oscilloscope</Name>
<AlgorithmClassIdentifier>(0x0842bcd1, 0xd53c1c89)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Matrix</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Markers</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Channel Localisation</Name>
<DefaultValue>${AdvancedViz_ChannelLocalisation}</DefaultValue>
<Value>${AdvancedViz_ChannelLocalisation}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x8f02e3f6, 0xffb00f4b)</TypeIdentifier>
<Name>Temporal Coherence</Name>
<DefaultValue>Time Locked</DefaultValue>
<Value>Time Locked</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Time Scale</Name>
<DefaultValue>20</DefaultValue>
<Value>20</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Matrix Count</Name>
<DefaultValue>50</DefaultValue>
<Value>50</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Positive Data Only ?</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Gain</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Caption</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Translucency</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x7f45a2a9, 0x7db12219)</TypeIdentifier>
<Name>Color</Name>
<DefaultValue>${AdvancedViz_DefaultColor}</DefaultValue>
<Value>${AdvancedViz_DefaultColor}</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>336</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>320</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x35390ab5, 0x7b926078)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc67a01dc, 0x28ce06c1)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>2</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x4ad3afd2, 0x3cf93093)</Identifier>
<Name>Crop</Name>
<AlgorithmClassIdentifier>(0x7f1a3002, 0x358117ba)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Input matrix</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output matrix</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0xd0643f9e, 0x8e35fe0a)</TypeIdentifier>
<Name>Crop method</Name>
<DefaultValue>Min</DefaultValue>
<Value>Min/Max</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Min crop value</Name>
<DefaultValue>-1</DefaultValue>
<Value>-3.000000</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Max crop value</Name>
<DefaultValue>1</DefaultValue>
<Value>3.000000</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>224</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>320</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x1b151919, 0x63b9f9c9)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
<Value>(0x00000000, 0x005c9c00)</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x4b232ee3, 0x159c74d0)</Identifier>
<Name>Simple DSP</Name>
<AlgorithmClassIdentifier>(0x00e26fa1, 0x1dbab1b2)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Input - A</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Equation</Name>
<DefaultValue>x</DefaultValue>
<Value>4 * cos(X)</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>112</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>320</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x21889dc4, 0x1126497e)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x6863e8d2, 0x76896199)</Identifier>
<Name>Time signal</Name>
<AlgorithmClassIdentifier>(0x28a5e7ff, 0x530095de)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Generated signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Sampling frequency</Name>
<DefaultValue>512</DefaultValue>
<Value>512</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Generated epoch sample count</Name>
<DefaultValue>32</DefaultValue>
<Value>32</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>320</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x9e5ca01e, 0x30a4d8c3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc46b3d00, 0x3e0454e1)</Identifier>
<Value>(0x00000000, 0x00903800)</Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x0000194d, 0x000013f5)</Identifier>
<Source>
<BoxIdentifier>(0x4ad3afd2, 0x3cf93093)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00004a0e, 0x000044ab)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x1a54d7bc, 0x42e57ef8)</Identifier>
<Source>
<BoxIdentifier>(0x4b232ee3, 0x159c74d0)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x4ad3afd2, 0x3cf93093)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x773bfbdd, 0x2c7c62c3)</Identifier>
<Source>
<BoxIdentifier>(0x6863e8d2, 0x76896199)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x4b232ee3, 0x159c74d0)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments>
<Comment>
<Identifier>(0x04e5dcb5, 0x41e96bdc)</Identifier>
<Text>The &lt;i&gt;&lt;b&gt;Crop&lt;/b&gt;&lt;/i&gt; box cuts the signal
to a minimum of &lt;b&gt;-3&lt;/b&gt; and a maximum of
&lt;b&gt;+3&lt;/b&gt;. If the signal gets lower to the minimum
or higher to the maximum, the sample value is simply
replaced by the actual minimum or the actual maximum.</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>576</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>240</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x0805b331, 0x060a1464)</Identifier>
<Text>Finally, the &lt;i&gt;Signal Display&lt;/i&gt; box
displays the result.</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>576</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>336</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x0a045ab2, 0x18722620)</Identifier>
<Text>The &lt;i&gt;Time Signal&lt;/i&gt; box generates
a 1 channel linear signal (&lt;i&gt;f(t)=t&lt;/i&gt;)</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>576</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>48</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x162eb70d, 0x78070223)</Identifier>
<Text>The &lt;i&gt;Simple DSP&lt;/i&gt; box applies a simple
function to each sample. This function is
&lt;i&gt;4 * cos(X)&lt;/i&gt; so the output signal is a
sinusoid in the [-4 +4] range.</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>576</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>128</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x5d5ce761, 0x1cd484d6)</Identifier>
<Text>You can browse each box' documentation by selecting the box and pressing &lt;b&gt;F1&lt;/b&gt;</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>400</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<Metadata>
<Entry>
<Identifier>(0x0000775c, 0x000078ff)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0x00004a0e, 0x000044ab)","childCount":0,"identifier":"(0x000020ad, 0x000032d0)","index":0,"parentIdentifier":"(0xffffffff, 0xffffffff)","type":3},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":1,"identifier":"(0x57071613, 0x5293bfef)","index":0,"name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":1},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x38bf53e5, 0x226f5320)","index":0,"name":"Default tab","parentIdentifier":"(0x57071613, 0x5293bfef)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x5a0201c7, 0x2a9c34e2)","index":0,"name":"Empty","parentIdentifier":"(0x38bf53e5, 0x226f5320)","type":0}]</Data>
</Entry>
</Metadata>
</OpenViBE-Scenario>
@@ -0,0 +1,66 @@
/**
* \page BoxAlgorithm_ChannelRename Channel Rename
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Description|
* This box renames the input channels with whatever name the author wants. The names
* should be separated by a semi-column ';'
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Inputs|
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Inputs|
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Input1|
* The input matrix which channels should be renamed. The type of this input can be changed to
* signal or spectrum depending on what kind of stream channel to rename.
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Outputs|
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Outputs|
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Output1|
* The output matrix with renamed channels. The type of this output can be changed to
* signal or spectrum depending on what kind of stream channel to rename.
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Settings|
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Settings|
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Setting1|
* A semi-colon separated list of new channel names.
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Examples|
* Suppose you compute the delta, alpha and beta band power at location Cz and merge the three
* streams in a single stream. The resulting stream will handle three channels all named Cz. For
* convenience, it could be useful to rename those channels Delta, Alpha and Beta respectively.
* In order to achieve this, use a \ref Doc_BoxAlgorithm_ChannelRename with the following setting
* value : "Delta;Alpha;Beta"
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelRename_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_ChannelRename_Miscellaneous|
*/
@@ -0,0 +1,83 @@
/**
* \page BoxAlgorithm_ChannelSelector Channel Selector
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Description|
* This box allows to restrict outgoing signal data to a subset of incoming data based on a list of channels.
* Channels may be identified by their index, their name (the case matters), or a mix of both. Additionally,
* the channels can be rejected instead of being selected.
* The name of the box displayed in the designer is the channel list (first setting). It allows the user to see directly if the box configuration is correct.
* However, should the user chose to rename the box manually, further change in the configuration won't be reflected in the name, except if the name is set
* back to its default value.
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Inputs|
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Inputs|
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Input1|
* The input matrix which channels should be selected or rejected. The type of this input can be changed to
* Signal, Spectrum or Streamed matrix depending on what kind of stream channel to select.
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Outputs|
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Outputs|
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Output1|
* The output matrix with selected or preserved channels. The type of this output can be changed to
* signal or spectrum depending on what kind of stream channel to select.
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Settings|
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Settings|
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Setting1|
* A semi colon separated list of channel identifiers. You can use the index of the channel or the name of the
* channel. Also, ranges can be selected specifying first channel identifier, followed by a colon, followed by
* the second channel identifier.
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Setting1|
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Setting2|
* The action to perform on the identifier channel, be :
* - \c select,
* - \c reject,
* - \c select \c EEG, selecting all EEG channels using their names. In this case, Channel List is no more considered.
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Setting2|
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Setting3|
* The kind of identification for channel list.
* - \c Smart let the box try to detect if the channel identifier is an index or a name
* - \c Name forces the channel identifiers to be considered as channel names. This can be useful if channel names are numbers.
* - \c Index forces the channel identifiers to be considered as channel indices. This can be useful if channel names are numbers.
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Examples|
* Suppose you want to select the first 8 channels of an input stream, plus you want the Cz electrode and the last 3 channels.
* You would then use the following string : [1:8;Cz;-3:-1].
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ChannelSelector_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_ChannelSelector_Miscellaneous|
*/
@@ -0,0 +1,62 @@
/**
* \page BoxAlgorithm_CommonAverageReferenceFilter Common average reference filter
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Description|
Re-referencing the signal to common average reference consists in subtracting
to each sample the average value of the samples of all electrodes at this time
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Inputs|
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Inputs|
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Input1|
The input signals, to be re-referenced to common average reference
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Outputs|
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Outputs|
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Output1|
the output is equal to the input signals re-referenced to common average reference
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Output1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Examples|
Connect the "Signal" output of the "Acquisition client" box to this "Common Average Reference" (CAR), then
use the output signal for your BCI or any other applications. The CAR is a spatial filter
commonly employed in EEG-based BCI. This can reduce the noise which is present globally over all electrodes.
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_CommonAverageReferenceFilter_Miscellaneous|
the input EEG signals should be recorded with more than one electrode, otherwise this box is useless.
* |OVP_DocEnd_BoxAlgorithm_CommonAverageReferenceFilter_Miscellaneous|
*/
@@ -0,0 +1,147 @@
/**
* \page BoxAlgorithm_ContinuousWaveletAnalysis Continuous Wavelet Analysis
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Description|
The Continuous Wavelet Transform (CWT) provides a Time-Frequency representation of an input signal, using Morlet, Paul or derivative of Gaussian wavelets.
Considering an input signal \f$ X \in \mathbb{R}^{C \times N} \f$, composed of \f$ C \f$ channels and \f$ N \f$ temporal samples,
this plugin computes the CWT of this signal \f$ \Phi \in \mathbb{C}^{C \times F \times N} \f$, composed of \f$ C \f$ channels, \f$ F \f$ scales and \f$ N \f$ temporal samples.
For the \f$ c^{ \text{th} } \f$ channel, the \f$ f^{ \text{th} } \f$ scale \f$ s_f \f$ and the \f$ n^{ \text{th} } \f$ sample, the Time-Frequency representation is defined as:
\f[ \Phi (c,f,n) = \sum_{n'=0}^{N-1} X(c,n') \ \psi^{*} \left( \frac{(n-n') \delta t}{s_f} \right) \ , \f]
where \f$ \psi \f$ is the normalized wavelet, \f$ (.)^{*} \f$ is the complex conjugate and \f$ \delta t \f$ is the sampling period.
Using the inverse relation between wavelet scale \f$ s_f \f$ and Fourier frequency \f$ \text{freq}_f \f$, output is finally defined as:
\f[ \Phi(c,f,n) = \Phi_r(c,f,n) + \mathsf{i} \times \Phi_i(c,f,n) = \left| \Phi(c,f,n) \right| \times e^{\mathsf{i} \arg(\Phi(c,f,n))} \ , \f]
with \f$ \mathsf{i} \f$ being the imaginary unit.
Output can be visualized with a \ref Doc_BoxAlgorithm_InstantBitmap3DStream.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Inputs|
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Inputs|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Input1|
An input multichannel signal \f$ X \in \mathbb{R}^{C \times N} \f$, composed of \f$ C \f$ channels and \f$ N \f$ temporal samples.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Outputs|
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Outputs|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Output1|
An output spectral amplitude (absolute value) \f$ \left| \Phi \right| \in \mathbb{R}^{C \times F \times N} \f$.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Output1|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Output2|
An output spectral phase \f$ \arg(\Phi) \in \mathbb{R}^{C \times F \times N} \f$, in radians.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Output2|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Output3|
An output real part of the spectrum \f$ \Phi_r \in \mathbb{R}^{C \times F \times N} \f$.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Output3|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Output4|
An output imaginary part of the spectrum \f$ \Phi_i \in \mathbb{R}^{C \times F \times N} \f$.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Output4|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Settings|
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Settings|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Setting1|
This setting defines the type of the wavelet:
- Morlet:
\f[ \psi_0 (n) = \pi^{1/4} e^{\mathsf{i} \omega_0 n} e^{-n^2 / 2} \ , \f]
- Paul:
\f[ \psi_0 (n) = \frac{2^m \mathsf{i}^m m!}{\sqrt{\pi(2m)!}} (1-\mathsf{i} n)^{-(m+1)} \ , \f]
- derivative of Gaussian:
\f[ \psi_0 (n) = \frac{(-1)^{m+1}}{\sqrt{\Gamma(m+\frac{1}{2})}} \frac{d^m}{d n^m} (e^{-n^2 / 2}) \ . \f]
\n
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Setting1|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Setting2|
This setting defines the wavelet parameter:
- Morlet wavelet: nondimensional frequency \f$ \omega_0 \f$, real positive parameter value. Values between 4.0 and 6.0 are typically used.
- Paul wavelet: order \f$ m \f$, positive integer values inferior to 20. Default value is 4.
- Derivative of Gaussian wavelet: derivative \f$ m \f$, positive even integer values. Value 2 gives the Marr or Mexican hat wavelet.
\n
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Setting2|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Setting3|
This setting defines the number of frequencies \f$ F \f$ of the CWT.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Setting3|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Setting4|
This setting defines the highest frequency \f$ \text{freq}_F \f$ of the CWT.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Setting4|
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Setting5|
This setting is related to the frequency non-linear spacing of the CWT.
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Setting5|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Examples|
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ContinuousWaveletAnalysis_Miscellaneous|
Reference:
C Torrence and GP Compo, <em>A Practical Guide to Wavelet Analysis</em>, Bulletin of the American Meteorological Society, vol. 79, pp. 61–78, 1998
* |OVP_DocEnd_BoxAlgorithm_ContinuousWaveletAnalysis_Miscellaneous|
*/
@@ -0,0 +1,66 @@
/**
* \page BoxAlgorithm_Crop Crop
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Crop_Description|
This box allows to set minimum and/or maximum thresholds to incoming data. Values lying outside the allowed range are cropped to it.
* |OVP_DocEnd_BoxAlgorithm_Crop_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Crop_Inputs|
* |OVP_DocEnd_BoxAlgorithm_Crop_Inputs|
* |OVP_DocBegin_BoxAlgorithm_Crop_Input1|
* |OVP_DocEnd_BoxAlgorithm_Crop_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Crop_Outputs|
* |OVP_DocEnd_BoxAlgorithm_Crop_Outputs|
* |OVP_DocBegin_BoxAlgorithm_Crop_Output1|
* |OVP_DocEnd_BoxAlgorithm_Crop_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Crop_Settings|
* |OVP_DocEnd_BoxAlgorithm_Crop_Settings|
* |OVP_DocBegin_BoxAlgorithm_Crop_Setting1|
Method to use to crop incoming data. A minimum and/or a maximum threshold(s) may be defined.
* |OVP_DocEnd_BoxAlgorithm_Crop_Setting1|
* |OVP_DocBegin_BoxAlgorithm_Crop_Setting2|
Minimum threshold.
* |OVP_DocEnd_BoxAlgorithm_Crop_Setting2|
* |OVP_DocBegin_BoxAlgorithm_Crop_Setting3|
Maximum threshold.
* |OVP_DocEnd_BoxAlgorithm_Crop_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Crop_Examples|
* |OVP_DocEnd_BoxAlgorithm_Crop_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Crop_Miscellaneous|
Note : the type of input data may be changed. Allowed types include streamed matrix, feature vector, signal and spectrum.
* |OVP_DocEnd_BoxAlgorithm_Crop_Miscellaneous|
*/
@@ -0,0 +1,171 @@
/**
* \page BoxAlgorithm_EpochAverage Epoch average
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Description|
* This box offers several methods of averaging for epoched streams.
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Inputs|
* The input type of this box can be changed. Its type must be derived of
* type \ref Doc_Streams_StreamedMatrix in order to be parsed by the input
* reader. If the author changes the input type, the output type will
* be changed the same way.
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Inputs|
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Input1|
* This input receives the input streamed matrix to average.
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Outputs|
* The output type of this box can be changed. Its type must be derived of
* type \ref Doc_Streams_StreamedMatrix in order for the writer to format
* the output chunks. If the author changes the output type, the input
* type will be changed the same way.
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Outputs|
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Output1|
* This output sends the averaged streamed matrix. Averaging method is done
* according to the box settings.
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Settings|
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Settings|
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Setting1|
* This setting gives the method to use in order to average the input
* matrices. It can be of two types :
* - <em>Moving average</em> : in this case, the averaging is done at
* every input reception on the last few buffers, starting as soon
* as enough input has been received.
* - <em>Moving average (Immediate)</em> : in this case, the averaging is done at
* every input reception on the last few buffers, starting immediately. When
* the number of received buffer is lower than the wished number of epochs, the
* average is computed on this very few number of input buffers.
* - <em>Epoch block average</em> : in this case, the averaging
* is done on a number of epochs (see next setting). Once this exact
* number of input is received, the average is computed and output.
* - <em>Cumulative average</em> : in this case, the averaging
* is done on an infinite number of epochs starting from the first
* received buffer to the last received buffer. This can be \b very
* memory consuming !
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Setting1|
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Setting2|
* This setting tells the box how much buffer it should use in order to
* compute the average.
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Setting2|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Examples|
* Let's study two cases. First, suppose you have such box with
* <em>Epoch block average</em> set and <em>four</em> epochs.
* The input stream is as follows :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* The output stream will look like this :
\verbatim
+----+ +----+
| O1 | | O2 | ...
+----+ +----+
\endverbatim
* where \c O1 is the average of \c I1, \c I2, \c I3 and \c I4 and
* where \c O2 is the average of \c I5, \c I6, \c I7 and \c I8.
*
* Now consider the case where you configured this box with
* <em>Moving average</em> and <em>four</em> epochs. Given the
* same input stream :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* The output stream will look like this :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+
| O1 | | O2 | | O3 | | O4 | | O5 | | O6 | ...
+----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* where :
* - \c O1 is the average of \c I1, \c I2, \c I3 and \c I4
* - \c O2 is the average of \c I2, \c I3, \c I4 and \c I5
* - \c O3 is the average of \c I3, \c I4, \c I5 and \c I6
* - \c O4 is the average of \c I4, \c I5, \c I6 and \c I7
* - etc...
*
* Again consider the case where you configured this box with
* <em>Moving average (Immediate)</em> and <em>four</em> epochs. Given the
* same input stream :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* The output stream will look like this :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+
| O1 | | O2 | | O3 | | O4 | | O5 | | O6 | ...
+----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* where :
* - \c O1 is exactly \c I1
* - \c O2 is the average of \c I1 and \c I2
* - \c O3 is the average of \c I1, \c I2 and \c I3
* - \c O4 is the average of \c I1, \c I2, \c I3 and \c I4
* - \c O5 is the average of \c I2, \c I3, \c I4 and \c I5
* - \c O6 is the average of \c I3, \c I4, \c I5 and \c I6
* - etc...
*
* Finally consider the case where you configured this box with
* <em>Cumulative average</em> and <em>four</em> epochs. Given the
* same input stream :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* The output stream will look like this :
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+
| O1 | | O2 | | O3 | | O4 | | O5 | | O6 | ...
+----+ +----+ +----+ +----+ +----+ +----+
\endverbatim
* where :
* - \c O1 is exactly \c I1
* - \c O2 is the average of \c I1 and \c I2
* - \c O3 is the average of \c I1, \c I2 and \c I3
* - \c O4 is the average of \c I1, \c I2, \c I3 and \c I4
* - \c O5 is the average of \c I1, \c I2, \c I3, \c I4, and \c I5
* - \c O6 is the average of \c I1, \c I2, \c I3, \c I4, \c I5, and \c I6
* - etc...
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_EpochAverage_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_EpochAverage_Miscellaneous|
*/
@@ -0,0 +1,64 @@
/**
* \page BoxAlgorithm_FrequencyBandSelector Frequency Band Selector
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Description|
* This box selects a subset of a spectrum matrix, turning all un selected frequency
* band to 0.
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Inputs|
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Inputs|
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Input1|
* The input spectrum to select from.
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Outputs|
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Outputs|
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Output1|
* The modified spectrum with unselected bands turned to 0
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Settings|
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Settings|
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Setting1|
* The range of frequencies you want to select.
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Examples|
* Suppose you have a full band spectrum and that you want to work on alpha band. You can use
* a \ref Doc_BoxAlgorithm_FrequencyBandSelector box to select this band for later processing
* with the following setting : "8:12". Now suppose you want to work on two bands, for instance
* Alpha and the 16-24Hz subset of Beta, you should use the following setting value : "8:12[SEMICOLON]16:24"
* with [SEMICOLON] replaced by the actual semicolon charater.
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_FrequencyBandSelector_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_FrequencyBandSelector_Miscellaneous|
*/
@@ -0,0 +1,55 @@
/**
* \page BoxAlgorithm_Identity Identity
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Identity_Description|
* This box simply duplicates inputs to corresponding outputs in a similar way
* |OVP_DocEnd_BoxAlgorithm_Identity_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Identity_Inputs|
* This box can have any number of input of any type. Each input has a corresponding
* output with the same type. Incoming chunks are simply duplicated to the corresponding output.
* |OVP_DocEnd_BoxAlgorithm_Identity_Inputs|
* |OVP_DocBegin_BoxAlgorithm_Identity_Input1|
* The default input for this box.
* |OVP_DocEnd_BoxAlgorithm_Identity_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Identity_Outputs|
* This box can have any number of output of any type. Each output has a corresponding
* input with the same type. Incoming chunks are simply duplicated to the corresponding output.
* |OVP_DocEnd_BoxAlgorithm_Identity_Outputs|
* |OVP_DocBegin_BoxAlgorithm_Identity_Output1|
* The default output for this box.
* |OVP_DocEnd_BoxAlgorithm_Identity_Output1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Identity_Examples|
* This box could be used in order to quickly switch between acquisition or file reading mode.
* At the very top of the scenario, you would put an identity box with four connectors of type
* \ref Doc_Streams_ExperimentInfo, \ref Doc_Streams_Signal, \ref Doc_Streams_Stimulation and
* \ref Doc_Streams_ChannelLocalisation.
* |OVP_DocEnd_BoxAlgorithm_Identity_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Identity_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_Identity_Miscellaneous|
*/
@@ -0,0 +1,60 @@
/**
* \page BoxAlgorithm_ReferenceChannel Reference channel
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Description|
* This plugin subtracts the values of the samples from a reference
* channel from the other channels' samples.
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Inputs|
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Inputs|
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Input1|
* The input signal.
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Outputs|
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Outputs|
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Output1|
* The resulting signal.
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Settings|
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Settings|
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Setting1|
* Index of the reference channel in the input stream.
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Examples|
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ReferenceChannel_Miscellaneous|
* The reference channel is kept in the resulting signal.
* |OVP_DocEnd_BoxAlgorithm_ReferenceChannel_Miscellaneous|
*/
@@ -0,0 +1,146 @@
/**
* \page BoxAlgorithm_RegularizedCSPTrainer Shrinkage CSP Trainer
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Description|
The box estimates a filter bank of spatial filters using Common Spatial Patterns (CSP) algorithm. The algorithm tries to find a linear transform of the data that
makes the signal conditions (or classes) more distinct, when the data is projected to the matrix of the filters found by the algorithm (i.e. s=Wx, where we
assume x is the signal sample, W is the discovered filter bank, and s a lower-dimensional representation of the sample). The spatial filters are constructed
for two conditions to maximize the variance of the signals of the first condition while at the same time minimizing it for the second condition.
If signal variance contains discriminatory information, CSP filters can be useful in classification situations where designing spatial filter masks
such as Laplacians manually is cumbersome or when it is believed that a filter optimized to the current data/user would produce better
results than using an one-size-fits all filter.
In principle CSP can be useful in experiments where there is discriminative information in the variance (or power) of the signal conditions,
for example motor imagery or SSVEP.
The box implements some of the methods described e.g. by Lotte & Guan [1]. Especially, it
allows using the methods called "CSP with Diagonal Loading" and "CSP with Tikhonov Regularization" in the paper. In the approach of
the paper, the filters for condition 1 are found by eigenvector decomposition of inv(Sigma1+rho*I)*Sigma2, where Sigma1 and Sigma2 are
empirical covariance matrices for the two conditions, rho the amount of Tikhonov regularization and I an identity matrix. For condition 2,
the formula is the same with the sigmas swapped. The matrices Sigma1 and Sigma2 may be optionally shrunk towards diagonal matrices.
In situations with more than two conditions, the box implements a one-vs-all approach, where each class gives a Sigma1 in turn, with the
covariances of the other classes combined with a class frequency weighted average to obtain Sigma2. Hence, for k classes, k pairings
are estimated, and out of each pair, a requested amount of filters is selected separately. This results in k times the amount of filters
requested in total.
To avoid caching the whole dataset in the box, the algorithm tries to estimate the required covariances incrementally. The box supports
a few different incremental ways to compute the covariances. The 'block average' approach takes an average of all the covariances of the incoming
signal chunks, an approach described in [2], whereas the incremental (per sample) method aims to implement Youngs & Cramer
algorithm as described in [3]. Which one gives better results may depend on the situation -- however,
taking an average of covariance matrices is not your usual textbook method for computing covariance over the whole data.
Finally, the box also presents an option to try to compensate for the effect of changes in the average signal power over time. This is
can be done by enabling 'Trace Normalization' setting. When trace normalization is enabled, each data chunks contribution to
the covariance gets divided by its trace. See miscellaneous notes for details.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Inputs|
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Inputs|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Input1|
This stimulus input is needed to indicate the end of a recording session (or end of file). It triggers the training/computation of the CSP filters.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Input1|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Input2|
This input expects epoched data for the first condition (e.g. epochs for left hand motor imagery).
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Input2|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Input3|
This input expects epoched data for the second condition (e.g. epochs for right hand motor imagery).
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Input3|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Outputs|
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Outputs|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Output1|
The CSP Trainer outputs the stimulation <b>OVTK_StimulationId_TrainCompleted</b> when the training process was successful. No output is produced if the process failed.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Settings|
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Settings|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting1|
The stimulus identifier denoting the end of a recording session or end of file, e.g. OVTK_GDF_End_Of_Session or OVTK_StimulationId_ExperimentStop.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting1|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting2|
The path and filename of the configuration file in which the computed spatial filters are saved.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting2|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting3|
How many spatial filters should be selected per class.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting3|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting4|
If true, the file written will be a box configuration override especially for the spatial filter box. Otherwise, it will be an ASCII matrix.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting4|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting5|
Method to update the covariances
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting5|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting6|
Is trace normalization done when appending the per-chunk covariances?
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting6|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting7|
The amount of shrinkage, between 0 and 1. It interpolates between the covariance matrices and the diagonal matrix.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting7|
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Setting8|
The amount of Tikhonov regularization, bigger signifies more. This equals parameter rho in the description above. If 0, the method behaves approximately as a regular CSP.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Setting8|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_RegularizedCSPTrainer_Miscellaneous|
The suitable amount of regularization may depend on the variance of the data. You may need to try different values to find the one that suits your situation best.
Before the CSP training, it may be useful to temporally filter the input data to remove bands which are believed to have no relevant discriminative information.
Note that the usage of the CSP filters before classification training can make the cross-validation results optimistic, unless strictly non-overlapping parts of the data were used to train the CSP and the classifier (disjoint sets for each).
The trace normalization can be found in the literature [2]. The idea is to normalize the scale of each chunk in order to
compensate for a possible signal power drift over time during the EEG recording, making each chunks' covariance contribute
similarly to the aggregate regardless of the current chunks average power.
To get the "CSP with Diagonal Loading" of Lotte & Guan paper [1], set shrinkage to a positive value and Tikhonov to 0. To get the
"CSP with Tikhonov regularization", do the opposite. You can also try a mixture of the two. Note that the Guan & Lotte paper does not appear to use trace normalization.
To get the CSP resembling the one in the Muller-Gerkin paper, set Trace Normalization to True and the Covariance method to Chunk Average, with no regularization. Then, feed the algorithm each trial as a separate chunk (with Stimulation based epoching box). This is also the classic OV way of computing the CSP.
Once the spatial filters are computed and saved, you can load and apply the filters with the \ref Doc_BoxAlgorithm_SpatialFilter "Spatial Filter" box.
References
1) Lotte & Guan: "Regularizing common spatial patterns to Improve BCI Designs: Unified Theory and New Algorithms", 2011.
2) Muller-Gerkin & al., "Designing optimal spatial filters for single-trial EEG classification in a movement task", 1999.
3) Chan, Golub & Leveq, "Updating formulae and a pairwise algorithm for computing sample variances", 1979.
* |OVP_DocEnd_BoxAlgorithm_RegularizedCSPTrainer_Miscellaneous|
*/
@@ -0,0 +1,51 @@
/**
* \page BoxAlgorithm_SignalAverage Signal average
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Description|
* This plugin computes the average of each incoming sample
* buffer and outputs the resulting signal.
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Inputs|
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Input1|
* The input signal.
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Output1|
* Signal containing the averages of the input sample buffers.
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Output1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Examples|
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalAverage_Miscellaneous|
* The output signal's sample count per channel per buffer is one,
* since a buffer contains the averages (per channel) of the values
* of an input buffer.
* |OVP_DocEnd_BoxAlgorithm_SignalAverage_Miscellaneous|
*/
@@ -0,0 +1,73 @@
/**
* \page BoxAlgorithm_SignalDecimation Signal Decimation
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Description|
This box reduces the sampling frequency of an input signal <em>the easy way</em>.
It is different of the existing \ref Doc_BoxAlgorithm_Downsampling box in the sense
that there is no pre-filtering and that you can not choose an arbitrary sampling
frequency. Thus you will have to pre-filter the input signal with the
\ref Doc_BoxAlgorithm_TemporalFilter box for example. The new sampling frequency
will be an exact divider of the source signal sampling frequency. For instance, if your
input sampling frequency is 1000Hz, you are allowed to divide that frequency by 2 or 4
but you can't divide it by 3. See section \ref Doc_BoxAlgorithm_SignalDecimation_Examples
for a detailed example of what can be done.
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Inputs|
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Input1|
The input signal.
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Output1|
The decimated signal.
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Settings|
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Settings|
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Setting1|
The sampling rate divider. At each chunk reception, 1 sample among n of the input signal will be
sent to the output signal.
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Examples|
Suppose you have a signal with a sampling rate of 1000Hz streamed with 32 samples per buffer
that you want to downsample to 100Hz. Downsampling this signal to 100Hz will require that you
low-pass filter the signal to at most 50Hz to avoid bad results. Additionally,
Changing the epoch sizes using a the
\ref Doc_BoxAlgorithm_TimeBasedEpoching box and configuring it e.g. for epochs of 0.1s every
0.1s will cause this box to output 10 samples sized buffers.
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalDecimation_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_SignalDecimation_Miscellaneous|
*/
@@ -0,0 +1,81 @@
/**
* \page BoxAlgorithm_SignalResampling Signal Resampling
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Description|
This plugin can apply a downsampling or an upsampling, to/from any sampling frequency, including non-integer sampling rate conversion.
The signal is low-pass filtered with a FIR filter to avoid spectral aliasing, using a sinc function-based fractional delay filter bank.
If the sampling rate is badly conditioned, this plugin can induce latency.
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Inputs|
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Input1|
An input multichannel signal \f$ X \in \mathbb{R}^{C \times N_1} \f$, composed of \f$ C \f$ channels and \f$ N_1 \f$ temporal samples, at the sampling frequency \f$ F_1 \f$.
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Output1|
An output multichannel signal \f$ Y \in \mathbb{R}^{C \times N_2} \f$, composed of \f$ C \f$ channels and \f$ N_2 \f$ temporal samples, at the sampling frequency \f$ F_2 \f$.
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Settings|
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Settings|
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Setting1|
New sampling frequency \f$ F_2 > 0 \f$. No processing is applied if \f$ F_2 = F_1 \f$.
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Setting1|
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Setting2|
Number of temporal samples \f$ N_2 > 0 \f$ per resampled buffer.
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Setting2|
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Setting3|
Low-pass filtering activation, to avoid spectral aliasing.
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Examples|
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SignalResampling_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_SignalResampling_Miscellaneous|
*/
@@ -0,0 +1,171 @@
/**
* \page BoxAlgorithm_SimpleDSP Simple DSP
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Description|
* This plugin is used to apply a mathematical formulae to each sample of an incoming signal and output
* the resulting signal. It thus acts as a simple DSP.
*
* The author may add up to 15 additional inputs.
* In such circumstances, each input would be identified
* by a letter from \e A to \e P.
*
* Also the type of the inputs could be changed to any
* streamed matrix derived type. Thus you can process
* signal, spectrum or feature vector if you need.
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Inputs|
You can use from 1 to 16 inputs.
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Input1|
* Input signal
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Output1|
* Filtered signal.
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Settings|
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Settings|
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Setting1|
* Formula to apply to incoming data (identified as 'X'). See \ref Doc_BoxAlgorithm_SimpleDSP_Miscellaneous for more details.
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Examples|
* Let's consider that we want to compute the natural logarithm of the absolute value
* of the input signal plus one. We just have to type the equation like that :
* \code
* log(abs(X) + 1)
* \endcode
*
* Another example : if you want to sum the cosinus of X minus Pi with its sinus plus Pi,
* you can enter this equation :
* \code
* cos(X - M_PI) + sin(X + M_PI)
* \endcode
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SimpleDSP_Miscellaneous|
* The equation can use at most 16 variables, for 16 input signals.
* The variable names are the 16 first letters of the alphabet, i.e. 'a' (or 'A') to 'p' (or 'P') matches inputs 1 to 16.
* \b NB : The first input variable can be named 'x' or 'X'.
*
* Here is a list of supported functions/operators :
*
* - Operators
* - +
* - -
* - *
* - /
*
* - Unary functions
* - abs
* - acos
* - asin
* - atan
* - ceil
* - cos
* - exp
* - floor
* - log
* - log10
* - sin
* - sqrt
* - tan
*
* - Binary function
* - pow
*
* - Comparison operators
* - >
* - >=
* - <
* - <=
* - ==
* - = (equivalent to ==)
* - !=
* - <> (equivalent to !=)
*
* - Boolean operators
* - & as \e and
* - && also as \e and
* - | as \e or
* - || also as \e or
* - ! as \e not
* - ~ as \e xor
* - ^ also as \e xor
*
* - ternary operator
* - ? :
*
* There are also a few defined constants :
*
* - M_PI
* - M_PI_2
* - M_PI_4
* - M_1_PI
* - M_2_PI
* - M_2_SQRTPI
* - M_SQRT2
* - M_SQRT1_2
* - M_E
* - M_LOG2E
* - M_LOG10E
* - M_LN2
* - M_LN10
*
* (note : their meaning is the same as the constants of the same name in math.c)
*
* Furthermore, the equation parser is totally case-insensitive. So you can write "COS(m_pi+x)" or "cos(M_PI+X)", it doesn't matter.
*
* Don't worry about the whitespaces and blank characters, they are automatically skipped by the equation parser.
* That means, for instance, that both "X+1" and "X + 1" work.
*
* This plugin implements basic constant folding. That means that when the plugin analyses the equation,
* if it can compute some parts of it before compilation, it will. For now, it does not support rational
* equations simplification.
* |OVP_DocEnd_BoxAlgorithm_SimpleDSP_Miscellaneous|
*/
/*
Since the equation is translated into a set of function calls, it is quite slower than it would be if the equation was directly compiled into machine code. Consequently, the plugin uses a number of "built-in" simple equations, which will achieve significantly faster execution times.
Those equations are :
- X (identity)
- X*X or pow(X,2)
- X + Constant
- X - Constant
- X * Constant
- X / Constant
- Unaryfunction(X), where Unaryfunction is any of the previously introduced unary functions.
*/
@@ -0,0 +1,117 @@
/**
* \page BoxAlgorithm_SpatialFilter Spatial filter
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Description|
* The spatial filter generates a number of output channels from another number of input
* channels, each output channel being a linear combination of the input channels.
* For example, lets say <em>ICj</em> is the <em>j</em>th input channel, <em>OCk</em> is the
* <em>k</em>th output channel, and <em>Sjk</em> is the coefficient for the <em>j</em>th input
* channel and <em>k</em>th output channel in the <em>Spatial filter</em> matrix.
*
* Then the output channels are computed this way :
* <em>OCk</em> <b>=</b> Sum on <em>j</em> ( <em>Sjk</em> * <em>ICj</em> )
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Inputs|
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Input1|
* This input contains the input channels to mix.
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Output1|
* This output contains the generated channels, mixed from the input channels.
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Settings|
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Settings|
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Setting1|
* This setting contains a flat view of the spatial filter matrix. The coefficient orders is as follows :
* all the coefficients for the first output followed by all the coefficients for the second output and so on..
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Setting1|
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Setting2|
* Number of output channels to generate
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Setting2|
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Setting3|
* Number of input channels to compute from
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Setting3|
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Setting4|
* Filter matrix. You can alternatively provide the filter coefficients as an ASCII file.
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Setting4|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Examples|
* Let's consider the following example :
* - Input channels list: C3;C4;FC3;FC4;C5;C1;C2;C6;CP3;CP4 (10 channels)
* - Spatial filter coefficients: 4 0 -1 0 -1 -1 0 0 -1 0 0 4 0 -1 0 0 -1 -1 0 -1 (20 values)
* - Number of output channels: 2
* - Number of input channels: 10
*
* The output channels becomes :
* \code
* OC1 = 4 * C3 + 0 * C4 + (-1) * FC3 + 0 * FC4 + (-1) * C5 + (-1) * C1 + 0 * C2 + 0 * C6 + (-1) * CP3 + 0 * CP4
* = 4 * C3 - FC3 - C5 - C1 - CP3
*
* OC2 = 0 * C3 + 4 * C4 + 0 * FC3 + (-1) * FC4 + 0 * C5 + 0 * C1 + (-1) * C2 + (-1) * C6 + 0 * CP3 + (-1) * CP4
* = 4 * C4 - FC4 - C2 - C6 - CP4
* \endcode
*
* This is basically a Surface Laplacian around C4 and C5.
*
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpatialFilter_Miscellaneous|
*
* For large filters, it is somewhat faster to provide the matrix in an ASCII file than having the coefficients in scenario.xml directly.
* If a file is used, the filter size is read from the file and the other parameters of the box are ignored.
*
* To provide the filter matrix as a file, the format is the same as is used for storing electrode localizations. E.g. for 3x3 identity matrix, the file would be
*
* \code
* [
* [ "row1" "row2" "row3" ]
* [ "col1" "col2" "col3" ]
* ]
* [
* [ 1 0 0 ]
* ]
* [
* [ 0 1 0 ]
* ]
* [
* [ 0 0 1 ]
* ]
* \endcode
*
* |OVP_DocEnd_BoxAlgorithm_SpatialFilter_Miscellaneous|
*/
@@ -0,0 +1,125 @@
/**
* \page BoxAlgorithm_SpectralAnalysis Spectral analysis
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Description|
The Spectral Analysis box performs spectrum computations on incoming signals and possible outputs include the spectrum amplitude (the power of the signal in a number of frequency bands), as well as its phase, real part and imaginary part.
Output computations may be enabled/disabled from the settings dialogue box. The analysis is performed using a <b> Fast Fourier Transform </b>.
Do not forget to apply a \ref Doc_BoxAlgorithm_Windowing step before spectral analysis.
Considering an input signal \f$ X \in \mathbb{R}^{C \times N} \f$, composed of \f$ C \f$ channels and \f$ N \f$ temporal samples, this plugin computes the spectrum of this signal \f$ \Phi \in \mathbb{C}^{C \times F} \f$, composed of \f$ C \f$ channels and \f$ F \f$ frequencies.
Input signal being real, the spectrum exhibits conjugate symmetry: consequently, only half of the spectrum is returned with \f$ F = \left\lfloor N/2 \right\rfloor + 1 \f$.
For the \f$ c^{ \text{th} } \f$ channel and the \f$ f^{ \text{th} } \f$ frequency, the spectrum is defined as:
\f[ \Phi(c,f) = \Phi_r(c,f) + \mathsf{i} \times \Phi_i(c,f) = \left| \Phi(c,f) \right| \times e^{\mathsf{i} \arg(\Phi(c,f))} \f]
with \f$ \mathsf{i} \f$ being the imaginary unit.
Using these notations, for the \f$ c^{ \text{th} } \f$ channel, the Parseval's Theorem gives:
\f[ \sum_{n=0}^{N-1} \left| X(c,n) \right|^2 = \frac{1}{N} \sum_{f=0}^{F-1} \left| \Phi(c,f) \right|^2 \f]
with \f$ \left| \Phi(c,f) \right|^2 = \Phi_r(c,f)^2 + \Phi_i(c,f)^2 \f$.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Inputs|
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Input1|
An input multichannel signal \f$ X \in \mathbb{R}^{C \times N} \f$, composed of \f$ C \f$ channels and \f$ N \f$ temporal samples.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Output1|
An output spectral amplitude (absolute value) \f$ \left| \Phi \right| \in \mathbb{R}^{C \times F} \f$.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Output1|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Output2|
An output spectral phase \f$ \arg(\Phi) \in \mathbb{R}^{C \times F} \f$, in radians.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Output2|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Output3|
An output real part of the spectrum \f$ \Phi_r \in \mathbb{R}^{C \times F} \f$.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Output3|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Output4|
An output imaginary part of the spectrum \f$ \Phi_i \in \mathbb{R}^{C \times F} \f$.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Output4|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Settings|
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Settings|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Setting1|
Activate or not the Amplitude output.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Setting1|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Setting2|
Activate or not the Phase output.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Setting2|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Setting3|
Activate or not the Real Part output.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Setting3|
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Setting4|
Activate or not the Imaginary Part output.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Setting4|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Examples|
Practical example : visualising the power spectrum of a signal.
Let's use a Signal Oscillator box to generator sinusoidal signals on one channel. Next we add a Spectral Analysis box and connect boxes together. We make sure the 'Amplitude' of the signal is computed by checking the appropriate setting in the settings dialog box (see image below). Finally, we connect the 'Amplitude' output connector of the Spectral Analysis box to the input connector of a Power Spectrum Display box. The player may now be launched to visualize the power spectrum of the signal.
\image html spectralanalysis_online.png "Visualising the power spectrum of sinusoidal signals."
\image latex spectralanalysis_online.png "Visualising the power spectrum of sinusoidal signals." width=8cm
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectralAnalysis_Miscellaneous|
To verify the Parseval's Theorem, in version 1.1, spectra have been multiplied by \f$ \sqrt{2} \f$ with respect the previous version 1.0.
DC bin and Nyquist bin (when \f$ N \f$ is even) are not concerned by this correction.
* |OVP_DocEnd_BoxAlgorithm_SpectralAnalysis_Miscellaneous|
*/
@@ -0,0 +1,68 @@
/**
* \page BoxAlgorithm_SpectrumAverage Spectrum Average
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Description|
* This box can be used in conjunction with the \ref Doc_BoxAlgorithm_SpectralAnalysis and
* the \ref Doc_BoxAlgorithm_FrequencyBandSelector boxes in order to compute a power in specific
* frequency bands of a spectrum. The output is a column matrix giving a single value for each
* channel : the actual average power of the spectrum. You can may want to use these values
* with the \ref Doc_BoxAlgorithm_SimpleDSP to get ratios.
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Inputs|
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Inputs|
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Input1|
* This input should connect to a spectrum stream, either filtered with the
* \ref Doc_BoxAlgorithm_FrequencyBandSelector box or not.
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Outputs|
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Outputs|
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Output1|
* The output is a column matrix giving a single value for each
* channel : the actual average power of the spectrum.
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Settings|
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Settings|
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Setting1|
* The \ref Doc_BoxAlgorithm_FrequencyBandSelector outputs a spectrum with
* all initial frequency band represented. The bands that were not selected
* just have a 0 instead of their value. Consequently, you can use this settings
* to tell the box if the 0s contained in the spectrum should be part of the
* mean or not.
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Examples|
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_SpectrumAverage_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_SpectrumAverage_Miscellaneous|
*/
@@ -0,0 +1,207 @@
/**
* \page BoxAlgorithm_StimulationBasedEpoching Stimulation based epoching
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Description|
The aim of this box it to select some signal near a specific event. The event
is sent to the box in the form of an OpenViBE Stimulation. The author can
configure the duration of the selected signal and the offset of this selection
as regarding to the stimulation. For instance, it is possible to start the selection
a few hundreds of milliseconds <em>after</em> the event, or even a few hundreds of
milliseconds <em>before</em> the event.
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Inputs|
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Inputs|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Input1|
This input should receive the signal that contains the epoch to extract.
It is possible to pass either continuous signal or discontinuous signal.
However, it is good to know how it is expected to work before trying to
connect discontinuous signal.
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Input1|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Input2|
This input should receive the stimulation that triggers a new
epoching. Any stimulation other than the one specified in the settings
will be silently ignored.
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Input2|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Outputs|
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Outputs|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Output1|
This output will send the selected epochs of signal. This output
stream is discontinuous by design meaning that successive epochs are
not connected in time. It is possible to later re-epoch the output
signals if needed.
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Output1|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Output2|
This output sends a stimulation at the beginning of the selected epoch.
<em><b>This output is deprecated and should not be used anymore.</b></em>
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Output2|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Settings|
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Settings|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Setting1|
This setting defines the duration of the selected epoch (in seconds). For instance,
if you want to select 600ms of signal, you should enter 0.6
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Setting1|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Setting2|
This setting defines the offset of the epoch as against the stimulation date.
This is where the selection starts. If this offset is greater than 0, then
the signal selection starts <em>after</em> the actual stimulation. If this
offset is less than 0, then the signal selection starts <em>before</em> the actual
stimulation. Refer to \ref Doc_BoxAlgorithm_StimulationBasedEpoching_Miscellaneous for
more detailed examples
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Setting2|
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Setting3|
This setting defines the stimulation identifier which should trigger
a new epoching. Each time this stimulation is received, a new epocher
starts and a new epoch should be sent.
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Setting3|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Examples|
In the case of motor imagery, the user is usually instructed to imagine
either left or right hand movement. This mental task typically runs for
5 seconds. So selecting the signal block related to the imagination
of left hand movement can be done with this box using the following
parameters :
- duration : 5 seconds
- offset : 0 second
- stimulation : OVTK_GDF_Left
In case you'd want to avoid the first half second (because it could
reflect a phase where the user is <em>starting</em> to perform the task)
and wand to avoir the last half second (because it could reflect a phase
where the user is exhausted and does not perform the taks optimally), then
you could use the following parameters :
- duration : 4 seconds
- offset : 0.5 seconds
- stimulation : OVTK_GDF_Left
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_StimulationBasedEpoching_Miscellaneous|
<b>1. Continuous signal</b>
Suppose that we want to grab 1 second of signal following a specific stimulation code.
Suppose that the actual stimulation happens at <em>t=3.5</em> and <em>t=6</em>. Then the following figure
illustrate how epochs will be built. (<em>Ix</em> represents the x<em>th</em> input epoch and
<em>Ox</em> represents the x<em>th</em> output epoch)
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
input = | I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I7 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
+----+ +----+
output = | O1 | | O2 | ...
+----+ +----+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^
\endverbatim
Suppose that we want to grab 3 seconds of signal beginning 1 second before a specific stimulation code.
Suppose that the actual stimulation happens at <em>t=1.5</em> and <em>t=6</em>. Then the following figure
illustrate how epochs will be built. (<em>Ix</em> represents the x<em>th</em> input epoch and
<em>Ox</em> represents the x<em>th</em> output epoch)
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
input = | I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I7 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
+------------------+ +------------------+
output = | O1 | | O2 | ...
+------------------+ +------------------+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^
\endverbatim
Overlapping epochs would also work as defined on the following example...
Suppose that we want to grab 3 seconds of signal beginning 1 second before a specific stimulation code.
Suppose that the actual stimulation happens at <em>t=1.5</em>, <em>t=2</em> and <em>t=6</em>. Then the following figure
illustrate how epochs will be built. (<em>Ix</em> represents the x<em>th</em> input epoch and
<em>Ox</em> represents the x<em>th</em> output epoch)
\verbatim
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
input = | I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I7 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
+------------------+ +------------------+
| O1 | | O3 |
output = +------------------+ +------------------+ ....
+------------------+
| O2 |
+------------------+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^ ^
\endverbatim
<b>2. Discontinuous signal</b>
The case where input signal is not continuous (for instance, the signal has already been epoched with
either a \ref Doc_BoxAlgorithm_StimulationBasedEpoching or a \ref Doc_BoxAlgorithm_TimeBasedEpoching box)
can be tricky... Indeed, it is not possible to join input epochs correctly. The epoching only consists
in signal extraction from an individual input chunk.
For instance, suppose the following input signal (<em>Ix</em> represents the x<em>th</em> input epoch) :
\verbatim
+------------------+ +------------------+ +-----
input = | I1 | | I2 | | ...
+------------------+ +------------------+ +-----
time = 1 2 3 4 5 6 7 8 9
\endverbatim
Suppose that we want to grab 1 second of signal following a specific stimulation code.
Suppose that the actual stimulation happens at <em>t=1</em>, <em>t=2</em>, <em>t=4.5</em> and <em>t=6.5</em>. Then the following figure
illustrate how epochs will be built. (<em>Ix</em> represents the x<em>th</em> input epoch and
<em>Ox</em> represents the x<em>th</em> output epoch)
\verbatim
+------------------+ +------------------+ +-----
input = | I1 | | I2 | | ...
+------------------+ +------------------+ +-----
+----+ +----+ +----+
output = | O1 | | O2 | | O3 | ...
+----+ +----+ +----+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^ ^ ^
\endverbatim
In this case, note that the last stimulation at <em>t=6.5</em> can not generate a valid epoch. Indeed, the input
signal does not cover the time period from <em>t=6.5</em> to <em>t=7.5</em> so no epoch should be generated.
* |OVP_DocEnd_BoxAlgorithm_StimulationBasedEpoching_Miscellaneous|
*/
@@ -0,0 +1,83 @@
/**
* \page BoxAlgorithm_TemporalFilter Temporal Filter
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Description|
This plugin is used to filter the input signal. This plugin allows the selection of the kinf of filter (Butterworth, Chebyshev, Yule-Walker),
the kind of filter (low-pass, high-pass, band-pass, band-stop), the low or/and the high edge of the filter, and the passband ripple for the Chebyshev filter.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Inputs|
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Inputs|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Input1|
The input signal.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Outputs|
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Outputs|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Output1|
The filtered signal.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Settings|
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Settings|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Setting1|
Select the name of filter between Butterworth, Chebyshev and Yule-Walker.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Setting1|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Setting2|
Select the kind of filter between low-pass, high-pass, band-pass, band-stop.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Setting2|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Setting3|
Order \f$ n \f$ of the filter, with \f$ n \geq 1 \f$.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Setting3|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Setting4|
Low cut-off frequency \f$ f_1 > 0 \f$ for high-pass, band-pass and band-stop filters (not used with low-pass filter).
Low cut-off frequency can not be above Nyquist-Shannon criteria (half of the sampling rate).
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Setting4|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Setting5|
High cut-off frequency \f$ f_2 > 0 \f$ for low-pass, band-pass and band-stop filters (not used with high-pass filter). For band-pass and band-stop filters, \f$ f_1 < f_2 \f$.
High cut-off frequency can not be above Nyquist-Shannon criteria (half of the sampling rate).
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Setting5|
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Setting6|
If Chebyshev filter is selected, pass band ripple in dB is a necessary information.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Setting6|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Examples|
Let's consider our input signal is very noisy (50 Hz).
To filter this signal, select a Low pass Butterworth filter of 4th order and High Edge equal to 30 Hz for example.
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TemporalFilter_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_TemporalFilter_Miscellaneous|
*/
@@ -0,0 +1,87 @@
/**
* \page BoxAlgorithm_TimeBasedEpoching Time based epoching
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Description|
* The time based epoching box generates 'epochs', i.e. signal 'slices' which length is configurable, as is the time offset between two consecutive epochs. This box has one input and one output connectors, both of which are of 'signal' type. This box is essential to other signal processing boxes when the size of data blocks being forwarded to them is not significant enough.
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Inputs|
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Inputs|
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Input1|
* Input signal #1.
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Outputs|
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Outputs|
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Output1|
* Epoched signal #1.
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Settings|
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Settings|
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Setting1|
* Length of epoched signal #1
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Setting1|
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Setting2|
* Time interval between two consecutive epochs for signal #1
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Setting2|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Examples|
Practical example : apply time based epoching to compute the power spectrum of a signal
For the spectral analysis to work properly, signal data must come in chunks big enough for the analysis to be meaningful. Let's see how the time-based epoching box can help to improve the power spectrum computation of a signal.
First, we add a Signal Oscillator box to a scenario, connect it to a Spectral Analysis box, and connect the Amplitude output connector to the
input of a Power Spectrum Display box. Let's use default Sinus Oscillator settings (512Hz sampling frequency, data blocks size of 32 samples) and
make sure the Amplitude setting is enabled in the Spectral Analysis box. Now we can launch the player : the power spectrum is very coarse.
This is because the Sinus Oscillator generates small data blocks (32/512 = 1/16th of a second per block) compared to the periods of sinusoids making up the signal. The spectral analysis yields very coarse results when working on such blocks (see image below).
\image html timebasedepoching_1.png "Coarse power spectrum computation due to small data blocks."
\image latex timebasedepoching_1.png "Coarse power spectrum computation due to small data blocks." width = 10cm
One way to correct this problem is to increase the data blocks size. Let's send bigger blocks by setting their size to 512 samples. When launching the player again, the power spectrum should be much finer than before, since the spectral analysis works on blocks representing 1 second of signal. However, notice how the spectrum is only refreshed at 1Hz now. This solution is not satisfactory.
\image html timebasedepoching_2.png "Finer power spectrum computation by sending bigger chunks."
\image latex timebasedepoching_2.png "Finer power spectrum computation by sending bigger chunks." width=10cm
Now we insert a Time based epoching box before the Spectral Analysis. We reset the Sinus Oscillator settings to 512 samples a second and blocks of 32 samples. Let's now setup the epoching box : we are going to generate epochs of 1 second every 1/16th of a second. Now let's launch the player again : the power spectrum is refined and updated regularly.
\image html timebasedepoching_3.png "Epoching 1-second chunks to refine spectrum computations"
\image latex timebasedepoching_3.png "Epoching 1-second chunks to refine spectrum computations" width=10cm
The stimulation based epoching box is similar to time based epoching, only it generates epochs when a given stimulation is received. Thus, the box has two input connectors : one for signals and another for stimulations. Settings include epoch size, epoch offset (delay when epoching should start after the target stimulation is received), and stimulation identifier.
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_TimeBasedEpoching_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_TimeBasedEpoching_Miscellaneous|
*/
@@ -0,0 +1,62 @@
/**
* \page BoxAlgorithm_Windowing Windowing
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Windowing_Description|
* This plugin is used to apply a window to the input signal.
* This plugin allows the selection of the kind of window
* (None, Hamming, Hanning, Hann, Blackman, Triangular, Square Root).
* |OVP_DocEnd_BoxAlgorithm_Windowing_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Windowing_Inputs|
* |OVP_DocEnd_BoxAlgorithm_Windowing_Inputs|
* |OVP_DocBegin_BoxAlgorithm_Windowing_Input1|
* |OVP_DocEnd_BoxAlgorithm_Windowing_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Windowing_Outputs|
* |OVP_DocEnd_BoxAlgorithm_Windowing_Outputs|
* |OVP_DocBegin_BoxAlgorithm_Windowing_Output1|
* |OVP_DocEnd_BoxAlgorithm_Windowing_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Windowing_Settings|
* |OVP_DocEnd_BoxAlgorithm_Windowing_Settings|
* |OVP_DocBegin_BoxAlgorithm_Windowing_Setting1|
* Select the name of window between: None(equivalent to a rectangular window), Hamming, Hann, Hanning (equivalent to Hann window), Blackman,
* Triangular and Square Root.
* |OVP_DocEnd_BoxAlgorithm_Windowing_Setting1|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Windowing_Examples|
* Let's consider our input signal.
* To prevent rebound in spectrum analysis due to the square root
* windowing, select a Hanning window for example.
* |OVP_DocEnd_BoxAlgorithm_Windowing_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_Windowing_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_Windowing_Miscellaneous|
*/
@@ -0,0 +1,96 @@
/**
* \page BoxAlgorithm_XDAWNTrainer xDAWN Trainer
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Description|
* This box can be used in order to compute a spatial filter in order to enhance the
* detection of evoked response potentials. In order to compute such filter, this box
* needs to receive the whole contain of a session on the first hand, and a succession
* of evoked response potentials on the other hand. It then computes the averaged evoked
* response potential computes the spatial filter that makes this averaged potential
* appear in the whole signal. This can be used e.g. for better P300 signal detection.
*
* It is important to consider the fact that this box will have best results for a
* reasonably big number of input channels, possibly all over the scalp (areas where
* the evoked response potential can not be seen will be naturally used as references
* to reduce noise). The spatial filter results in space reduction to only keep significant
* chanels for later detection. Consider using at least 4 times more input channels than
* the number of output channels you want. For example, reducing 16 electrodes to 3 channels
* for P300 detection is OK.
*
* For more details about xDAWN, see <a href="http://www.icp.inpg.fr/~rivetber/Publications/references/Rivet2009a.pdf">Rivet et al. 2009</a>
* or in case this links disapears, <a href="http://www.ncbi.nlm.nih.gov/pubmed/19174332">this website</a>.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Inputs|
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Inputs|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Input1|
* This input receives the exepriment stimulations. As soon as the "train"
* stimulation is received, the spatial filter is computed.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Input1|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Input2|
* This input should receive the whole signal of the session.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Input2|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Input3|
* This input should receive the multiple evoked response potentials.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Input3|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_CSPSpatialFilterTrainer_Outputs|
* |OVP_DocEnd_BoxAlgorithm_CSPSpatialFilterTrainer_Outputs|
* |OVP_DocBegin_BoxAlgorithm_CSPSpatialFilterTrainer_Output1|
The xDAWN Trainer outputs the stimulation <b>OVTK_StimulationId_TrainCompleted</b> when the training process was successfull. No output is produced if the process failed.
* |OVP_DocEnd_BoxAlgorithm_CSPSpatialFilterTrainer_Output1|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Settings|
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Settings|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Setting1|
* This setting contains the stimulation to use to trigger the training process.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Setting1|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Setting2|
* This setting tells the box what configuration file to generate. This configuration file can
* be used to set the correct values of a \ref Doc_BoxAlgorithm_SpatialFilter box.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Setting2|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Setting3|
* This setting tells how many dimension should be kept out of the spatial filter.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Setting3|
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Setting4|
* If true, the file written will be a box configuration override especially for a spatial filter box. Otherwise, it will be an ASCII matrix.
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Setting4|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Examples|
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_XDAWNTrainer_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_XDAWNTrainer_Miscellaneous|
*/
@@ -0,0 +1,100 @@
/**
* \page BoxAlgorithm_ZeroCrossingDetector Zero-Crossing Detector
__________________________________________________________________
Detailed description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Description|
Using an hysteresis thresholding, this box detects the zero-crossings of the input, operating on all channels.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Description|
__________________________________________________________________
Inputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Inputs|
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Inputs|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Input1|
The input signal \f$ X \in \mathbb{R}^{C \times N} \f$, composed of \f$ C \f$ sensors and \f$ N \f$ samples.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Input1|
__________________________________________________________________
Outputs description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Outputs|
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Outputs|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Output1|
Zero-crossing signal \f$ Z \in \mathbb{R}^{C \times N} \f$, composed of \f$ C \f$ sensors and \f$ N \f$ samples.
It is defined as 1 for positive zero-crossings (negative-to-positive), -1 for negatives ones (positive-to-negative), 0 otherwise.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Output1|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Output2|
For all channels, stimulations mark positive and negatives zero-crossings.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Output2|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Output3|
For each channel, the rythm of negative-to-positive zero-crossings is computed in events per min.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Output3|
__________________________________________________________________
Settings description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Settings|
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Settings|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Setting1|
This setting defines the value \f$ t \f$ of the hysteresis threshold, to provide a robust detection.
\image html ZeroCrossingDetector_thresholding.png "Difference between naive and hysteresis sign thresholding"
\image latex ZeroCrossingDetector_thresholding.png "Difference between naive and hysteresis sign thresholding" width=\textwidth
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Setting1|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Setting2|
This setting defines the length of the time window for the rythm estimation.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Setting2|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Setting3|
This setting defines the stimulation id for negative-to-positive crossings.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Setting3|
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Setting4|
This setting defines the stimulation id for positive-to-negative crossings.
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Setting4|
__________________________________________________________________
Examples description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Examples|
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Examples|
__________________________________________________________________
Miscellaneous description
__________________________________________________________________
* |OVP_DocBegin_BoxAlgorithm_ZeroCrossingDetector_Miscellaneous|
* |OVP_DocEnd_BoxAlgorithm_ZeroCrossingDetector_Miscellaneous|
*/
@@ -0,0 +1,71 @@
.. _Doc_BoxAlgorithm_ChannelRename:
Channel Rename
==============
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_ChannelRename.png
This box renames the input channels with whatever name the author wants. The names
should be separated by a semi-colon ';'.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input matrix", "Signal"
Input matrix
~~~~~~~~~~~~
The input matrix which channels should be renamed. The type of this input can be changed to
signal or spectrum depending on what kind of stream channel to rename.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output matrix", "Signal"
Output matrix
~~~~~~~~~~~~~
The output matrix with renamed channels. The type of this output can be changed to
signal or spectrum depending on what kind of stream channel to rename.
.. _Doc_BoxAlgorithm_ChannelRename_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"New channel names", "String", "Channel 1;Channel 2"
New channel names
~~~~~~~~~~~~~~~~~
A semi-colon separated list of new channel names.
.. _Doc_BoxAlgorithm_ChannelRename_Examples:
Examples
--------
Suppose you compute the delta, alpha and beta band power at location Cz and merge the three
streams in a single stream. The resulting stream will handle three channels all named Cz. For
convenience, it could be useful to rename those channels Delta, Alpha and Beta respectively.
In order to achieve this, use a :ref:`Doc_BoxAlgorithm_ChannelRename` with the following setting
value : "Delta;Alpha;Beta"
@@ -0,0 +1,100 @@
.. _Doc_BoxAlgorithm_ChannelSelector:
Channel Selector
================
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_ChannelSelector.png
Selection can be based on channel name (case-sensitive) or index starting from 0
This box allows to restrict outgoing signal data to a subset of incoming data based on a list of channels.
Channels may be identified by their index, their name (the case matters), or a mix of both. Additionally,
the channels can be rejected instead of being selected.
The name of the box displayed in the designer is the channel list (first setting). It allows the user to see directly if the box configuration is correct.
However, should the user chose to rename the box manually, further change in the configuration won't be reflected in the name, except if the name is set
back to its default value.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
The input matrix which channels should be selected or rejected. The type of this input can be changed to
Signal, Spectrum or Streamed matrix depending on what kind of stream channel to select.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
Output signal
~~~~~~~~~~~~~
The output matrix with selected or preserved channels. The type of this output can be changed to
signal or spectrum depending on what kind of stream channel to select.
.. _Doc_BoxAlgorithm_ChannelSelector_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Channel List", "String", ":"
"Action", "Selection method", "Select"
"Channel Matching Method", "Match method", "Smart"
Channel List
~~~~~~~~~~~~
A semi colon separated list of channel identifiers. You can use the index of the channel or the name of the
channel. Also, ranges can be selected specifying first channel identifier, followed by a colon, followed by
the second channel identifier.
Action
~~~~~~
The action to perform on the identifier channel, be :
- ``select,``
- ``reject,``
- ``select`` ``EEG,`` selecting all EEG channels using their names. In this case, Channel List is no more considered.
Channel Matching Method
~~~~~~~~~~~~~~~~~~~~~~~
The kind of identification for channel list.
- ``Smart`` let the box try to detect if the channel identifier is an index or a name
- ``Name`` forces the channel identifiers to be considered as channel names. This can be useful if channel names are numbers.
- ``Index`` forces the channel identifiers to be considered as channel indices. This can be useful if channel names are numbers.
.. _Doc_BoxAlgorithm_ChannelSelector_Examples:
Examples
--------
Suppose you want to select the first 8 channels of an input stream, plus you want the Cz electrode and the last 3 channels.
You would then use the following string : [1:8;Cz;-3:-1].
@@ -0,0 +1,33 @@
.. _Doc_BoxAlgorithm_CommonAverageReference:
Common Average Reference
========================
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_CommonAverageReference.png
Re-referencing the signal to common average reference consists in subtracting from each sample the average value of the samples of all electrodes at this time
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
@@ -0,0 +1,159 @@
.. _Doc_BoxAlgorithm_ContinuousWaveletAnalysis:
Continuous Wavelet Analysis
===========================
.. container:: attribution
:Author:
Quentin Barthelemy
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_ContinuousWaveletAnalysis.png
Performs a Time-Frequency Analysis using Continuous Wavelet Transform.
The Continuous Wavelet Transform (CWT) provides a Time-Frequency representation of an input signal, using Morlet, Paul or derivative of Gaussian wavelets.
Considering an input signal :math:`X \in \mathbb{R}^{C \times N}`, composed of :math:`C` channels and :math:`N` temporal samples,
this plugin computes the CWT of this signal :math:`\Phi \in \mathbb{C}^{C \times F \times N}`, composed of :math:`C` channels, :math:`F` scales and :math:`N` temporal samples.
For the :math:`c^{ \text{th} }` channel, the :math:`f^{ \text{th} }` scale :math:`s_f` and the :math:`n^{ \text{th} }` sample, the Time-Frequency representation is defined as:
:math:`\Phi (c,f,n) = \sum_{n'=0}^{N-1} X(c,n') \ \psi^{*} \left( \frac{(n-n') \delta t}{s_f} \right)`,
where :math:`\psi` is the normalized wavelet, :math:`(.)^{*}` is the complex conjugate and :math:`\delta t` is the sampling period.
Using the inverse relation between wavelet scale :math:`s_f` and Fourier frequency :math:`\text{freq}_f`, output is finally defined as:
:math:`\Phi(c,f,n) = \Phi_r(c,f,n) + \mathsf{i} \times \Phi_i(c,f,n) = \left| \Phi(c,f,n) \right| \times e^{\mathsf{i} \arg(\Phi(c,f,n))}`,
with :math:`\mathsf{i}` being the imaginary unit.
Output can be visualized with a :ref:`Doc_BoxAlgorithm_InstantBitmap3DStream`.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
An input multichannel signal :math:`X \in \mathbb{R}^{C \times N}`, composed of :math:`C` channels and :math:`N` temporal samples.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Amplitude", "Time-frequency"
"Phase", "Time-frequency"
"Real Part", "Time-frequency"
"Imaginary Part", "Time-frequency"
Amplitude
~~~~~~~~~
An output spectral amplitude (absolute value) :math:`\left| \Phi \right| \in \mathbb{R}^{C \times F \times N}`.
Phase
~~~~~
An output spectral phase :math:`\arg(\Phi) \in \mathbb{R}^{C \times F \times N}`, in radians.
Real Part
~~~~~~~~~
An output real part of the spectrum :math:`\Phi_r \in \mathbb{R}^{C \times F \times N}`.
Imaginary Part
~~~~~~~~~~~~~~
An output imaginary part of the spectrum :math:`\Phi_i \in \mathbb{R}^{C \times F \times N}`.
.. _Doc_BoxAlgorithm_ContinuousWaveletAnalysis_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Wavelet type", "Continuous Wavelet Type", "Morlet wavelet"
"Wavelet parameter", "Float", "4"
"Number of frequencies", "Integer", "60"
"Highest frequency", "Float", "35"
"Frequency spacing", "Float", "12.5"
Wavelet type
~~~~~~~~~~~~
This setting defines the type of the wavelet:
- Morlet:
:math:`\psi_0 (n) = \pi^{1/4} e^{\mathsf{i} \omega_0 n} e^{-n^2 / 2}`
- Paul:
:math:`\psi_0 (n) = \frac{2^m \mathsf{i}^m m!}{\sqrt{\pi(2m)!}} (1-\mathsf{i} n)^{-(m+1)}`
- derivative of Gaussian:
:math:`\psi_0 (n) = \frac{(-1)^{m+1}}{\sqrt{\Gamma(m+\frac{1}{2})}} \frac{d^m}{d n^m} (e^{-n^2 / 2})`
Wavelet parameter
~~~~~~~~~~~~~~~~~
This setting defines the wavelet parameter:
- Morlet wavelet: nondimensional frequency :math:`\omega_0`, real positive parameter value. Values between 4.0 and 6.0 are typically used.
- Paul wavelet: order :math:`m`, positive integer values inferior to 20. Default value is 4.
- Derivative of Gaussian wavelet: derivative :math:`m`, positive even integer values. Value 2 gives the Marr or Mexican hat wavelet.
\n
Number of frequencies
~~~~~~~~~~~~~~~~~~~~~
This setting defines the number of frequencies :math:`F` of the CWT.
Highest frequency
~~~~~~~~~~~~~~~~~
This setting defines the highest frequency :math:`\text{freq}_F` of the CWT.
Frequency spacing
~~~~~~~~~~~~~~~~~
This setting is related to the frequency non-linear spacing of the CWT.
.. _Doc_BoxAlgorithm_ContinuousWaveletAnalysis_Miscellaneous:
Miscellaneous
-------------
Reference:
C Torrence and GP Compo, *A Practical Guide to Wavelet Analysis*, Bulletin of the American Meteorological Society, vol. 79, pp. 61–78, 1998
@@ -0,0 +1,68 @@
.. _Doc_BoxAlgorithm_Crop:
Crop
====
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA/IRISA
.. image:: images/Doc_BoxAlgorithm_Crop.png
Minimum or maximum or both limits can be specified
This box allows to set minimum and/or maximum thresholds to incoming data. Values lying outside the allowed range are cropped to it.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input matrix", "Streamed matrix"
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output matrix", "Streamed matrix"
.. _Doc_BoxAlgorithm_Crop_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Crop method", "Crop method", "Min/Max"
"Min crop value", "Float", "-1"
"Max crop value", "Float", "1"
Crop method
~~~~~~~~~~~
Method to use to crop incoming data. A minimum and/or a maximum threshold(s) may be defined.
Min crop value
~~~~~~~~~~~~~~
Minimum threshold.
Max crop value
~~~~~~~~~~~~~~
Maximum threshold.
.. _Doc_BoxAlgorithm_Crop_Miscellaneous:
Miscellaneous
-------------
Note : the type of input data may be changed. Allowed types include streamed matrix, feature vector, signal and spectrum.
@@ -0,0 +1,212 @@
.. _Doc_BoxAlgorithm_EpochAverage:
Epoch average
=============
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA/IRISA
.. image:: images/Doc_BoxAlgorithm_EpochAverage.png
This box can average matrices of different types including signal, spectrum or feature vectors
This box offers several methods of averaging for epoched streams.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input epochs", "Streamed matrix"
The input type of this box can be changed. Its type must be derived of
type :ref:`Doc_Streams_StreamedMatrix` in order to be parsed by the input
reader. If the author changes the input type, the output type will
be changed the same way.
Input epochs
~~~~~~~~~~~~
This input receives the input streamed matrix to average.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Averaged epochs", "Streamed matrix"
The output type of this box can be changed. Its type must be derived of
type :ref:`Doc_Streams_StreamedMatrix` in order for the writer to format
the output chunks. If the author changes the output type, the input
type will be changed the same way.
Averaged epochs
~~~~~~~~~~~~~~~
This output sends the averaged streamed matrix. Averaging method is done
according to the box settings.
.. _Doc_BoxAlgorithm_EpochAverage_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Averaging type", "Epoch Average method", "Moving epoch average"
"Epoch count", "Integer", "4"
Averaging type
~~~~~~~~~~~~~~
This setting gives the method to use in order to average the input
matrices. It can be of two types :
- *Moving average* : in this case, the averaging is done at
every input reception on the last few buffers, starting as soon
as enough input has been received.
- *Moving average (Immediate)* : in this case, the averaging is done at
every input reception on the last few buffers, starting immediately. When
the number of received buffer is lower than the wished number of epochs, the
average is computed on this very few number of input buffers.
- *Epoch block average* : in this case, the averaging
is done on a number of epochs (see next setting). Once this exact
number of input is received, the average is computed and output.
- *Cumulative average* : in this case, the averaging
is done on an infinite number of epochs starting from the first
received buffer to the last received buffer. This can be **very**
memory consuming !
Epoch count
~~~~~~~~~~~
This setting tells the box how much buffer it should use in order to
compute the average.
.. _Doc_BoxAlgorithm_EpochAverage_Examples:
Examples
--------
Let's study two cases. First, suppose you have such box with
*Epoch block average* set and *four* epochs.
The input stream is as follows :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
The output stream will look like this :
.. code::
+----+ +----+
| O1 | | O2 | ...
+----+ +----+
where ``O1`` is the average of ``I1,`` ``I2,`` ``I3`` and ``I4`` and
where ``O2`` is the average of ``I5,`` ``I6,`` ``I7`` and ``I8.``
Now consider the case where you configured this box with
*Moving average* and *four* epochs. Given the
same input stream :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
The output stream will look like this :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+
| O1 | | O2 | | O3 | | O4 | | O5 | | O6 | ...
+----+ +----+ +----+ +----+ +----+ +----+
where :
- ``O1`` is the average of ``I1,`` ``I2,`` ``I3`` and ``I4``
- ``O2`` is the average of ``I2,`` ``I3,`` ``I4`` and ``I5``
- ``O3`` is the average of ``I3,`` ``I4,`` ``I5`` and ``I6``
- ``O4`` is the average of ``I4,`` ``I5,`` ``I6`` and ``I7``
- etc...
Again consider the case where you configured this box with
*Moving average (Immediate)* and *four* epochs. Given the
same input stream :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
The output stream will look like this :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+
| O1 | | O2 | | O3 | | O4 | | O5 | | O6 | ...
+----+ +----+ +----+ +----+ +----+ +----+
where :
- ``O1`` is exactly ``I1``
- ``O2`` is the average of ``I1`` and ``I2``
- ``O3`` is the average of ``I1,`` ``I2`` and ``I3``
- ``O4`` is the average of ``I1,`` ``I2,`` ``I3`` and ``I4``
- ``O5`` is the average of ``I2,`` ``I3,`` ``I4`` and ``I5``
- ``O6`` is the average of ``I3,`` ``I4,`` ``I5`` and ``I6``
- etc...
Finally consider the case where you configured this box with
*Cumulative average* and *four* epochs. Given the
same input stream :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I8 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
The output stream will look like this :
.. code::
+----+ +----+ +----+ +----+ +----+ +----+
| O1 | | O2 | | O3 | | O4 | | O5 | | O6 | ...
+----+ +----+ +----+ +----+ +----+ +----+
where :
- ``O1`` is exactly ``I1``
- ``O2`` is the average of ``I1`` and ``I2``
- ``O3`` is the average of ``I1,`` ``I2`` and ``I3``
- ``O4`` is the average of ``I1,`` ``I2,`` ``I3`` and ``I4``
- ``O5`` is the average of ``I1,`` ``I2,`` ``I3,`` ``I4,`` and ``I5``
- ``O6`` is the average of ``I1,`` ``I2,`` ``I3,`` ``I4,`` ``I5,`` and ``I6``
- etc...
@@ -0,0 +1,69 @@
.. _Doc_BoxAlgorithm_FrequencyBandSelector:
Frequency Band Selector
=======================
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_FrequencyBandSelector.png
This box selects a subset of a spectrum matrix, turning all un selected frequency
band to 0.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input spectrum", "Spectrum"
Input spectrum
~~~~~~~~~~~~~~
The input spectrum to select from.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output spectrum", "Spectrum"
Output spectrum
~~~~~~~~~~~~~~~
The modified spectrum with unselected bands turned to 0
.. _Doc_BoxAlgorithm_FrequencyBandSelector_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Frequencies to select", "String", "8:12;16:24"
Frequencies to select
~~~~~~~~~~~~~~~~~~~~~
The range of frequencies you want to select.
.. _Doc_BoxAlgorithm_FrequencyBandSelector_Examples:
Examples
--------
Suppose you have a full band spectrum and that you want to work on alpha band. You can use
a :ref:`Doc_BoxAlgorithm_FrequencyBandSelector` box to select this band for later processing
with the following setting : "8:12". Now suppose you want to work on two bands, for instance
Alpha and the 16-24Hz subset of Beta, you should use the following setting value : "8:12[SEMICOLON]16:24"
with [SEMICOLON] replaced by the actual semicolon charater.
@@ -0,0 +1,60 @@
.. _Doc_BoxAlgorithm_Identity:
Identity
========
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA/IRISA
.. image:: images/Doc_BoxAlgorithm_Identity.png
This simply duplicates intput on its output
This box simply duplicates inputs to corresponding outputs in a similar way
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input stream", "Signal"
This box can have any number of input of any type. Each input has a corresponding
output with the same type. Incoming chunks are simply duplicated to the corresponding output.
Input stream
~~~~~~~~~~~~
The default input for this box.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output stream", "Signal"
This box can have any number of output of any type. Each output has a corresponding
input with the same type. Incoming chunks are simply duplicated to the corresponding output.
Output stream
~~~~~~~~~~~~~
The default output for this box.
.. _Doc_BoxAlgorithm_Identity_Examples:
Examples
--------
This box could be used in order to quickly switch between acquisition or file reading mode.
At the very top of the scenario, you would put an identity box with four connectors of type
:ref:`Doc_Streams_ExperimentInfo`, :ref:`Doc_Streams_Signal`, :ref:`Doc_Streams_Stimulation` and
:ref:`Doc_Streams_ChannelLocalization`.
@@ -0,0 +1,68 @@
.. _Doc_BoxAlgorithm_ReferenceChannel:
Reference Channel
=================
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_ReferenceChannel.png
Reference channel must be specified as a parameter for the box
This plugin subtracts the values of the samples from a reference
channel from the other channels' samples.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
The input signal.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
Output signal
~~~~~~~~~~~~~
The resulting signal.
.. _Doc_BoxAlgorithm_ReferenceChannel_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Channel", "String", "Ref_Nose"
"Channel Matching Method", "Match method", "Smart"
Channel
~~~~~~~
Index of the reference channel in the input stream.
.. _Doc_BoxAlgorithm_ReferenceChannel_Miscellaneous:
Miscellaneous
-------------
The reference channel is kept in the resulting signal.
@@ -0,0 +1,170 @@
.. _Doc_BoxAlgorithm_RegularizedCSPTrainer:
Regularized CSP Trainer
=======================
.. container:: attribution
:Author:
Jussi T. Lindgren
:Company:
Inria
.. image:: images/Doc_BoxAlgorithm_RegularizedCSPTrainer.png
The box estimates a filter bank of spatial filters using Common Spatial Patterns (CSP) algorithm. The algorithm tries to find a linear transform of the data that
makes the signal conditions (or classes) more distinct, when the data is projected to the matrix of the filters found by the algorithm (i.e. s=Wx, where we
assume x is the signal sample, W is the discovered filter bank, and s a lower-dimensional representation of the sample). The spatial filters are constructed
for two conditions to maximize the variance of the signals of the first condition while at the same time minimizing it for the second condition.
If signal variance contains discriminatory information, CSP filters can be useful in classification situations where designing spatial filter masks
such as Laplacians manually is cumbersome or when it is believed that a filter optimized to the current data/user would produce better
results than using an one-size-fits all filter.
In principle CSP can be useful in experiments where there is discriminative information in the variance (or power) of the signal conditions,
for example motor imagery or SSVEP.
The box implements some of the methods described e.g. by Lotte & Guan [1]. Especially, it
allows using the methods called "CSP with Diagonal Loading" and "CSP with Tikhonov Regularization" in the paper. In the approach of
the paper, the filters for condition 1 are found by eigenvector decomposition of inv(Sigma1+rho\*I)\*Sigma2, where Sigma1 and Sigma2 are
empirical covariance matrices for the two conditions, rho the amount of Tikhonov regularization and I an identity matrix. For condition 2,
the formula is the same with the sigmas swapped. The matrices Sigma1 and Sigma2 may be optionally shrunk towards diagonal matrices.
In situations with more than two conditions, the box implements a one-vs-all approach, where each class gives a Sigma1 in turn, with the
covariances of the other classes combined with a class frequency weighted average to obtain Sigma2. Hence, for k classes, k pairings
are estimated, and out of each pair, a requested amount of filters is selected separately. This results in k times the amount of filters
requested in total.
To avoid caching the whole dataset in the box, the algorithm tries to estimate the required covariances incrementally. The box supports
a few different incremental ways to compute the covariances. The 'block average' approach takes an average of all the covariances of the incoming
signal chunks, an approach described in [2], whereas the incremental (per sample) method aims to implement Youngs & Cramer
algorithm as described in [3]. Which one gives better results may depend on the situation -- however,
taking an average of covariance matrices is not your usual textbook method for computing covariance over the whole data.
Finally, the box also presents an option to try to compensate for the effect of changes in the average signal power over time. This is
can be done by enabling 'Trace Normalization' setting. When trace normalization is enabled, each data chunks contribution to
the covariance gets divided by its trace. See miscellaneous notes for details.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Stimulations", "Stimulations"
"Signal condition 1", "Signal"
"Signal condition 2", "Signal"
Stimulations
~~~~~~~~~~~~
This stimulus input is needed to indicate the end of a recording session (or end of file). It triggers the training/computation of the CSP filters.
Signal condition 1
~~~~~~~~~~~~~~~~~~
This input expects epoched data for the first condition (e.g. epochs for left hand motor imagery).
Signal condition 2
~~~~~~~~~~~~~~~~~~
This input expects epoched data for the second condition (e.g. epochs for right hand motor imagery).
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Train-completed Flag", "Stimulations"
Train-completed Flag
~~~~~~~~~~~~~~~~~~~~
The CSP Trainer outputs the stimulation **OVTK_StimulationId_TrainCompleted** when the training process was successful. No output is produced if the process failed.
.. _Doc_BoxAlgorithm_RegularizedCSPTrainer_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Train Trigger", "Stimulation", "OVTK_GDF_End_Of_Session"
"Spatial filter configuration", "Filename", ""
"Filters per condition", "Integer", "2"
"Save filters as box config", "Boolean", "false"
"Covariance update", "Update method", "Chunk average"
"Trace normalization", "Boolean", "false"
"Shrinkage coefficient", "Float", "0.0"
"Tikhonov coefficient", "Float", "0.0"
Train Trigger
~~~~~~~~~~~~~
The stimulus identifier denoting the end of a recording session or end of file, e.g. OVTK_GDF_End_Of_Session or OVTK_StimulationId_ExperimentStop.
Spatial filter configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The path and filename of the configuration file in which the computed spatial filters are saved.
Filters per condition
~~~~~~~~~~~~~~~~~~~~~
How many spatial filters should be selected per class.
Save filters as box config
~~~~~~~~~~~~~~~~~~~~~~~~~~
If true, the file written will be a box configuration override especially for the spatial filter box. Otherwise, it will be an ASCII matrix.
Covariance update
~~~~~~~~~~~~~~~~~
Method to update the covariances
Trace normalization
~~~~~~~~~~~~~~~~~~~
Is trace normalization done when appending the per-chunk covariances?
Shrinkage coefficient
~~~~~~~~~~~~~~~~~~~~~
The amount of shrinkage, between 0 and 1. It interpolates between the covariance matrices and the diagonal matrix.
Tikhonov coefficient
~~~~~~~~~~~~~~~~~~~~
The amount of Tikhonov regularization, bigger signifies more. This equals parameter rho in the description above. If 0, the method behaves approximately as a regular CSP.
.. _Doc_BoxAlgorithm_RegularizedCSPTrainer_Miscellaneous:
Miscellaneous
-------------
The suitable amount of regularization may depend on the variance of the data. You may need to try different values to find the one that suits your situation best.
Before the CSP training, it may be useful to temporally filter the input data to remove bands which are believed to have no relevant discriminative information.
Note that the usage of the CSP filters before classification training can make the cross-validation results optimistic, unless strictly non-overlapping parts of the data were used to train the CSP and the classifier (disjoint sets for each).
The trace normalization can be found in the literature [2]. The idea is to normalize the scale of each chunk in order to
compensate for a possible signal power drift over time during the EEG recording, making each chunks' covariance contribute
similarly to the aggregate regardless of the current chunks average power.
To get the "CSP with Diagonal Loading" of Lotte & Guan paper [1], set shrinkage to a positive value and Tikhonov to 0. To get the
"CSP with Tikhonov regularization", do the opposite. You can also try a mixture of the two. Note that the Guan & Lotte paper does not appear to use trace normalization.
To get the CSP resembling the one in the Muller-Gerkin paper, set Trace Normalization to True and the Covariance method to Chunk Average, with no regularization. Then, feed the algorithm each trial as a separate chunk (with Stimulation based epoching box). This is also the classic OV way of computing the CSP.
Once the spatial filters are computed and saved, you can load and apply the filters with the :ref:`Doc_BoxAlgorithm_SpatialFilter` "Spatial Filter" box.
References
1) Lotte & Guan: "Regularizing common spatial patterns to Improve BCI Designs: Unified Theory and New Algorithms", 2011.
2) Muller-Gerkin & al., "Designing optimal spatial filters for single-trial EEG classification in a movement task", 1999.
3) Chan, Golub & Leveq, "Updating formulae and a pairwise algorithm for computing sample variances", 1979.
@@ -0,0 +1,52 @@
.. _Doc_BoxAlgorithm_SignalAverage:
Signal average
==============
.. container:: attribution
:Author:
Bruno Renier
:Company:
INRIA/IRISA
.. image:: images/Doc_BoxAlgorithm_SignalAverage.png
This plugin computes the average of each incoming sample
buffer and outputs the resulting signal.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
The input signal.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Filtered signal", "Signal"
Filtered signal
~~~~~~~~~~~~~~~
Signal containing the averages of the input sample buffers.
.. _Doc_BoxAlgorithm_SignalAverage_Miscellaneous:
Miscellaneous
-------------
The output signal's sample count per channel per buffer is one,
since a buffer contains the averages (per channel) of the values
of an input buffer.
@@ -0,0 +1,80 @@
.. _Doc_BoxAlgorithm_SignalDecimation:
Signal Decimation
=================
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_SignalDecimation.png
No pre filtering applied - Number of samples per block have to be a multiple of the decimation factor
This box reduces the sampling frequency of an input signal *the easy way*.
It is different of the existing :ref:`Doc_BoxAlgorithm_SignalResampling` box in the sense
that there is no pre-filtering and that you can not choose an arbitrary sampling
frequency. Thus you will have to pre-filter the input signal with the
:ref:`Doc_BoxAlgorithm_TemporalFilter` box for example. The new sampling frequency
will be an exact divider of the source signal sampling frequency. For instance, if your
input sampling frequency is 1000Hz, you are allowed to divide that frequency by 2 or 4
but you can't divide it by 3. See section :ref:`Doc_BoxAlgorithm_SignalDecimation_Examples`
for a detailed example of what can be done.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
The input signal.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
Output signal
~~~~~~~~~~~~~
The decimated signal.
.. _Doc_BoxAlgorithm_SignalDecimation_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Decimation factor", "Integer", "8"
Decimation factor
~~~~~~~~~~~~~~~~~
The sampling rate divider. At each chunk reception, 1 sample among n of the input signal will be
sent to the output signal.
.. _Doc_BoxAlgorithm_SignalDecimation_Examples:
Examples
--------
Suppose you have a signal with a sampling rate of 1000Hz streamed with 32 samples per buffer
that you want to downsample to 100Hz. Downsampling this signal to 100Hz will require that you
low-pass filter the signal to at most 50Hz to avoid bad results. Additionally,
Changing the epoch sizes using a the
:ref:`Doc_BoxAlgorithm_TimeBasedEpoching` box and configuring it e.g. for epochs of 0.1s every
0.1s will cause this box to output 10 samples sized buffers.
@@ -0,0 +1,74 @@
.. _Doc_BoxAlgorithm_SignalResampling:
Signal Resampling
=================
.. container:: attribution
:Author:
Quentin Barthelemy
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_SignalResampling.png
The input signal is resampled, down-sampled or up-sampled, at a chosen sampling frequency and then re-epoched.
This plugin can apply a downsampling or an upsampling, to/from any sampling frequency, including non-integer sampling rate conversion.
The signal is low-pass filtered with a FIR filter to avoid spectral aliasing, using a sinc function-based fractional delay filter bank.
If the sampling rate is badly conditioned, this plugin can induce latency.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
An input multichannel signal :math:`X \in \mathbb{R}^{C \times N_1}`, composed of :math:`C` channels and :math:`N_1` temporal samples, at the sampling frequency :math:`F_1`.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
Output signal
~~~~~~~~~~~~~
An output multichannel signal :math:`Y \in \mathbb{R}^{C \times N_2}`, composed of :math:`C` channels and :math:`N_2` temporal samples, at the sampling frequency :math:`F_2`.
.. _Doc_BoxAlgorithm_SignalResampling_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"New Sampling Frequency", "Integer", "128"
"Sample Count Per Buffer", "Integer", "8"
"Low Pass Filter Signal Before Downsampling", "Boolean", "true"
New Sampling Frequency
~~~~~~~~~~~~~~~~~~~~~~
New sampling frequency :math:`F_2 > 0`. No processing is applied if :math:`F_2 = F_1`.
Sample Count Per Buffer
~~~~~~~~~~~~~~~~~~~~~~~
Number of temporal samples :math:`N_2 > 0` per resampled buffer.
Low Pass Filter Signal Before Downsampling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Low-pass filtering activation, to avoid spectral aliasing.
@@ -0,0 +1,176 @@
.. _Doc_BoxAlgorithm_SimpleDSP:
Simple DSP
==========
.. container:: attribution
:Author:
Bruno Renier / Yann Renard
:Company:
INRIA / IRISA
.. image:: images/Doc_BoxAlgorithm_SimpleDSP.png
This plugin is used to apply a mathematical formulae to each sample of an incoming signal and output
the resulting signal. It thus acts as a simple DSP.
The author may add up to 15 additional inputs.
In such circumstances, each input would be identified
by a letter from \e A to \e P.
Also the type of the inputs could be changed to any
streamed matrix derived type. Thus you can process
signal, spectrum or feature vector if you need.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input - A", "Signal"
You can use from 1 to 16 inputs.
Input - A
~~~~~~~~~
Input signal
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output", "Signal"
Output
~~~~~~
Filtered signal.
.. _Doc_BoxAlgorithm_SimpleDSP_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Equation", "String", "x"
Equation
~~~~~~~~
Formula to apply to incoming data (identified as 'X'). See :ref:`Doc_BoxAlgorithm_SimpleDSP_Miscellaneous` for more details.
.. _Doc_BoxAlgorithm_SimpleDSP_Examples:
Examples
--------
Let's consider that we want to compute the natural logarithm of the absolute value
of the input signal plus one. We just have to type the equation like that :
.. code::
log(abs(X) + 1)
Another example : if you want to sum the cosinus of X minus Pi with its sinus plus Pi,
you can enter this equation :
.. code::
cos(X - M_PI) + sin(X + M_PI)
.. _Doc_BoxAlgorithm_SimpleDSP_Miscellaneous:
Miscellaneous
-------------
The equation can use at most 16 variables, for 16 input signals.
The variable names are the 16 first letters of the alphabet, i.e. 'a' (or 'A') to 'p' (or 'P') matches inputs 1 to 16.
**NB** : The first input variable can be named 'x' or 'X'.
Here is a list of supported functions/operators :
- Operators:
- ``+``
- ``-``
- ``*``
- ``/``
- Unary functions
- ``abs`` (absolute value)
- ``acos`` (arc cosinus, requires n in the range [-1:1], result ranged in [0:M_PI])
- ``asin`` (arc sinus, requires n in the range [-1:1], result ranged in [-M_PI_2:M_PI_2])
- ``atan`` (arc tangent, requires n in the range [-1:1], result ranged in [-M_PI_2:M_PI_2])
- ``ceil`` (upper-bound rounding)
- ``cos`` (cosinus, n in radians, result ranged in [-1:1])
- ``exp`` (exponential)
- ``floor`` (lower-bound rounding)
- ``log`` (natural logarithm, requires n>0)
- ``log10`` (decimal logarithm, requires n>0)
- ``rand`` (pseudo-random, result ranged in [0:n])
- ``sin`` (sinus, n in radians, result ranged in [-1:1])
- ``sqrt`` (square root, requires n>=0)
- ``tan`` (tangent, n in radians, result ranged in [-1:1])
- Binary function
- ``pow`` (power)
- Comparison operators
- ``>``
- ``>=``
- ``<``
- ``<=``
- ``==``
- ``=`` (equivalent to ==)
- ``!=``
- ``<>`` (equivalent to !=)
- Boolean operators
- ``&`` as \e and
- ``&&`` also as \e and
- ``|`` as \e or
- ``||`` also as \e or
- ``!`` as \e not
- ``~`` as \e xor
- ``^`` also as \e xor
- ternary operator
- ``? :``
There are also a few defined constants :
- ``M_PI``
- ``M_PI_2``
- ``M_PI_4``
- ``M_1_PI``
- ``M_2_PI``
- ``M_2_SQRTPI``
- ``M_SQRT2``
- ``M_SQRT1_2``
- ``M_E``
- ``M_LOG2E``
- ``M_LOG10E``
- ``M_LN2``
- ``M_LN10``
(note : their meaning is the same as the constants of the same name in math.c)
Furthermore, the equation parser is totally case-insensitive. So you can write ``COS(m_pi+x)`` or ``cos(M_PI+X)``, it doesn't matter.
Don't worry about the whitespaces and blank characters, they are automatically skipped by the equation parser.
That means, for instance, that both ``X+1`` and ``X + 1`` work.
This plugin implements basic constant folding. That means that when the plugin analyses the equation,
if it can compute some parts of it before compilation, it will. For now, it does not support rational
equations simplification.
@@ -0,0 +1,131 @@
.. _Doc_BoxAlgorithm_SpatialFilter:
Spatial Filter
==============
.. container:: attribution
:Author:
Yann Renard, Jussi T. Lindgren
:Company:
Inria
.. image:: images/Doc_BoxAlgorithm_SpatialFilter.png
The applied coefficient matrix must be specified as a box parameter. The filter processes each sample independently of the past samples.
The spatial filter generates a number of output channels from another number of input
channels, each output channel being a linear combination of the input channels.
For example, lets say :math:`IC_j` is the :math:`j` th input channel, :math:`OC_k` is the
:math:`k` th output channel, and :math:`S_{jk}` is the coefficient for the :math:`j` th input
channel and :math:`k` th output channel in the Spatial filter matrix.
Then the output channels are computed this way :
:math:`OC_k = \sum_j S_{jk} * IC_j`.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input Signal", "Signal"
Input Signal
~~~~~~~~~~~~
This input contains the input channels to mix.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output Signal", "Signal"
Output Signal
~~~~~~~~~~~~~
This output contains the generated channels, mixed from the input channels.
.. _Doc_BoxAlgorithm_SpatialFilter_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Spatial Filter Coefficients", "String", "1;0;0;0;0;1;0;0;0;0;1;0;0;0;0;1"
"Number of Output Channels", "Integer", "4"
"Number of Input Channels", "Integer", "4"
"Filter matrix file", "Filename", ""
Spatial Filter Coefficients
~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting contains a flat view of the spatial filter matrix. The coefficient orders is as follows :
all the coefficients for the first output followed by all the coefficients for the second output and so on..
Number of Output Channels
~~~~~~~~~~~~~~~~~~~~~~~~~
Number of output channels to generate
Number of Input Channels
~~~~~~~~~~~~~~~~~~~~~~~~
Number of input channels to compute from
Filter matrix file
~~~~~~~~~~~~~~~~~~
Filter matrix. You can alternatively provide the filter coefficients as an ASCII file.
.. _Doc_BoxAlgorithm_SpatialFilter_Examples:
Examples
--------
Let's consider the following example :
- Input channels list: C3;C4;FC3;FC4;C5;C1;C2;C6;CP3;CP4 (10 channels)
- Spatial filter coefficients: 4 0 -1 0 -1 -1 0 0 -1 0 0 4 0 -1 0 0 -1 -1 0 -1 (20 values)
- Number of output channels: 2
- Number of input channels: 10
The output channels becomes :
.. code::
OC1 = 4 * C3 + 0 * C4 + (-1) * FC3 + 0 * FC4 + (-1) * C5 + (-1) * C1 + 0 * C2 + 0 * C6 + (-1) * CP3 + 0 * CP4
= 4 * C3 - FC3 - C5 - C1 - CP3
OC2 = 0 * C3 + 4 * C4 + 0 * FC3 + (-1) * FC4 + 0 * C5 + 0 * C1 + (-1) * C2 + (-1) * C6 + 0 * CP3 + (-1) * CP4
= 4 * C4 - FC4 - C2 - C6 - CP4
This is basically a Surface Laplacian around C4 and C5.
.. _Doc_BoxAlgorithm_SpatialFilter_Miscellaneous:
Miscellaneous
-------------
For large filters, it is somewhat faster to provide the matrix in an ASCII file than having the coefficients in scenario.xml directly.
If a file is used, the filter size is read from the file and the other parameters of the box are ignored.
To provide the filter matrix as a file, the format is the same as is used for storing electrode localizations. E.g. for 3x3 identity matrix, the file would be
.. code::
[ [ "row1" "row2" "row3" ] [ "col1" "col2" "col3" ] ]
[ [ 1 0 0 ] ]
[ [ 0 1 0 ] ]
[ [ 0 0 1 ] ]
@@ -0,0 +1,131 @@
.. _Doc_BoxAlgorithm_SpectralAnalysis:
Spectral Analysis
=================
.. container:: attribution
:Author:
Laurent Bonnet / Quentin Barthelemy
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_SpectralAnalysis.png
Performs a Spectral Analysis using FFT.
The Spectral Analysis box performs spectrum computations on incoming signals and possible outputs include the spectrum amplitude (the power of the signal in a number of frequency bands), as well as its phase, real part and imaginary part.
Output computations may be enabled/disabled from the settings dialogue box. The analysis is performed using a **Fast Fourier Transform**.
Do not forget to apply a :ref:`Doc_BoxAlgorithm_Windowing` step before spectral analysis.
Considering an input signal :math:`X \in \mathbb{R}^{C \times N}`, composed of :math:`C` channels and :math:`N` temporal samples, this plugin computes the spectrum of this signal :math:`\Phi \in \mathbb{C}^{C \times F}`, composed of :math:`C` channels and :math:`F` frequencies.
Input signal being real, the spectrum exhibits conjugate symmetry: consequently, only half of the spectrum is returned with :math:`F = \left\lfloor N/2 \right\rfloor + 1`.
For the :math:`c^{ \text{th} }` channel and the :math:`f^{ \text{th} }` frequency, the spectrum is defined as:
:math:`\Phi(c,f) = \Phi_r(c,f) + \mathsf{i} \times \Phi_i(c,f) = \left| \Phi(c,f) \right| \times e^{\mathsf{i} \arg(\Phi(c,f))}`
with :math:`\mathsf{i}` being the imaginary unit.
Using these notations, for the :math:`c^{ \text{th} }` channel, the Parseval's Theorem gives:
:math:`\sum_{n=0}^{N-1} \left| X(c,n) \right|^2 = \frac{1}{N} \sum_{f=0}^{F-1} \left| \Phi(c,f) \right|^2`
with :math:`\left| \Phi(c,f) \right|^2 = \Phi_r(c,f)^2 + \Phi_i(c,f)^2`.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
An input multichannel signal :math:`X \in \mathbb{R}^{C \times N}`, composed of :math:`C` channels and :math:`N` temporal samples.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Amplitude", "Spectrum"
"Phase", "Spectrum"
"Real Part", "Spectrum"
"Imaginary Part", "Spectrum"
Amplitude
~~~~~~~~~
An output spectral amplitude (absolute value) :math:`\left| \Phi \right| \in \mathbb{R}^{C \times F}`.
Phase
~~~~~
An output spectral phase :math:`\arg(\Phi) \in \mathbb{R}^{C \times F}`, in radians.
Real Part
~~~~~~~~~
An output real part of the spectrum :math:`\Phi_r \in \mathbb{R}^{C \times F}`.
Imaginary Part
~~~~~~~~~~~~~~
An output imaginary part of the spectrum :math:`\Phi_i \in \mathbb{R}^{C \times F}`.
.. _Doc_BoxAlgorithm_SpectralAnalysis_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Amplitude", "Boolean", "true"
"Phase", "Boolean", "false"
"Real Part", "Boolean", "false"
"Imaginary Part", "Boolean", "false"
Amplitude
~~~~~~~~~
Activate or not the Amplitude output.
Phase
~~~~~
Activate or not the Phase output.
Real Part
~~~~~~~~~
Activate or not the Real Part output.
Imaginary Part
~~~~~~~~~~~~~~
Activate or not the Imaginary Part output.
.. _Doc_BoxAlgorithm_SpectralAnalysis_Examples:
Examples
--------
Practical example : visualising the power spectrum of a signal.
Let's use a Signal Oscillator box to generator sinusoidal signals on one channel. Next we add a Spectral Analysis box and connect boxes together. We make sure the 'Amplitude' of the signal is computed by checking the appropriate setting in the settings dialog box (see image below). Finally, we connect the 'Amplitude' output connector of the Spectral Analysis box to the input connector of a Power Spectrum Display box. The player may now be launched to visualize the power spectrum of the signal.
.. figure:: images/spectralanalysis_online.png
:alt: Visualising the power spectrum of sinusoidal signals.
:align: center
Visualising the power spectrum of sinusoidal signals.
.. _Doc_BoxAlgorithm_SpectralAnalysis_Miscellaneous:
Miscellaneous
-------------
To verify the Parseval's Theorem, in version 1.1, spectra have been multiplied by :math:`\sqrt{2}` with respect the previous version 1.0.
DC bin and Nyquist bin (when :math:`N` is even) are not concerned by this correction.
@@ -0,0 +1,67 @@
.. _Doc_BoxAlgorithm_SpectrumAverage:
Spectrum Average
================
.. container:: attribution
:Author:
Yann Renard
:Company:
INRIA
.. image:: images/Doc_BoxAlgorithm_SpectrumAverage.png
This box can be used in conjunction with the :ref:`Doc_BoxAlgorithm_SpectralAnalysis` and
the :ref:`Doc_BoxAlgorithm_FrequencyBandSelector` boxes in order to compute a power in specific
frequency bands of a spectrum. The output is a column matrix giving a single value for each
channel : the actual average power of the spectrum. You can may want to use these values
with the :ref:`Doc_BoxAlgorithm_SimpleDSP` to get ratios.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Spectrum", "Spectrum"
Spectrum
~~~~~~~~
This input should connect to a spectrum stream, either filtered with the
:ref:`Doc_BoxAlgorithm_FrequencyBandSelector` box or not.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Spectrum average", "Streamed matrix"
Spectrum average
~~~~~~~~~~~~~~~~
The output is a column matrix giving a single value for each
channel : the actual average power of the spectrum.
.. _Doc_BoxAlgorithm_SpectrumAverage_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Considers zeros", "Boolean", "false"
Considers zeros
~~~~~~~~~~~~~~~
The :ref:`Doc_BoxAlgorithm_FrequencyBandSelector` outputs a spectrum with
all initial frequency band represented. The bands that were not selected
just have a 0 instead of their value. Consequently, you can use this settings
to tell the box if the 0s contained in the spectrum should be part of the
mean or not.
@@ -0,0 +1,229 @@
.. _Doc_BoxAlgorithm_StimulationBasedEpoching:
Stimulation based epoching
==========================
.. container:: attribution
:Author:
Jozef Legeny
:Company:
Mensia Technologies
.. image:: images/Doc_BoxAlgorithm_StimulationBasedEpoching.png
Slices signal into chunks of a desired length following a stimulation event.
The aim of this box it to select some signal near a specific event. The event
is sent to the box in the form of an OpenViBE Stimulation. The author can
configure the duration of the selected signal and the offset of this selection
as regarding to the stimulation. For instance, it is possible to start the selection
a few hundreds of milliseconds *after* the event, or even a few hundreds of
milliseconds *before* the event.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
"Input stimulations", "Stimulations"
Input signal
~~~~~~~~~~~~
This input should receive the signal that contains the epoch to extract.
It is possible to pass either continuous signal or discontinuous signal.
However, it is good to know how it is expected to work before trying to
connect discontinuous signal.
Input stimulations
~~~~~~~~~~~~~~~~~~
This input should receive the stimulation that triggers a new
epoching. Any stimulation other than the one specified in the settings
will be silently ignored.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Epoched signal", "Signal"
Epoched signal
~~~~~~~~~~~~~~
This output will send the selected epochs of signal. This output
stream is discontinuous by design meaning that successive epochs are
not connected in time. It is possible to later re-epoch the output
signals if needed.
.. _Doc_BoxAlgorithm_StimulationBasedEpoching_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Epoch duration (in sec)", "Float", "1"
"Epoch offset (in sec)", "Float", "0.5"
"Stimulation to epoch from", "Stimulation", "OVTK_StimulationId_Label_00"
Epoch duration (in sec)
~~~~~~~~~~~~~~~~~~~~~~~
This setting defines the duration of the selected epoch (in seconds). For instance,
if you want to select 600ms of signal, you should enter 0.6
Epoch offset (in sec)
~~~~~~~~~~~~~~~~~~~~~
This setting defines the offset of the epoch as against the stimulation date.
This is where the selection starts. If this offset is greater than 0, then
the signal selection starts *after* the actual stimulation. If this
offset is less than 0, then the signal selection starts *before* the actual
stimulation. Refer to :ref:`Doc_BoxAlgorithm_StimulationBasedEpoching_Miscellaneous` for
more detailed examples
Stimulation to epoch from
~~~~~~~~~~~~~~~~~~~~~~~~~
This setting defines the stimulation identifier which should trigger
a new epoching. Each time this stimulation is received, a new epocher
starts and a new epoch should be sent.
.. _Doc_BoxAlgorithm_StimulationBasedEpoching_Examples:
Examples
--------
In the case of motor imagery, the user is usually instructed to imagine
either left or right hand movement. This mental task typically runs for
5 seconds. So selecting the signal block related to the imagination
of left hand movement can be done with this box using the following
parameters :
- duration : 5 seconds
- offset : 0 second
- stimulation : OVTK_GDF_Left
In case you'd want to avoid the first half second (because it could
reflect a phase where the user is *starting* to perform the task)
and wand to avoir the last half second (because it could reflect a phase
where the user is exhausted and does not perform the taks optimally), then
you could use the following parameters :
- duration : 4 seconds
- offset : 0.5 seconds
- stimulation : OVTK_GDF_Left
.. _Doc_BoxAlgorithm_StimulationBasedEpoching_Miscellaneous:
Miscellaneous
-------------
**1. Continuous signal**
Suppose that we want to grab 1 second of signal following a specific stimulation code.
Suppose that the actual stimulation happens at *t=3.5* and *t=6*. Then the following figure
illustrate how epochs will be built. (*Ix* represents the x*th* input epoch and
*Ox* represents the x*th* output epoch)
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
input = | I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I7 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
+----+ +----+
output = | O1 | | O2 | ...
+----+ +----+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^
Suppose that we want to grab 3 seconds of signal beginning 1 second before a specific stimulation code.
Suppose that the actual stimulation happens at *t=1.5* and *t=6*. Then the following figure
illustrate how epochs will be built. (*Ix* represents the x*th* input epoch and
*Ox* represents the x*th* output epoch)
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
input = | I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I7 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
+------------------+ +------------------+
output = | O1 | | O2 | ...
+------------------+ +------------------+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^
Overlapping epochs would also work as defined on the following example...
Suppose that we want to grab 3 seconds of signal beginning 1 second before a specific stimulation code.
Suppose that the actual stimulation happens at *t=1.5*, *t=2* and *t=6*. Then the following figure
illustrate how epochs will be built. (*Ix* represents the x*th* input epoch and
*Ox* represents the x*th* output epoch)
.. code::
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
input = | I1 | | I2 | | I3 | | I4 | | I5 | | I6 | | I7 | | I7 | | I9 | ...
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
+------------------+ +------------------+
| O1 | | O3 |
output = +------------------+ +------------------+ ....
+------------------+
| O2 |
+------------------+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^ ^
**2. Discontinuous signal**
The case where input signal is not continuous (for instance, the signal has already been epoched with
either a :ref:`Doc_BoxAlgorithm_StimulationBasedEpoching` or a :ref:`Doc_BoxAlgorithm_TimeBasedEpoching` box)
can be tricky... Indeed, it is not possible to join input epochs correctly. The epoching only consists
in signal extraction from an individual input chunk.
For instance, suppose the following input signal (*Ix* represents the x*th* input epoch) :
.. code::
+------------------+ +------------------+ +-----
input = | I1 | | I2 | | ...
+------------------+ +------------------+ +-----
time = 1 2 3 4 5 6 7 8 9
Suppose that we want to grab 1 second of signal following a specific stimulation code.
Suppose that the actual stimulation happens at *t=1*, *t=2*, *t=4.5* and *t=6.5*. Then the following figure
illustrate how epochs will be built. (*Ix* represents the x*th* input epoch and
*Ox* represents the x*th* output epoch)
.. code::
+------------------+ +------------------+ +-----
input = | I1 | | I2 | | ...
+------------------+ +------------------+ +-----
+----+ +----+ +----+
output = | O1 | | O2 | | O3 | ...
+----+ +----+ +----+
time = 1 2 3 4 5 6 7 8 9
stim = ^ ^ ^ ^
In this case, note that the last stimulation at *t=6.5* can not generate a valid epoch. Indeed, the input
signal does not cover the time period from *t=6.5* to *t=7.5* so no epoch should be generated.
@@ -0,0 +1,110 @@
.. _Doc_BoxAlgorithm_TemporalFilter:
Temporal Filter
===============
.. container:: attribution
:Author:
Yann Renard & Laurent Bonnet
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_TemporalFilter.png
Applies a temporal filter, based on various one-way IIR filter designs, to the input stream.
This plugin is used to filter the input signal. This plugin allows the selection of the kinf of filter (Butterworth, Chebyshev, Yule-Walker),
the kind of filter (low-pass, high-pass, band-pass, band-stop), the low or/and the high edge of the filter, and the passband ripple for the Chebyshev filter.
The algorithm used for this filter comes from an external library dsp-filters (https://github.com/vinniefalco/DSPFilters).
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
The input signal.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
Output signal
~~~~~~~~~~~~~
The filtered signal.
.. _Doc_BoxAlgorithm_TemporalFilter_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Filter Method", "Filter method", "Butterworth"
"Filter Type", "Filter type", "Band Pass"
"Filter Order", "Integer", "4"
"Low Cut-off Frequency (Hz)", "Float", "1"
"High Cut-off Frequency (Hz)", "Float", "40"
"Band Pass Ripple (dB)", "Float", "0.5"
Filter Method
~~~~~~~~~~~~~
Select the name of filter between Butterworth, Chebyshev and Yule-Walker.
Filter Type
~~~~~~~~~~~
Select the kind of filter between low-pass, high-pass, band-pass, band-stop.
The Temporal Filter applies a DC removal for Band-Pass and High-Pass filters.
This DC is estimated as the first value of the first chunk.
Filter Order
~~~~~~~~~~~~
Order :math:`n` of the filter, with :math:`n \geq 1`.
Low Cut-off Frequency (Hz)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Low cut-off frequency :math:`f_1 > 0` for high-pass, band-pass and band-stop filters (not used with low-pass filter).
Low cut-off frequency can not be above Nyquist-Shannon criteria (half of the sampling rate).
High Cut-off Frequency (Hz)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
High cut-off frequency :math:`f_2 > 0` for low-pass, band-pass and band-stop filters (not used with high-pass filter). For band-pass and band-stop filters, :math:`f_1 < f_2`.
High cut-off frequency can not be above Nyquist-Shannon criteria (half of the sampling rate).
Band Pass Ripple (dB)
~~~~~~~~~~~~~~~~~~~~~
If Chebyshev filter is selected, pass band ripple in dB is a necessary information.
.. _Doc_BoxAlgorithm_TemporalFilter_Examples:
Examples
--------
Let's consider our input signal is very noisy (50 Hz).
To filter this signal, select a Low pass Butterworth filter of 4th order and High Edge equal to 30 Hz for example.
Miscellaneous
-------------
The Temporal Filter bow does not behave properly on some limit cases: when using a low cut frequency or with a high
filter order, in these cases we advise to remove the first seconds of the processed signal from your process.
@@ -0,0 +1,103 @@
.. _Doc_BoxAlgorithm_TimeBasedEpoching:
Time based epoching
===================
.. container:: attribution
:Author:
Quentin Barthelemy
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_TimeBasedEpoching.png
Interval can be used to control the overlap of epochs
The time based epoching box generates 'epochs', i.e. signal 'slices' which length is configurable, as is the time offset between two consecutive epochs. This box has one input and one output connectors, both of which are of 'signal' type. This box is essential to other signal processing boxes when the size of data blocks being forwarded to them is not significant enough.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
Input signal #1.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Epoched signal", "Signal"
Epoched signal
~~~~~~~~~~~~~~
Epoched signal #1.
.. _Doc_BoxAlgorithm_TimeBasedEpoching_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Epoch duration (in sec)", "Float", "1"
"Epoch intervals (in sec)", "Float", "0.5"
Epoch duration (in sec)
~~~~~~~~~~~~~~~~~~~~~~~
Length of epoched signal #1
Epoch intervals (in sec)
~~~~~~~~~~~~~~~~~~~~~~~~
Time interval between two consecutive epochs for signal #1
.. _Doc_BoxAlgorithm_TimeBasedEpoching_Examples:
Examples
--------
Practical example : apply time based epoching to compute the power spectrum of a signal
For the spectral analysis to work properly, signal data must come in chunks big enough for the analysis to be meaningful. Let's see how the time-based epoching box can help to improve the power spectrum computation of a signal.
First, we add a Signal Oscillator box to a scenario, connect it to a Spectral Analysis box, and connect the Amplitude output connector to the
input of a Power Spectrum Display box. Let's use default Sinus Oscillator settings (512Hz sampling frequency, data blocks size of 32 samples) and
make sure the Amplitude setting is enabled in the Spectral Analysis box. Now we can launch the player : the power spectrum is very coarse.
This is because the Sinus Oscillator generates small data blocks (32/512 = 1/16th of a second per block) compared to the periods of sinusoids making up the signal. The spectral analysis yields very coarse results when working on such blocks (see image below).
.. figure:: images/timebasedepoching_1.png
:alt: Coarse power spectrum computation due to small data blocks.
:align: center
Coarse power spectrum computation due to small data blocks.
One way to correct this problem is to increase the data blocks size. Let's send bigger blocks by setting their size to 512 samples. When launching the player again, the power spectrum should be much finer than before, since the spectral analysis works on blocks representing 1 second of signal. However, notice how the spectrum is only refreshed at 1Hz now. This solution is not satisfactory.
.. figure:: images/timebasedepoching_2.png
:alt: Finer power spectrum computation by sending bigger chunks.
:align: center
Finer power spectrum computation by sending bigger chunks.
Now we insert a Time based epoching box before the Spectral Analysis. We reset the Sinus Oscillator settings to 512 samples a second and blocks of 32 samples. Let's now setup the epoching box : we are going to generate epochs of 1 second every 1/16th of a second. Now let's launch the player again : the power spectrum is refined and updated regularly.
.. figure:: images/timebasedepoching_3.png
:alt: Epoching 1-second chunks to refine spectrum computations
:align: center
Epoching 1-second chunks to refine spectrum computations
The stimulation based epoching box is similar to time based epoching, only it generates epochs when a given stimulation is received. Thus, the box has two input connectors : one for signals and another for stimulations. Settings include epoch size, epoch offset (delay when epoching should start after the target stimulation is received), and stimulation identifier.
@@ -0,0 +1,62 @@
.. _Doc_BoxAlgorithm_Windowing:
Windowing
=========
.. container:: attribution
:Author:
Laurent Bonnet
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_Windowing.png
Applies a windowing function to the signal.
This plugin is used to apply a window to the input signal.
This plugin allows the selection of the kind of window
(None, Hamming, Hanning, Hann, Blackman, Triangular, Square Root).
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Output signal", "Signal"
.. _Doc_BoxAlgorithm_Windowing_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Window method", "Window method", "Hamming"
Window method
~~~~~~~~~~~~~
Select the name of window between: None(equivalent to a rectangular window), Hamming, Hanning, Hann, Blackman,
Triangular and Square Root.
.. _Doc_BoxAlgorithm_Windowing_Examples:
Examples
--------
Let's consider our input signal.
To prevent rebound in spectrum analysis due to the square root
windowing, select a Hanning window for example.
@@ -0,0 +1,102 @@
.. _Doc_BoxAlgorithm_XDAWNTrainer:
xDAWN Trainer
=============
.. container:: attribution
:Author:
Yann Renard
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_XDAWNTrainer.png
Trains spatial filters that best highlight Evoked Response Potentials (ERP) such as P300
This box can be used in order to compute a spatial filter in order to enhance the
detection of evoked response potentials. In order to compute such filter, this box
needs to receive the whole contain of a session on the first hand, and a succession
of evoked response potentials on the other hand. It then computes the averaged evoked
response potential computes the spatial filter that makes this averaged potential
appear in the whole signal. This can be used e.g. for better P300 signal detection.
It is important to consider the fact that this box will have best results for a
reasonably big number of input channels, possibly all over the scalp (areas where
the evoked response potential can not be seen will be naturally used as references
to reduce noise). The spatial filter results in space reduction to only keep significant
chanels for later detection. Consider using at least 4 times more input channels than
the number of output channels you want. For example, reducing 16 electrodes to 3 channels
for P300 detection is OK.
For more details about xDAWN, see <a href="http://www.icp.inpg.fr/~rivetber/Publications/references/Rivet2009a.pdf">Rivet et al. 2009</a>
or in case this links disapears, <a href="http://www.ncbi.nlm.nih.gov/pubmed/19174332">this website</a>.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Stimulations", "Stimulations"
"Session signal", "Signal"
"Evoked potential epochs", "Signal"
Stimulations
~~~~~~~~~~~~
This input receives the exepriment stimulations. As soon as the "train"
stimulation is received, the spatial filter is computed.
Session signal
~~~~~~~~~~~~~~
This input should receive the whole signal of the session.
Evoked potential epochs
~~~~~~~~~~~~~~~~~~~~~~~
This input should receive the multiple evoked response potentials.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Train-completed Flag", "Stimulations"
.. _Doc_BoxAlgorithm_XDAWNTrainer_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Train stimulation", "Stimulation", "OVTK_StimulationId_Train"
"Spatial filter configuration", "Filename", ""
"Filter dimension", "Integer", "4"
"Save as box config", "Boolean", "true"
Train stimulation
~~~~~~~~~~~~~~~~~
This setting contains the stimulation to use to trigger the training process.
Spatial filter configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting tells the box what configuration file to generate. This configuration file can
be used to set the correct values of a :ref:`Doc_BoxAlgorithm_SpatialFilter` box.
Filter dimension
~~~~~~~~~~~~~~~~
This setting tells how many dimension should be kept out of the spatial filter.
Save as box config
~~~~~~~~~~~~~~~~~~
If true, the file written will be a box configuration override especially for a spatial filter box. Otherwise, it will be an ASCII matrix.
@@ -0,0 +1,96 @@
.. _Doc_BoxAlgorithm_ZeroCrossingDetector:
Zero-Crossing Detector
======================
.. container:: attribution
:Author:
Quentin Barthelemy
:Company:
Mensia Technologies SA
.. image:: images/Doc_BoxAlgorithm_ZeroCrossingDetector.png
Detects zero-crossings of the signal for each channel, with 1 for positive zero-crossings (negative-to-positive), -1 for negatives ones (positive-to-negative), 0 otherwise. For all channels, stimulations mark positive and negatives zero-crossings. For each channel, the rythm is computed in events per min.
Using an hysteresis thresholding, this box detects the zero-crossings of the input, operating on all channels.
Inputs
------
.. csv-table::
:header: "Input Name", "Stream Type"
"Input signal", "Signal"
Input signal
~~~~~~~~~~~~
The input signal :math:`X \in \mathbb{R}^{C \times N}`, composed of :math:`C` sensors and :math:`N` samples.
Outputs
-------
.. csv-table::
:header: "Output Name", "Stream Type"
"Zero-crossing signal", "Signal"
"Zero-crossing stimulations", "Stimulations"
"Events rythm (per min)", "Streamed matrix"
Zero-crossing signal
~~~~~~~~~~~~~~~~~~~~
Zero-crossing signal :math:`Z \in \mathbb{R}^{C \times N}`, composed of :math:`C` sensors and :math:`N` samples.
It is defined as 1 for positive zero-crossings (negative-to-positive), -1 for negatives ones (positive-to-negative), 0 otherwise.
Zero-crossing stimulations
~~~~~~~~~~~~~~~~~~~~~~~~~~
For all channels, stimulations mark positive and negatives zero-crossings.
Events rythm (per min)
~~~~~~~~~~~~~~~~~~~~~~
For each channel, the rythm of negative-to-positive zero-crossings is computed in events per min.
.. _Doc_BoxAlgorithm_ZeroCrossingDetector_Settings:
Settings
--------
.. csv-table::
:header: "Setting Name", "Type", "Default Value"
"Hysteresis threshold", "Float", "0.01"
"Rythm estimation window (in sec)", "Float", "10"
"Negative-to-positive stimulation", "Stimulation", "OVTK_StimulationId_ThresholdPassed_Positive"
"Positive-to-negative stimulation", "Stimulation", "OVTK_StimulationId_ThresholdPassed_Negative"
Hysteresis threshold
~~~~~~~~~~~~~~~~~~~~
This setting defines the value :math:`t` of the hysteresis threshold, to provide a robust detection.
.. figure:: images/ZeroCrossingDetector_thresholding.png
:alt: Difference between naive and hysteresis sign thresholding
:align: center
Difference between naive and hysteresis sign thresholding
Rythm estimation window (in sec)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting defines the length of the time window for the rythm estimation.
Negative-to-positive stimulation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting defines the stimulation id for negative-to-positive crossings.
Positive-to-negative stimulation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting defines the stimulation id for positive-to-negative crossings.
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,193 @@
#include "ovpCAlgorithmMatrixAverage.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
// ________________________________________________________________________________________________________________
//
bool CAlgorithmMatrixAverage::initialize()
{
ip_averagingMethod.initialize(getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod));
ip_matrixCount.initialize(getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount));
ip_matrix.initialize(getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_Matrix));
op_averagedMatrix.initialize(getOutputParameter(OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix));
m_nAverageSamples = 0;
return true;
}
bool CAlgorithmMatrixAverage::uninitialize()
{
for (auto it = m_history.begin(); it != m_history.end(); ++it) { delete *it; }
m_history.clear();
op_averagedMatrix.uninitialize();
ip_matrix.uninitialize();
ip_matrixCount.uninitialize();
ip_averagingMethod.uninitialize();
return true;
}
// ________________________________________________________________________________________________________________
//
bool CAlgorithmMatrixAverage::process()
{
CMatrix* iMatrix = ip_matrix;
CMatrix* oMatrix = op_averagedMatrix;
bool shouldPerformAverage = false;
if (this->isInputTriggerActive(OVP_Algorithm_MatrixAverage_InputTriggerId_Reset))
{
for (auto it = m_history.begin(); it != m_history.end(); ++it) { delete*it; }
m_history.clear();
oMatrix->copyDescription(*iMatrix);
}
if (this->isInputTriggerActive(OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix))
{
if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Moving))
{
CMatrix* swapMatrix;
if (m_history.size() >= ip_matrixCount)
{
swapMatrix = m_history.front();
m_history.pop_front();
}
else
{
swapMatrix = new CMatrix();
swapMatrix->copyDescription(*iMatrix);
}
swapMatrix->copyContent(*iMatrix);
m_history.push_back(swapMatrix);
shouldPerformAverage = (m_history.size() == ip_matrixCount);
}
else if (ip_averagingMethod == uint64_t(EEpochAverageMethod::MovingImmediate))
{
CMatrix* swapMatrix;
if (m_history.size() >= ip_matrixCount)
{
swapMatrix = m_history.front();
m_history.pop_front();
}
else
{
swapMatrix = new CMatrix();
swapMatrix->copyDescription(*iMatrix);
}
swapMatrix->copyContent(*iMatrix);
m_history.push_back(swapMatrix);
shouldPerformAverage = (!m_history.empty());
}
else if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Block))
{
CMatrix* swapMatrix = new CMatrix();
if (m_history.size() >= ip_matrixCount)
{
for (auto it = m_history.begin(); it != m_history.end(); ++it) { delete *it; }
m_history.clear();
}
swapMatrix->copy(*iMatrix);
m_history.push_back(swapMatrix);
shouldPerformAverage = (m_history.size() == ip_matrixCount);
}
else if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Cumulative))
{
m_history.push_back(iMatrix);
shouldPerformAverage = true;
}
else { shouldPerformAverage = false; }
}
if (shouldPerformAverage)
{
oMatrix->resetBuffer();
if (ip_averagingMethod == uint64_t(EEpochAverageMethod::Cumulative))
{
CMatrix* matrix = m_history.at(0);
m_nAverageSamples++;
if (m_nAverageSamples == 1) // If it's the first matrix, the average is the first matrix
{
double* buffer = matrix->getBuffer();
const size_t size = matrix->getBufferElementCount();
m_averageMatrices.clear();
m_averageMatrices.insert(m_averageMatrices.begin(), buffer, buffer + size);
}
else
{
if (matrix->getBufferElementCount() != m_averageMatrices.size()) { return false; }
const double n = double(m_nAverageSamples);
double* iBuffer = matrix->getBuffer();
for (double& value : m_averageMatrices)
{
// calculate cumulative mean as
// mean{k} = mean{k-1} + (new_value - mean{k-1}) / k
// which is a recurrence equivalent to mean{k] = mean{k-1} * (k-1)/k + new_value/k
// but more numerically stable
value += (*iBuffer - value) / n;
iBuffer++;
}
}
double* oBuffer = oMatrix->getBuffer();
for (const double& value : m_averageMatrices)
{
*oBuffer = double(value);
oBuffer++;
}
m_history.clear();
}
else
{
const size_t n = oMatrix->getBufferElementCount();
const double scale = 1. / m_history.size();
for (CMatrix* matrix : m_history)
{
double* oBuffer = oMatrix->getBuffer();
double* iBuffer = matrix->getBuffer();
for (size_t i = 0; i < n; ++i)
{
*oBuffer += *iBuffer * scale;
oBuffer++;
iBuffer++;
}
}
}
this->activateOutputTrigger(OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed, true);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
#include <deque>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CAlgorithmMatrixAverage 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_MatrixAverage)
protected:
Kernel::TParameterHandler<uint64_t> ip_averagingMethod;
Kernel::TParameterHandler<uint64_t> ip_matrixCount;
Kernel::TParameterHandler<CMatrix*> ip_matrix;
Kernel::TParameterHandler<CMatrix*> op_averagedMatrix;
std::deque<CMatrix*> m_history;
std::vector<double> m_averageMatrices;
size_t m_nAverageSamples = 0;
};
class CAlgorithmMatrixAverageDesc final : public IAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Matrix average"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString(""); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Averaging"); }
CString getVersion() const override { return CString("1.1"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_MatrixAverage; }
IPluginObject* create() override { return new CAlgorithmMatrixAverage(); }
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
{
prototype.addInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_Matrix, "Matrix", Kernel::ParameterType_Matrix);
prototype.addInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount, "Matrix count", Kernel::ParameterType_UInteger);
prototype.addInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod, "Averaging Method", Kernel::ParameterType_UInteger);
prototype.addOutputParameter(OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix, "Averaged matrix", Kernel::ParameterType_Matrix);
prototype.addInputTrigger(OVP_Algorithm_MatrixAverage_InputTriggerId_Reset, "Reset");
prototype.addInputTrigger(OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix, "Feed matrix");
prototype.addInputTrigger(OVP_Algorithm_MatrixAverage_InputTriggerId_ForceAverage, "Force average");
prototype.addOutputTrigger(OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed, "Average performed");
return true;
}
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_MatrixAverageDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,277 @@
#if defined TARGET_HAS_ThirdPartyEIGEN
#include "ovpCAlgorithmOnlineCovariance.h"
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
#define COV_DEBUG 0
#if COV_DEBUG
void CAlgorithmOnlineCovariance::dumpMatrix(Kernel::ILogManager &rMgr, const MatrixXdRowMajor &mat, const CString &desc)
{
rMgr << Kernel::LogLevel_Info << desc << "\n";
for (int i = 0 ; i < mat.rows() ; i++)
{
rMgr << Kernel::LogLevel_Info << "Row " << i << ": ";
for (int j = 0 ; j < mat.cols() ; j++) { rMgr << mat(i,j) << " "; }
rMgr << "\n";
}
}
#else
void CAlgorithmOnlineCovariance::dumpMatrix(Kernel::ILogManager& /* mgr */, const MatrixXdRowMajor& /*mat*/, const CString& /*desc*/) { }
#endif
bool CAlgorithmOnlineCovariance::initialize()
{
m_n = 0;
return true;
}
bool CAlgorithmOnlineCovariance::uninitialize() { return true; }
bool CAlgorithmOnlineCovariance::process()
{
// Note: The input parameters must have been set by the caller by now
const Kernel::TParameterHandler<double> ip_Shrinkage(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_Shrinkage));
const Kernel::TParameterHandler<bool> ip_TraceNormalization(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_TraceNormalization));
const Kernel::TParameterHandler<uint64_t> ip_UpdateMethod(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_UpdateMethod));
const Kernel::TParameterHandler<CMatrix*> ip_FeatureVectorSet(getInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors));
Kernel::TParameterHandler<CMatrix*> op_Mean(getOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_Mean));
Kernel::TParameterHandler<CMatrix*> op_CovarianceMatrix(getOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_CovarianceMatrix));
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_Reset))
{
OV_ERROR_UNLESS_KRF(ip_Shrinkage >= 0.0 && ip_Shrinkage <= 1.0, "Invalid shrinkage parameter (expected value between 0 and 1)", Kernel::ErrorType::BadInput);
OV_ERROR_UNLESS_KRF(ip_FeatureVectorSet->getDimensionCount() == 2,
"Invalid feature vector with " << ip_FeatureVectorSet->getDimensionCount() << " dimensions (expected dim = 2)",
Kernel::ErrorType::BadInput);
const size_t nRows = ip_FeatureVectorSet->getDimensionSize(0);
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(nRows >= 1 && nCols >= 1, "Invalid input matrix [" << nRows << "x" << nCols << "(minimum expected = 1x1)", Kernel::ErrorType::BadInput);
this->getLogManager() << Kernel::LogLevel_Debug << "Using shrinkage coeff " << ip_Shrinkage << " ...\n";
this->getLogManager() << Kernel::LogLevel_Debug << "Trace normalization is " << (ip_TraceNormalization ? "[on]" : "[off]") << "\n";
this->getLogManager() << Kernel::LogLevel_Debug << "Using update method " << getTypeManager().getEnumerationEntryNameFromValue(
OVP_TypeId_OnlineCovariance_UpdateMethod, ip_UpdateMethod) << "\n";
// Set the output buffers
op_Mean->resize(1, nCols);
op_CovarianceMatrix->resize(nCols, nCols);
// These keep track of the non-normalized incremental estimates
m_mean.resize(1, nCols);
m_mean.setZero();
m_cov.resize(nCols, nCols);
m_cov.setZero();
m_n = 0;
}
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_Update))
{
const size_t nRows = ip_FeatureVectorSet->getDimensionSize(0);
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
const double* buffer = ip_FeatureVectorSet->getBuffer();
OV_ERROR_UNLESS_KRF(buffer, "Input buffer is NULL", Kernel::ErrorType::BadInput);
// Cast our data into an Eigen matrix. As Eigen doesn't have const double* constructor, we cast away the const.
const Eigen::Map<MatrixXdRowMajor> sampleChunk(const_cast<double*>(buffer), nRows, nCols);
// Update the mean & cov estimates
if (ip_UpdateMethod == uint64_t(EUpdateMethod::ChunkAverage))
{
// 'Average of per-chunk covariance matrices'. This might not be a proper cov over
// the dataset, but seems occasionally produce nicely smoothed results when used for CSP.
const Eigen::MatrixXd chunkMean = sampleChunk.colwise().mean();
const Eigen::MatrixXd chunkCentered = sampleChunk.rowwise() - chunkMean.row(0);
Eigen::MatrixXd chunkCov = (1.0 / double(nRows)) * chunkCentered.transpose() * chunkCentered;
if (ip_TraceNormalization)
{
// This normalization can be seen e.g. Muller-Gerkin & al., 1999. Presumably the idea is to normalize the
// scale of each chunk in order to compensate for possible signal power drift over time during the EEG recording,
// making each chunks' covariance contribute similarly to the average regardless of
// the current average power. Such a normalization could also be implemented in its own
// box and not done here.
chunkCov = chunkCov / chunkCov.trace();
}
m_mean += chunkMean;
m_cov += chunkCov;
m_n++;
// dumpMatrix(this->getLogManager(), sampleChunk, "SampleChunk");
// dumpMatrix(this->getLogManager(), sampleCenteredMean, "SampleCenteredMean");
}
else if (ip_UpdateMethod == uint64_t(EUpdateMethod::Incremental))
{
// Incremental sample-per-sample cov updating.
// It should be implementing the Youngs & Cramer algorithm as described in
// Chan, Golub, Leveq, "Updating formulae and a pairwise algorithm...", 1979
size_t start = 0;
if (m_n == 0)
{
m_mean = sampleChunk.row(0);
start = 1;
m_n = 1;
}
Eigen::MatrixXd chunkContribution;
chunkContribution.resizeLike(m_cov);
chunkContribution.setZero();
for (size_t i = start; i < nRows; ++i)
{
m_mean += sampleChunk.row(i);
const Eigen::MatrixXd diff = (m_n + 1.0) * sampleChunk.row(i) - m_mean;
const Eigen::MatrixXd outerProd = diff.transpose() * diff;
chunkContribution += 1.0 / (m_n * (m_n + 1.0)) * outerProd;
m_n++;
}
if (ip_TraceNormalization) { chunkContribution = chunkContribution / chunkContribution.trace(); }
m_cov += chunkContribution;
// dumpMatrix(this->getLogManager(), sampleChunk, "Sample");
}
#if 0
else if(method == 2)
{
// Increment sample counts
const size_t countBefore = m_n;
const size_t countChunk = nRows;
const size_t countAfter = countBefore + countChunk;
const MatrixXd sampleSum = sampleChunk.colwise().sum();
// Center the chunk
const MatrixXd sampleCentered = sampleChunk.rowwise() - sampleSum.row(0)*(1.0/(double)countChunk);
const MatrixXd sampleCoMoment = (sampleCentered.transpose() * sampleCentered);
m_cov = m_cov + sampleCoMoment;
if(countBefore>0)
{
const MatrixXd meanDifference = (countChunk/(double)countBefore) * m_mean - sampleSum;
const MatrixXd meanDiffOuterProduct = meanDifference.transpose()*meanDifference;
m_cov += meanDiffOuterProduct*countBefore/(countChunk*countAfter);
}
m_mean = m_mean + sampleSum;
m_n = countAfter;
}
else
{
// Increment sample counts
const size_t countBefore = m_n;
const size_t countChunk = nRows;
const size_t countAfter = countBefore + countChunk;
// Insert our data into an Eigen matrix. As Eigen doesn't have const double* constructor, we cast away the const.
const Map<MatrixXdRowMajor> dataMatrix(const_cast<double*>(buffer),nRows,nCols);
// Estimate the current sample means
const MatrixXdRowMajor sampleMean = dataMatrix.colwise().mean();
// Center the current data with the previous(!) mean
const MatrixXdRowMajor sampleCentered = dataMatrix.rowwise() - m_mean.row(0);
// Estimate the current covariance
const MatrixXd sampleCov = (sampleCentered.transpose() * sampleCentered) * (1.0/(double)nRows);
// fixme: recheck the weights ...
// Update the global mean and cov
if(countBefore>0)
{
m_mean = ( m_mean*countBefore + sampleMean*nRows) / (double)countAfter;
m_cov = ( m_cov*countBefore + sampleCov*(countBefore/(double)countAfter) ) / (double)countAfter;
}
else
{
m_mean = sampleMean;
m_cov = sampleCov;
}
m_n = countAfter;
}
#endif
else { OV_ERROR_KRF("Unknown update method [" << CIdentifier(ip_UpdateMethod).str() << "]", Kernel::ErrorType::BadSetting); }
}
// Give output with regularization (mix prior + cov)?
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_GetCov))
{
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(m_n > 0, "No sample to compute covariance", Kernel::ErrorType::BadConfig);
// Converters to CMatrix
Eigen::Map<MatrixXdRowMajor> outputMean(op_Mean->getBuffer(), 1, nCols);
Eigen::Map<MatrixXdRowMajor> outputCov(op_CovarianceMatrix->getBuffer(), nCols, nCols);
// The shrinkage parameter pulls the covariance matrix towards diagonal covariance
Eigen::MatrixXd priorCov;
priorCov.resizeLike(m_cov);
priorCov.setIdentity();
// Mix the prior and the sample estimates according to the shrinkage parameter. We scale by 1/n to normalize
outputMean = m_mean / double(m_n);
outputCov = ip_Shrinkage * priorCov + (1.0 - ip_Shrinkage) * (m_cov / double(m_n));
// Debug block
dumpMatrix(this->getLogManager(), outputMean, "Data mean");
dumpMatrix(this->getLogManager(), m_cov / double(m_n), "Data cov");
dumpMatrix(this->getLogManager(), ip_Shrinkage * priorCov, "Prior cov");
dumpMatrix(this->getLogManager(), outputCov, "Output cov");
}
// Give just the output with no shrinkage?
if (isInputTriggerActive(OVP_Algorithm_OnlineCovariance_Process_GetCovRaw))
{
const size_t nCols = ip_FeatureVectorSet->getDimensionSize(1);
OV_ERROR_UNLESS_KRF(m_n > 0, "No sample to compute covariance", Kernel::ErrorType::BadConfig);
// Converters to CMatrix
Eigen::Map<MatrixXdRowMajor> outputMean(op_Mean->getBuffer(), 1, nCols);
Eigen::Map<MatrixXdRowMajor> outputCov(op_CovarianceMatrix->getBuffer(), nCols, nCols);
// We scale by 1/n to normalize
outputMean = m_mean / double(m_n);
outputCov = m_cov / double(m_n);
// Debug block
dumpMatrix(this->getLogManager(), outputMean, "Data mean");
dumpMatrix(this->getLogManager(), outputCov, "Data Cov");
}
return true;
}
#endif // TARGET_HAS_ThirdPartyEIGEN
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,89 @@
/*
*
* Incremental covariance estimators with shrinkage
*
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyEIGEN
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <Eigen/Dense>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CAlgorithmOnlineCovariance final : virtual public Toolkit::TAlgorithm<IAlgorithm>
{
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_OnlineCovariance)
protected:
// Debug method. Prints the matrix to the logManager. May be disabled in implementation.
static void dumpMatrix(Kernel::ILogManager& mgr, const MatrixXdRowMajor& mat, const CString& desc);
// These are non-normalized estimates for the corresp. statistics
Eigen::MatrixXd m_cov;
Eigen::MatrixXd m_mean;
// The divisor for the above estimates to do the normalization
uint64_t m_n = 0;
};
class CAlgorithmOnlineCovarianceDesc final : virtual public IAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Online Covariance"); }
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Incrementally computes covariance with shrinkage."); }
CString getDetailedDescription() const override { return CString("Regularized covariance output is computed as (diag*shrink + cov)"); }
CString getCategory() const override { return CString(""); }
CString getVersion() const override { return CString("0.5"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_OnlineCovariance; }
IPluginObject* create() override { return new CAlgorithmOnlineCovariance; }
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
{
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_Shrinkage, "Shrinkage", Kernel::ParameterType_Float);
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_InputVectors, "Input vectors", Kernel::ParameterType_Matrix);
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_UpdateMethod, "Cov update method", Kernel::ParameterType_Enumeration,
OVP_TypeId_OnlineCovariance_UpdateMethod);
prototype.addInputParameter(OVP_Algorithm_OnlineCovariance_InputParameterId_TraceNormalization, "Trace normalization", Kernel::ParameterType_Boolean);
// The algorithm returns these outputs
prototype.addOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_Mean, "Mean vector", Kernel::ParameterType_Matrix);
prototype.addOutputParameter(OVP_Algorithm_OnlineCovariance_OutputParameterId_CovarianceMatrix, "Covariance matrix", Kernel::ParameterType_Matrix);
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Reset, "Reset the algorithm");
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_Update, "Append a chunk of data");
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_GetCov, "Get the current regularized covariance matrix & mean");
prototype.addInputTrigger(OVP_Algorithm_OnlineCovariance_Process_GetCovRaw, "Get the current covariance matrix & mean");
return true;
}
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_OnlineCovarianceDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyEIGEN
@@ -0,0 +1,93 @@
#include "ovpCBoxAlgorithmChannelRename.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmChannelRename::initialize()
{
std::vector<CString> tokens;
const CString setting = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const size_t nToken = split(setting, Toolkit::String::TSplitCallback<std::vector<CString>>(tokens), OV_Value_EnumeratedStringSeparator);
m_names.clear();
for (size_t i = 0; i < nToken; ++i) { m_names.push_back(tokens[i].toASCIIString()); }
this->getStaticBoxContext().getOutputType(0, m_typeID);
if (m_typeID == OV_TypeId_Signal)
{
m_decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmChannelRename>(*this, 0);
m_encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmChannelRename>(*this, 0);
}
else if (m_typeID == OV_TypeId_StreamedMatrix || m_typeID == OV_TypeId_CovarianceMatrix || m_typeID == OV_TypeId_TimeFrequency)
{
m_decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmChannelRename>(*this, 0);
m_encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmChannelRename>(*this, 0);
}
else if (m_typeID == OV_TypeId_Spectrum)
{
m_decoder = new Toolkit::TSpectrumDecoder<CBoxAlgorithmChannelRename>(*this, 0);
m_encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmChannelRename>(*this, 0);
}
else { OV_ERROR_KRF("Incompatible stream type", Kernel::ErrorType::BadConfig); }
ip_Matrix = m_encoder.getInputMatrix();
op_Matrix = m_decoder.getOutputMatrix();
m_encoder.getInputMatrix().setReferenceTarget(m_decoder.getOutputMatrix());
if (m_typeID == OV_TypeId_Signal) { m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate()); }
if (m_typeID == OV_TypeId_Spectrum)
{
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
m_encoder.getInputFrequencyAbcissa().setReferenceTarget(m_decoder.getOutputFrequencyAbcissa());
}
return true;
}
bool CBoxAlgorithmChannelRename::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmChannelRename::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmChannelRename::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t chunk = 0; chunk < boxContext.getInputChunkCount(0); ++chunk)
{
m_decoder.decode(chunk);
if (m_decoder.isHeaderReceived())
{
ip_Matrix->copyDescription(*op_Matrix);
for (size_t channel = 0; channel < ip_Matrix->getDimensionSize(0) && channel < m_names.size(); ++channel)
{
ip_Matrix->setDimensionLabel(0, channel, m_names[channel].c_str());
}
m_encoder.encodeHeader();
}
if (m_decoder.isBufferReceived()) { m_encoder.encodeBuffer(); }
if (m_decoder.isEndReceived()) { m_encoder.encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, chunk), boxContext.getInputChunkEndTime(0, chunk));
boxContext.markInputAsDeprecated(0, chunk);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,106 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <string>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmChannelRename 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_ChannelRename)
protected:
Toolkit::TGenericDecoder<CBoxAlgorithmChannelRename> m_decoder;
Toolkit::TGenericEncoder<CBoxAlgorithmChannelRename> m_encoder;
CIdentifier m_typeID = CIdentifier::undefined();
Kernel::TParameterHandler<CMatrix*> ip_Matrix;
Kernel::TParameterHandler<CMatrix*> op_Matrix;
std::vector<std::string> m_names;
};
class CBoxAlgorithmChannelRenameListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmChannelRenameDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Channel Rename"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Renames channels of different types of streamed matrices"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Signal processing/Channels"); }
CString getVersion() const override { return CString("1.1"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
CString getUpdatedSoftwareVersion() const override { return CString("1.1.0"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ChannelRename; }
IPluginObject* create() override { return new CBoxAlgorithmChannelRename; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmChannelRenameListener; }
void releaseBoxListener(IBoxListener* boxListener) const override { delete boxListener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input matrix", OV_TypeId_Signal);
prototype.addOutput("Output matrix", OV_TypeId_Signal);
prototype.addSetting("New channel names", OV_TypeId_String, "Channel 1;Channel 2");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_TimeFrequency);
prototype.addInputSupport(OV_TypeId_CovarianceMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_TimeFrequency);
prototype.addOutputSupport(OV_TypeId_CovarianceMatrix);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ChannelRenameDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,292 @@
#include "ovpCBoxAlgorithmChannelSelector.h"
#include <limits>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace {
size_t FindChannel(const CMatrix& matrix, const CString& channel, const EMatchMethod matchMethod, const size_t start = 0)
{
size_t result = std::numeric_limits<size_t>::max();
const size_t nChannel = matrix.getDimensionSize(0);
if (matchMethod == EMatchMethod::Name)
{
for (size_t i = start; i < matrix.getDimensionSize(0); ++i)
{
if (Toolkit::String::isAlmostEqual(matrix.getDimensionLabel(0, i), channel, false)) { result = i; }
}
}
else if (matchMethod == EMatchMethod::Index)
{
try
{
const int value = std::stoi(channel.toASCIIString());
if (value < 0)
{
size_t idx = size_t(- value - 1); // => makes it 0-indexed !
if (idx < nChannel)
{
idx = nChannel - idx - 1; // => reverses index
if (start <= idx) { result = idx; }
}
}
if (value > 0)
{
const size_t index = size_t(value - 1); // => makes it 0-indexed !
if (index < nChannel) { if (start <= index) { result = index; } }
}
}
catch (const std::exception&)
{
// catch block intentionnaly left blank
}
}
else if (matchMethod == EMatchMethod::Smart)
{
if (result == std::numeric_limits<size_t>::max()) { result = FindChannel(matrix, channel, EMatchMethod::Name, start); }
if (result == std::numeric_limits<size_t>::max()) { result = FindChannel(matrix, channel, EMatchMethod::Index, start); }
}
return result;
}
} // namespace
bool CBoxAlgorithmChannelSelector::initialize()
{
const Kernel::IBox& boxContext = this->getStaticBoxContext();
CIdentifier typeID;
boxContext.getOutputType(0, typeID);
m_decoder = nullptr;
m_encoder = nullptr;
if (typeID == OV_TypeId_Signal)
{
auto* encoder = new Toolkit::TSignalEncoder<CBoxAlgorithmChannelSelector>;
auto* decoder = new Toolkit::TSignalDecoder<CBoxAlgorithmChannelSelector>;
encoder->initialize(*this, 0);
decoder->initialize(*this, 0);
encoder->getInputSamplingRate().setReferenceTarget(decoder->getOutputSamplingRate());
m_decoder = decoder;
m_encoder = encoder;
m_iMatrix = decoder->getOutputMatrix();
m_oMatrix = encoder->getInputMatrix();
}
else if (typeID == OV_TypeId_Spectrum)
{
auto* encoder = new Toolkit::TSpectrumEncoder<CBoxAlgorithmChannelSelector>;
auto* decoder = new Toolkit::TSpectrumDecoder<CBoxAlgorithmChannelSelector>;
encoder->initialize(*this, 0);
decoder->initialize(*this, 0);
encoder->getInputFrequencyAbscissa().setReferenceTarget(decoder->getOutputFrequencyAbscissa());
encoder->getInputSamplingRate().setReferenceTarget(decoder->getOutputSamplingRate());
m_decoder = decoder;
m_encoder = encoder;
m_iMatrix = decoder->getOutputMatrix();
m_oMatrix = encoder->getInputMatrix();
}
else if (typeID == OV_TypeId_StreamedMatrix)
{
auto* encoder = new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmChannelSelector>;
auto* decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmChannelSelector>;
encoder->initialize(*this, 0);
decoder->initialize(*this, 0);
m_decoder = decoder;
m_encoder = encoder;
m_iMatrix = decoder->getOutputMatrix();
m_oMatrix = encoder->getInputMatrix();
}
else { OV_ERROR_KRF("Invalid input type [" << typeID.str() << "]", Kernel::ErrorType::BadInput); }
m_vLookup.clear();
return true;
}
bool CBoxAlgorithmChannelSelector::uninitialize()
{
if (m_decoder)
{
m_decoder->uninitialize();
delete m_decoder;
}
if (m_encoder)
{
m_encoder->uninitialize();
delete m_encoder;
}
return true;
}
bool CBoxAlgorithmChannelSelector::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmChannelSelector::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder->decode(i);
if (m_decoder->isHeaderReceived())
{
CString settingValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const ESelectionMethod selectionMethod = ESelectionMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
const EMatchMethod matchMethod = EMatchMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)));
if (selectionMethod == ESelectionMethod::Select_EEG)
{
// ______________________________________________________________________________________________________________________________________________________
//
// Collects channels with names corresponding to EEG
// ______________________________________________________________________________________________________________________________________________________
//
CString eegChannelNames = this->getConfigurationManager().expand("${Box_ChannelSelector_EEGChannelNames}");
std::vector<CString> token;
const size_t nToken = split(eegChannelNames, Toolkit::String::TSplitCallback<std::vector<CString>>(token),
OV_Value_EnumeratedStringSeparator);
for (size_t j = 0; j < m_iMatrix->getDimensionSize(0); ++j)
{
for (size_t k = 0; k < nToken; ++k)
{
if (Toolkit::String::isAlmostEqual(m_iMatrix->getDimensionLabel(0, j), token[k], false)) { m_vLookup.push_back(j); }
}
}
}
else
{
// ______________________________________________________________________________________________________________________________________________________
//
// Splits the channel list in order to build up the look up table
// The look up table is later used to fill in the matrix content
// ______________________________________________________________________________________________________________________________________________________
//
std::vector<CString> tokens;
const size_t nToken = split(settingValue, Toolkit::String::TSplitCallback<std::vector<CString>>(tokens),
OV_Value_EnumeratedStringSeparator);
for (size_t j = 0; j < nToken; ++j)
{
std::vector<CString> subTokens;
// Checks if the token is a range
if (split(tokens[j], Toolkit::String::TSplitCallback<std::vector<CString>>(subTokens),
OV_Value_RangeStringSeparator) == 2)
{
// Finds the first & second part of the range (only index based)
size_t startIdx = FindChannel(*m_iMatrix, subTokens[0], EMatchMethod::Index);
size_t endIdx = FindChannel(*m_iMatrix, subTokens[1], EMatchMethod::Index);
// When first or second part is not found but associated token is empty, don't consider this as an error
if (startIdx == std::numeric_limits<size_t>::max() && subTokens[0] == CString("")) { startIdx = 0; }
if (endIdx == std::numeric_limits<size_t>::max() && subTokens[1] == CString("")) { endIdx = m_iMatrix->getDimensionSize(0) - 1; }
// After these corections, if either first or second token were not found, or if start index is greater than start index, consider this an error and invalid range
OV_ERROR_UNLESS_KRF(
startIdx != std::numeric_limits<size_t>::max() && endIdx != std::numeric_limits<size_t>::max() && startIdx <= endIdx,
"Invalid channel range [" << tokens[j] << "] - splitted as [" << subTokens[0] << "][" << subTokens[1] << "]",
Kernel::ErrorType::BadSetting);
// The range is valid so selects all the channels in this range
this->getLogManager() << Kernel::LogLevel_Debug << "For range [" << tokens[j] << "] :\n";
for (size_t k = startIdx; k <= endIdx; ++k)
{
m_vLookup.push_back(k);
this->getLogManager() << Kernel::LogLevel_Debug << " Selected channel [" << k + 1 << "]\n";
}
}
else
{
// This is not a range, so we can consider the whole token as a single token name
size_t found = false;
size_t index = std::numeric_limits<size_t>::max();
// Looks for all the channels with this name
while ((index = FindChannel(*m_iMatrix, tokens[j], matchMethod, index + 1)) != std::numeric_limits<size_t>::max())
{
found = true;
m_vLookup.push_back(index);
this->getLogManager() << Kernel::LogLevel_Debug << "Selected channel [" << index + 1 << "]\n";
}
OV_ERROR_UNLESS_KRF(found, "Invalid channel [" << tokens[j] << "]", Kernel::ErrorType::BadSetting);
}
}
// ______________________________________________________________________________________________________________________________________________________
//
// When selection method is set to reject
// We have to revert the selection building up a new look up table and replacing the old one
// ______________________________________________________________________________________________________________________________________________________
//
if (selectionMethod == ESelectionMethod::Reject)
{
std::vector<size_t> inversedLookup;
for (size_t j = 0; j < m_iMatrix->getDimensionSize(0); ++j)
{
bool selected = false;
for (size_t k = 0; k < m_vLookup.size(); ++k) { selected |= (m_vLookup[k] == j); }
if (!selected) { inversedLookup.push_back(j); }
}
m_vLookup = inversedLookup;
}
}
// ______________________________________________________________________________________________________________________________________________________
//
// Now we have the exact topology of the output matrix :)
// ______________________________________________________________________________________________________________________________________________________
//
OV_ERROR_UNLESS_KRF(!m_vLookup.empty(), "No channel selected", Kernel::ErrorType::BadConfig);
m_oMatrix->resize(m_vLookup.size(), m_iMatrix->getDimensionSize(1));
for (size_t j = 0; j < m_vLookup.size(); ++j)
{
if (m_vLookup[j] < m_iMatrix->getDimensionSize(0)) { m_oMatrix->setDimensionLabel(0, j, m_iMatrix->getDimensionLabel(0, m_vLookup[j])); }
else { m_oMatrix->setDimensionLabel(0, j, "Missing channel"); }
}
for (size_t j = 0; j < m_iMatrix->getDimensionSize(1); ++j) { m_oMatrix->setDimensionLabel(1, j, m_iMatrix->getDimensionLabel(1, j)); }
m_encoder->encodeHeader();
}
if (m_decoder->isBufferReceived())
{
// ______________________________________________________________________________________________________________________________________________________
//
// When a buffer is received, just copy the channel content depending on the look up table
// ______________________________________________________________________________________________________________________________________________________
//
const size_t nSample = m_oMatrix->getDimensionSize(1);
for (size_t j = 0; j < m_vLookup.size(); ++j)
{
if (m_vLookup[j] < m_iMatrix->getDimensionSize(0))
{
memcpy(m_oMatrix->getBuffer() + j * nSample, m_iMatrix->getBuffer() + m_vLookup[j] * nSample, nSample * sizeof(double));
}
}
m_encoder->encodeBuffer();
}
if (m_decoder->isEndReceived()) { m_encoder->encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,160 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <string>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmChannelSelector 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_ChannelSelector)
protected:
Toolkit::TDecoder<CBoxAlgorithmChannelSelector>* m_decoder = nullptr;
Toolkit::TEncoder<CBoxAlgorithmChannelSelector>* m_encoder = nullptr;
CMatrix* m_iMatrix = nullptr;
CMatrix* m_oMatrix = nullptr;
std::vector<size_t> m_vLookup;
};
class CBoxAlgorithmChannelSelectorListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onOutputTypeChanged(Kernel::IBox& box, const size_t /*index*/) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(0, typeID);
if (typeID == OV_TypeId_Signal || typeID == OV_TypeId_Spectrum || typeID == OV_TypeId_StreamedMatrix)
{
box.setInputType(0, typeID);
return true;
}
box.getInputType(0, typeID);
box.setOutputType(0, typeID);
OV_ERROR_KRF("Invalid output type [" << typeID.str() << "] (expected Signal, Spectrum or Streamed Matrix)", Kernel::ErrorType::BadOutput);
}
bool onInputTypeChanged(Kernel::IBox& box, const size_t /*index*/) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(0, typeID);
if (typeID == OV_TypeId_Signal || typeID == OV_TypeId_Spectrum || typeID == OV_TypeId_StreamedMatrix)
{
box.setOutputType(0, typeID);
return true;
}
box.getOutputType(0, typeID);
box.setInputType(0, typeID);
OV_ERROR_KRF("Invalid input type [" << typeID.str() << "] (expected Signal, Spectrum or Streamed Matrix)", Kernel::ErrorType::BadInput);
}
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
{
//we are only interested in the setting 0 and the type changes (select or reject)
if ((index == 0 || index == 1) && (!m_hasUserSetName))
{
CString channels;
box.getSettingValue(0, channels);
CString method;
CIdentifier enumID = CIdentifier::undefined();
box.getSettingValue(1, method);
box.getSettingType(1, enumID);
const ESelectionMethod methodID = ESelectionMethod(this->getTypeManager().getEnumerationEntryValueFromName(enumID, method));
if (methodID == ESelectionMethod::Reject) { channels = CString("!") + channels; }
box.setName(channels);
}
return true;
}
bool onNameChanged(Kernel::IBox& box) override
//when user set box name manually
{
if (m_hasUserSetName)
{
const CString rename = box.getName();
if (rename == CString("Channel Selector"))
{//default name, we switch back to default behaviour
m_hasUserSetName = false;
}
}
else { m_hasUserSetName = true; }
return true;
}
bool initialize() override
{
m_hasUserSetName = false;//need to initialize this value
return true;
}
private:
bool m_hasUserSetName = false;
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmChannelSelectorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Channel Selector"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Select a subset of signal channels"); }
CString getDetailedDescription() const override { return CString("Selection can be based on channel name (case-sensitive) or index starting from 0"); }
CString getCategory() const override { return CString("Signal processing/Channels"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_ChannelSelector; }
IPluginObject* create() override { return new CBoxAlgorithmChannelSelector; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmChannelSelectorListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Channel List", OV_TypeId_String, ":");
prototype.addSetting("Action", OVP_TypeId_SelectionMethod, "Select");
prototype.addSetting("Channel Matching Method", OVP_TypeId_MatchMethod, "Smart");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,128 @@
#include "ovpCBoxAlgorithmCrop.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmCrop::initialize()
{
CIdentifier inputTypeID;
getStaticBoxContext().getInputType(0, inputTypeID);
if (inputTypeID == OV_TypeId_StreamedMatrix)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixEncoder));
}
else if (inputTypeID == OV_TypeId_FeatureVector)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorEncoder));
}
else if (inputTypeID == OV_TypeId_Signal)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
}
else if (inputTypeID == OV_TypeId_Spectrum)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumEncoder));
}
else { return false; }
m_decoder->initialize();
m_encoder->initialize();
if (inputTypeID == OV_TypeId_StreamedMatrix) { }
else if (inputTypeID == OV_TypeId_FeatureVector) { }
else if (inputTypeID == OV_TypeId_Signal)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
}
else if (inputTypeID == OV_TypeId_Spectrum)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_FrequencyAbscissa)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_FrequencyAbscissa));
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Sampling));
}
m_matrix = new CMatrix();
Kernel::TParameterHandler<CMatrix*>(m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix)).setReferenceTarget(m_matrix);
Kernel::TParameterHandler<CMatrix*>(m_decoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix)).setReferenceTarget(m_matrix);
m_cropMethod = ECropMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0)));
m_minCropValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_maxCropValue = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
OV_ERROR_UNLESS_KRF(m_minCropValue < m_maxCropValue,
"Invalid crop values: minimum crop value [" << m_minCropValue << "] should be lower than the maximum crop value ["
<< m_maxCropValue << "]", Kernel::ErrorType::BadSetting);
return true;
}
bool CBoxAlgorithmCrop::uninitialize()
{
delete m_matrix;
m_encoder->uninitialize();
m_decoder->uninitialize();
getAlgorithmManager().releaseAlgorithm(*m_encoder);
getAlgorithmManager().releaseAlgorithm(*m_decoder);
return true;
}
bool CBoxAlgorithmCrop::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmCrop::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
Kernel::TParameterHandler<const IMemoryBuffer*> iHandle(
m_decoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
Kernel::TParameterHandler<IMemoryBuffer*> oHandle(m_encoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_OutputParameterId_EncodedMemoryBuffer));
iHandle = boxContext.getInputChunk(0, i);
oHandle = boxContext.getOutputChunk(0);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedHeader))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeHeader);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
{
double* buffer = m_matrix->getBuffer();
for (size_t j = 0; j < m_matrix->getBufferElementCount(); j++, buffer++)
{
if (*buffer < m_minCropValue && (m_cropMethod == ECropMethod::Min || m_cropMethod == ECropMethod::MinMax)) { *buffer = m_minCropValue; }
if (*buffer > m_maxCropValue && (m_cropMethod == ECropMethod::Max || m_cropMethod == ECropMethod::MinMax)) { *buffer = m_maxCropValue; }
}
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeBuffer);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeEnd);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,98 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmCrop final : virtual 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_Crop)
protected:
CMatrix* m_matrix = nullptr;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
double m_minCropValue = 0;
double m_maxCropValue = 0;
ECropMethod m_cropMethod = ECropMethod::MinMax;
};
class CBoxAlgorithmCropListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmCropDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Crop"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Truncates signal values to a specified range"); }
CString getDetailedDescription() const override { return CString("Minimum or maximum or both limits can be specified"); }
CString getCategory() const override { return CString("Signal processing/Basic"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_Crop; }
IPluginObject* create() override { return new CBoxAlgorithmCrop; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmCropListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input matrix", OV_TypeId_StreamedMatrix);
prototype.addOutput("Output matrix", OV_TypeId_StreamedMatrix);
prototype.addSetting("Crop method", OVP_TypeId_CropMethod, "MinMax");
prototype.addSetting("Min crop value", OV_TypeId_Float, "-1");
prototype.addSetting("Max crop value", OV_TypeId_Float, "1");
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_FeatureVector);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_FeatureVector);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_CropDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,146 @@
#include "ovpCBoxAlgorithmEpochAverage.h"
#include "../../algorithms/basic/ovpCAlgorithmMatrixAverage.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmEpochAverage::initialize()
{
CIdentifier inputTypeId;
getStaticBoxContext().getInputType(0, inputTypeId);
if (inputTypeId == OV_TypeId_StreamedMatrix || inputTypeId == OV_TypeId_TimeFrequency)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixEncoder));
}
else if (inputTypeId == OV_TypeId_FeatureVector)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_FeatureVectorEncoder));
}
else if (inputTypeId == OV_TypeId_Signal)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
}
else if (inputTypeId == OV_TypeId_Spectrum)
{
m_decoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumDecoder));
m_encoder = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SpectrumEncoder));
}
else { return false; }
m_decoder->initialize();
m_encoder->initialize();
m_matrixAverage = &getAlgorithmManager().getAlgorithm(getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_MatrixAverage));
m_matrixAverage->initialize();
if (inputTypeId == OV_TypeId_StreamedMatrix) { }
else if (inputTypeId == OV_TypeId_FeatureVector) { }
else if (inputTypeId == OV_TypeId_Signal)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
}
else if (inputTypeId == OV_TypeId_Spectrum)
{
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_FrequencyAbscissa)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_FrequencyAbscissa));
m_encoder->getInputParameter(OVP_GD_Algorithm_SpectrumEncoder_InputParameterId_Sampling)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_SpectrumDecoder_OutputParameterId_Sampling));
}
ip_averagingMethod.initialize(m_matrixAverage->getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_AveragingMethod));
ip_matrixCount.initialize(m_matrixAverage->getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_MatrixCount));
ip_averagingMethod = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0));
ip_matrixCount = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1));
m_matrixAverage->getInputParameter(OVP_Algorithm_MatrixAverage_InputParameterId_Matrix)->setReferenceTarget(
m_decoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix));
m_encoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_InputParameterId_Matrix)->setReferenceTarget(
m_matrixAverage->getOutputParameter(OVP_Algorithm_MatrixAverage_OutputParameterId_AveragedMatrix));
OV_ERROR_UNLESS_KRF(ip_matrixCount > 0, "Invalid number of epochs (expected value > 0)", Kernel::ErrorType::BadSetting);
return true;
}
bool CBoxAlgorithmEpochAverage::uninitialize()
{
CIdentifier inputTypeID;
getStaticBoxContext().getInputType(0, inputTypeID);
if (inputTypeID == OV_TypeId_StreamedMatrix || inputTypeID == OV_TypeId_FeatureVector || inputTypeID == OV_TypeId_Signal ||
inputTypeID == OV_TypeId_Spectrum)
{
ip_averagingMethod.uninitialize();
ip_matrixCount.uninitialize();
m_matrixAverage->uninitialize();
m_encoder->uninitialize();
m_decoder->uninitialize();
getAlgorithmManager().releaseAlgorithm(*m_matrixAverage);
getAlgorithmManager().releaseAlgorithm(*m_encoder);
getAlgorithmManager().releaseAlgorithm(*m_decoder);
}
return true;
}
bool CBoxAlgorithmEpochAverage::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmEpochAverage::process()
{
Kernel::IBoxIO& boxContext = getDynamicBoxContext();
const size_t nInput = this->getStaticBoxContext().getInputCount();
for (size_t i = 0; i < nInput; ++i)
{
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
{
Kernel::TParameterHandler<const IMemoryBuffer*> iMemoryBufferHandle(
m_decoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
Kernel::TParameterHandler<IMemoryBuffer*> oMemoryBufferHandle(
m_encoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixEncoder_OutputParameterId_EncodedMemoryBuffer));
iMemoryBufferHandle = boxContext.getInputChunk(i, j);
oMemoryBufferHandle = boxContext.getOutputChunk(i);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedHeader))
{
m_matrixAverage->process(OVP_Algorithm_MatrixAverage_InputTriggerId_Reset);
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeHeader);
boxContext.markOutputAsReadyToSend(i, boxContext.getInputChunkStartTime(i, j), boxContext.getInputChunkEndTime(i, j));
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
{
m_matrixAverage->process(OVP_Algorithm_MatrixAverage_InputTriggerId_FeedMatrix);
if (m_matrixAverage->isOutputTriggerActive(OVP_Algorithm_MatrixAverage_OutputTriggerId_AveragePerformed))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeBuffer);
boxContext.markOutputAsReadyToSend(i, boxContext.getInputChunkStartTime(i, j), boxContext.getInputChunkEndTime(i, j));
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_StreamedMatrixEncoder_InputTriggerId_EncodeEnd);
boxContext.markOutputAsReadyToSend(i, boxContext.getInputChunkStartTime(i, j), boxContext.getInputChunkEndTime(i, j));
}
boxContext.markInputAsDeprecated(i, j);
}
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,104 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmEpochAverage 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_EpochAverage)
protected:
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::IAlgorithmProxy* m_matrixAverage = nullptr;
Kernel::TParameterHandler<uint64_t> ip_matrixCount;
Kernel::TParameterHandler<uint64_t> ip_averagingMethod;
};
class CBoxAlgorithmEpochAverageListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmEpochAverageDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Epoch average"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Averages matrices among time, this can be used to enhance ERPs"); }
CString getDetailedDescription() const override
{
return CString("This box can average matrices of different types including signal, spectrum or feature vectors");
}
CString getCategory() const override { return CString("Signal processing/Averaging"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_EpochAverage; }
IPluginObject* create() override { return new CBoxAlgorithmEpochAverage(); }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmEpochAverageListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input epochs", OV_TypeId_StreamedMatrix);
prototype.addOutput("Averaged epochs", OV_TypeId_StreamedMatrix);
prototype.addSetting("Averaging type", OVP_TypeId_EpochAverageMethod, "Moving epoch average");
prototype.addSetting("Epoch count", OV_TypeId_Integer, "4");
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_Spectrum);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_FeatureVector);
prototype.addInputSupport(OV_TypeId_TimeFrequency);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_Spectrum);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_FeatureVector);
prototype.addOutputSupport(OV_TypeId_TimeFrequency);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_EpochAverageDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,40 @@
#include "ovpCBoxAlgorithmIdentity.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
void CBoxAlgorithmIdentity::release() { delete this; }
bool CBoxAlgorithmIdentity::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmIdentity::process()
{
Kernel::IBoxIO* boxContext = getBoxAlgorithmContext()->getDynamicBoxContext();
const size_t nInput = getBoxAlgorithmContext()->getStaticBoxContext()->getInputCount();
uint64_t tStart = 0;
uint64_t tEnd = 0;
size_t size = 0;
const uint8_t* buffer = nullptr;
for (size_t i = 0; i < nInput; ++i)
{
for (size_t j = 0; j < boxContext->getInputChunkCount(i); ++j)
{
boxContext->getInputChunk(i, j, tStart, tEnd, size, buffer);
boxContext->appendOutputChunkData(i, buffer, size);
boxContext->markOutputAsReadyToSend(i, tStart, tEnd);
boxContext->markInputAsDeprecated(i, j);
}
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,121 @@
#pragma once
#include "../../ovp_defines.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmIdentity final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_Identity)
};
class CBoxAlgorithmIdentityListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
static bool check(Kernel::IBox& box)
{
size_t i;
for (i = 0; i < box.getInputCount(); ++i) { box.setInputName(i, ("Input stream " + std::to_string(i + 1)).c_str()); }
for (i = 0; i < box.getOutputCount(); ++i) { box.setOutputName(i, ("Output stream " + std::to_string(i + 1)).c_str()); }
return true;
}
bool onDefaultInitialized(Kernel::IBox& box) override
{
box.setInputType(0, OV_TypeId_Signal);
box.setOutputType(0, OV_TypeId_Signal);
return true;
}
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_Signal);
box.addOutput("", OV_TypeId_Signal, box.getUnusedInputIdentifier());
check(box);
return true;
}
bool onInputRemoved(Kernel::IBox& box, const size_t index) override
{
box.removeOutput(index);
check(box);
return true;
}
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(index, typeID);
return true;
}
bool onOutputAdded(Kernel::IBox& box, const size_t index) override
{
box.setOutputType(index, OV_TypeId_Signal);
box.addInput("", OV_TypeId_Signal, box.getUnusedOutputIdentifier());
check(box);
return true;
}
bool onOutputRemoved(Kernel::IBox& box, const size_t index) override
{
box.removeInput(index);
check(box);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(index, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmIdentityDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Identity"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Duplicates input to output"); }
CString getDetailedDescription() const override { return CString("This simply duplicates intput on its output"); }
CString getCategory() const override { return CString("Signal processing/Basic"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_Identity; }
IPluginObject* create() override { return new CBoxAlgorithmIdentity(); }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmIdentityListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input stream", OV_TypeId_Signal);
prototype.addOutput("Output stream", OV_TypeId_Signal);
prototype.addFlag(Kernel::BoxFlag_CanAddOutput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_IdentityDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,132 @@
#include "ovpCBoxAlgorithmReferenceChannel.h"
#include <limits>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
namespace {
size_t FindChannel(const CMatrix& matrix, const CString& channel, const EMatchMethod matchMethod, const size_t start = 0)
{
size_t res = std::numeric_limits<size_t>::max();
if (matchMethod == EMatchMethod::Name)
{
for (size_t i = start; i < matrix.getDimensionSize(0); ++i)
{
if (Toolkit::String::isAlmostEqual(matrix.getDimensionLabel(0, i), channel, false)) { res = i; }
}
}
else if (matchMethod == EMatchMethod::Index)
{
try
{
size_t value = std::stoul(channel.toASCIIString());
value--; // => makes it 0-indexed !
if (start <= size_t(value) && size_t(value) < matrix.getDimensionSize(0)) { res = size_t(value); }
}
catch (const std::exception&)
{
// catch block intentionnaly left blank
}
}
else if (matchMethod == EMatchMethod::Smart)
{
if (res == std::numeric_limits<size_t>::max()) { res = FindChannel(matrix, channel, EMatchMethod::Name, start); }
if (res == std::numeric_limits<size_t>::max()) { res = FindChannel(matrix, channel, EMatchMethod::Index, start); }
}
return res;
}
} // namespace
bool CBoxAlgorithmReferenceChannel::initialize()
{
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_encoder.getInputSamplingRate().setReferenceTarget(m_decoder.getOutputSamplingRate());
return true;
}
bool CBoxAlgorithmReferenceChannel::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
bool CBoxAlgorithmReferenceChannel::processInput(const size_t /*index*/)
{
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmReferenceChannel::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
if (m_decoder.isHeaderReceived())
{
CMatrix& iMatrix = *m_decoder.getOutputMatrix();
CMatrix& oMatrix = *m_encoder.getInputMatrix();
OV_ERROR_UNLESS_KRF(iMatrix.getDimensionSize(0) >= 2,
"Invalid input matrix with [" << iMatrix.getDimensionSize(0) << "] channels (expected channels >= 2)", Kernel::ErrorType::BadInput);
CString channel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const EMatchMethod method = EMatchMethod(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
m_referenceChannelIdx = FindChannel(iMatrix, channel, method, 0);
OV_ERROR_UNLESS_KRF(m_referenceChannelIdx != std::numeric_limits<size_t>::max(), "Invalid channel [" << channel << "]: channel not found",
Kernel::ErrorType::BadSetting);
if (FindChannel(*m_decoder.getOutputMatrix(), channel, method, m_referenceChannelIdx + 1) != std::numeric_limits<size_t>::max())
{
OV_WARNING_K("Multiple channels match for setting [" << channel << "]. Selecting [" << m_referenceChannelIdx << "]");
}
oMatrix.resize(iMatrix.getDimensionSize(0) - 1, iMatrix.getDimensionSize(1));
for (size_t j = 0, k = 0; j < iMatrix.getDimensionSize(0); ++j)
{
if (j != m_referenceChannelIdx) { oMatrix.setDimensionLabel(0, k++, iMatrix.getDimensionLabel(0, j)); }
}
m_encoder.encodeHeader();
}
if (m_decoder.isBufferReceived())
{
CMatrix& iMatrix = *m_decoder.getOutputMatrix();
CMatrix& oMatrix = *m_encoder.getInputMatrix();
double* iBuffer = iMatrix.getBuffer();
double* oBuffer = oMatrix.getBuffer();
double* refBuffer = iMatrix.getBuffer() + m_referenceChannelIdx * iMatrix.getDimensionSize(1);
const size_t nChannel = iMatrix.getDimensionSize(0);
const size_t nSample = iMatrix.getDimensionSize(1);
for (size_t j = 0; j < nChannel; ++j)
{
if (j != m_referenceChannelIdx)
{
for (size_t k = 0; k < nSample; ++k) { oBuffer[k] = iBuffer[k] - refBuffer[k]; }
oBuffer += nSample;
}
iBuffer += nSample;
}
m_encoder.encodeBuffer();
}
if (m_decoder.isEndReceived()) { m_encoder.encodeEnd(); }
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,62 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmReferenceChannel 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_ReferenceChannel)
protected:
Toolkit::TSignalDecoder<CBoxAlgorithmReferenceChannel> m_decoder;
Toolkit::TSignalEncoder<CBoxAlgorithmReferenceChannel> m_encoder;
size_t m_referenceChannelIdx = 0;
};
class CBoxAlgorithmReferenceChannelDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Reference Channel"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Subtracts the value of the reference channel from all other channels"); }
CString getDetailedDescription() const override { return CString("Reference channel must be specified as a parameter for the box"); }
CString getCategory() const override { return CString("Signal processing/Channels"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_ReferenceChannel; }
IPluginObject* create() override { return new CBoxAlgorithmReferenceChannel; }
// virtual IBoxListener* createBoxListener() const { return new CBoxAlgorithmReferenceChannelListener; }
// virtual void releaseBoxListener(IBoxListener* listener) const { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Channel", OV_TypeId_String, "Ref_Nose");
prototype.addSetting("Channel Matching Method", OVP_TypeId_MatchMethod, "Smart");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,188 @@
#include "ovpCBoxAlgorithmSignalDecimation.h"
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
bool CBoxAlgorithmSignalDecimation::initialize()
{
m_decoder = nullptr;
m_encoder = nullptr;
m_decimationFactor = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
OV_ERROR_UNLESS_KRF(m_decimationFactor > 1, "Invalid decimation factor [" << m_decimationFactor << "] (expected value > 1)",
Kernel::ErrorType::BadSetting);
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalDecoder));
m_decoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_SignalDecoder_InputParameterId_MemoryBufferToDecode));
op_pMatrix.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Matrix));
op_sampling.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_SignalDecoder_OutputParameterId_Sampling));
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalEncoder));
m_encoder->initialize();
ip_sampling.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Sampling));
ip_pMatrix.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_SignalEncoder_InputParameterId_Matrix));
op_buffer.initialize(m_encoder->getOutputParameter(OVP_GD_Algorithm_SignalEncoder_OutputParameterId_EncodedMemoryBuffer));
m_nChannel = 0;
m_iSampleIdx = 0;
m_iNSamplePerBlock = 0;
m_oSampling = 0;
m_oSampleIdx = 0;
m_oNSamplePerBlock = 0;
m_nTotalSample = 0;
m_startTimeBase = 0;
m_lastStartTime = 0;
m_lastEndTime = 0;
return true;
}
bool CBoxAlgorithmSignalDecimation::uninitialize()
{
op_buffer.uninitialize();
ip_pMatrix.uninitialize();
ip_sampling.uninitialize();
if (m_encoder)
{
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_encoder = nullptr;
}
op_sampling.uninitialize();
op_pMatrix.uninitialize();
ip_buffer.uninitialize();
if (m_decoder)
{
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_decoder = nullptr;
}
return true;
}
bool CBoxAlgorithmSignalDecimation::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmSignalDecimation::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
op_buffer = boxContext.getOutputChunk(0);
const uint64_t tStart = boxContext.getInputChunkStartTime(0, i);
const uint64_t tEnd = boxContext.getInputChunkEndTime(0, i);
if (tStart != m_lastEndTime)
{
m_startTimeBase = tStart;
m_iSampleIdx = 0;
m_oSampleIdx = 0;
m_nTotalSample = 0;
}
m_lastStartTime = tStart;
m_lastEndTime = tEnd;
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedHeader))
{
m_iSampleIdx = 0;
m_iNSamplePerBlock = op_pMatrix->getDimensionSize(1);
m_iSampling = op_sampling;
OV_ERROR_UNLESS_KRF(m_iSampling%m_decimationFactor == 0,
"Failed to decimate: input sampling frequency [" << m_iSampling << "] not multiple of decimation factor [" <<
m_decimationFactor << "]", Kernel::ErrorType::BadSetting);
m_oSampleIdx = 0;
m_oNSamplePerBlock = size_t(m_iNSamplePerBlock / m_decimationFactor);
m_oNSamplePerBlock = (m_oNSamplePerBlock ? m_oNSamplePerBlock : 1);
m_oSampling = op_sampling / m_decimationFactor;
OV_ERROR_UNLESS_KRF(m_oSampling != 0, "Failed to decimate: output sampling frequency is 0", Kernel::ErrorType::BadOutput);
m_nChannel = op_pMatrix->getDimensionSize(0);
m_nTotalSample = 0;
ip_pMatrix->copyDescription(*op_pMatrix);
ip_pMatrix->setDimensionSize(1, m_oNSamplePerBlock);
ip_sampling = m_oSampling;
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeHeader);
ip_pMatrix->resetBuffer();
boxContext.markOutputAsReadyToSend(0, tStart, tStart); // $$$ supposes we have one node per chunk
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedBuffer))
{
double* iBuffer = op_pMatrix->getBuffer();
double* oBuffer = ip_pMatrix->getBuffer() + m_oSampleIdx;
for (size_t j = 0; j < m_iNSamplePerBlock; ++j)
{
double* iBufferTmp = iBuffer;
double* oBufferTmp = oBuffer;
for (size_t k = 0; k < m_nChannel; ++k)
{
*oBufferTmp += *iBufferTmp;
oBufferTmp += m_oNSamplePerBlock;
iBufferTmp += m_iNSamplePerBlock;
}
m_iSampleIdx++;
if (m_iSampleIdx == m_decimationFactor)
{
m_iSampleIdx = 0;
oBufferTmp = oBuffer;
for (size_t k = 0; k < m_nChannel; ++k)
{
*oBufferTmp /= m_decimationFactor;
oBufferTmp += m_oNSamplePerBlock;
}
oBuffer++;
m_oSampleIdx++;
if (m_oSampleIdx == m_oNSamplePerBlock)
{
oBuffer = ip_pMatrix->getBuffer();
m_oSampleIdx = 0;
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeBuffer);
const uint64_t tStartSample = m_startTimeBase + CTime(m_oSampling, m_nTotalSample).time();
const uint64_t tEndSample = m_startTimeBase + CTime(m_oSampling, m_nTotalSample + m_oNSamplePerBlock).time();
boxContext.markOutputAsReadyToSend(0, tStartSample, tEndSample);
m_nTotalSample += m_oNSamplePerBlock;
ip_pMatrix->resetBuffer();
}
}
iBuffer++;
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_SignalEncoder_InputTriggerId_EncodeEnd);
boxContext.markOutputAsReadyToSend(0, tStart, tStart); // $$$ supposes we have one node per chunk
}
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,83 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace SignalProcessing {
class CBoxAlgorithmSignalDecimation 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_SignalDecimation)
protected:
size_t m_decimationFactor = 0;
size_t m_nChannel = 0;
size_t m_iSampleIdx = 0;
size_t m_iNSamplePerBlock = 0;
size_t m_iSampling = 0;
size_t m_oSampleIdx = 0;
size_t m_oNSamplePerBlock = 0;
size_t m_oSampling = 0;
size_t m_nTotalSample = 0;
uint64_t m_startTimeBase = 0;
uint64_t m_lastStartTime = 0;
uint64_t m_lastEndTime = 0;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<CMatrix*> op_pMatrix;
Kernel::TParameterHandler<uint64_t> op_sampling;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::TParameterHandler<uint64_t> ip_sampling;
Kernel::TParameterHandler<CMatrix*> ip_pMatrix;
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer;
};
class CBoxAlgorithmSignalDecimationDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Signal Decimation"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Reduces the sampling frequency to a divider of the original sampling frequency"); }
CString getDetailedDescription() const override
{
return CString("No pre filtering applied - Number of samples per block have to be a multiple of the decimation factor");
}
CString getCategory() const override { return CString("Signal processing/Temporal Filtering"); }
CString getVersion() const override { return CString("1.0"); }
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
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_SignalDecimation; }
IPluginObject* create() override { return new CBoxAlgorithmSignalDecimation; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input signal", OV_TypeId_Signal);
prototype.addOutput("Output signal", OV_TypeId_Signal);
prototype.addSetting("Decimation factor", OV_TypeId_Integer, "8");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalDecimationDesc)
};
} // namespace SignalProcessing
} // namespace Plugins
} // namespace OpenViBE

Some files were not shown because too many files have changed in this diff Show More