void SignalDisplay::AdaptTo( const GenericSignal& inSignal ) { // Any changes in the signal size that we must react to? bool reconfigure = false; if( inSignal.Elements() > mNumSamples ) { OSMutex::Lock lock( mDataLock ); SetNumSamples( inSignal.Elements() ); reconfigure = true; } if( inSignal.Channels() != mData.Channels() ) { OSMutex::Lock lock( mDataLock ); int newNumDisplayGroups = ( inSignal.Channels() - mMarkerChannels - 1 ) / mChannelGroupSize + 1; if( mNumDisplayGroups == 0 ) mNumDisplayGroups = min<int>( newNumDisplayGroups, cInitialMaxDisplayGroups ); else if( newNumDisplayGroups < mNumDisplayGroups ) mNumDisplayGroups = newNumDisplayGroups; reconfigure = true; } if( reconfigure ) { OSMutex::Lock lock( mDataLock ); mData = GenericSignal( inSignal.Channels(), mNumSamples ); SetDisplayGroups( DisplayGroups() ); SyncLabelWidth(); mSampleCursor = 0; Invalidate(); } }
// ************************************************************************** // Function: Process // Purpose: This function is called within the data acquisition loop // it fills its output signal with values // and does not return until all data has been acquired. // Parameters: References to input signal (ignored) and output signal. // Returns: N/A // ************************************************************************** void NeuroSkyADC::Process( const GenericSignal&, GenericSignal& Output ) { // Clear out old data mDataBlock.clear(); // Fill a Sample Block for( int i = 0; i < Output.Elements(); i++ ) { // Read Packets until new raw data comes in while( true ) { TG_ReadPackets( mConnectionID, 1 ); if( TG_GetValueStatus( mConnectionID, TG_DATA_RAW ) != 0 ) break; Sleep( 1 ); } // Push the newest data into the data block mDataBlock.push_back( TG_GetValue( mConnectionID, TG_DATA_RAW ) ); } // Fill the output with that data for( int sample = 0; sample < Output.Elements(); ++sample ) Output( 0, sample ) = mDataBlock[ sample ]; }
void Normalizer::Process( const GenericSignal& Input, GenericSignal& Output ) { if( mDoAdapt ) { for( size_t channel = 0; channel < mBufferConditions.size(); ++channel ) for( size_t buffer = 0; buffer < mBufferConditions[ channel ].size(); ++buffer ) { double label = mBufferConditions[ channel ][ buffer ].Evaluate( &Input ); if( label != 0 ) for( int sample = 0; sample < Input.Elements(); ++sample ) mDataBuffers[ channel ][ buffer ].Put( Input( channel, sample ) ); } if( mpUpdateTrigger != NULL ) { bool currentTrigger = mpUpdateTrigger->Evaluate( &Input ); if( currentTrigger && !mPreviousTrigger ) Update(); mPreviousTrigger = currentTrigger; } else Update(); } for( int channel = 0; channel < Input.Channels(); ++channel ) for( int sample = 0; sample < Input.Elements(); ++sample ) Output( channel, sample ) = ( Input( channel, sample ) - mOffsets[ channel ] ) * mGains[ channel ]; }
void FFTFilter::Process( const GenericSignal& Input, GenericSignal& Output ) { if( mFFTOutputSignal == eInput ) Output = Input; for( size_t i = 0; i < mFFTInputChannels.size(); ++i ) { // Copy input signal values to the value buffer. vector<float>& buffer = mValueBuffers[ i ]; int inputSize = Input.Elements(), bufferSize = static_cast<int>( buffer.size() ); // Move old values towards the beginning of the buffer, if any. for( int j = 0; j < bufferSize - inputSize; ++j ) buffer[ j ] = buffer[ j + inputSize ]; // Copy new values to the end of the buffer; // buffer size may be greater or less than input size. for( int j = ::max( 0, bufferSize - inputSize ); j < bufferSize; ++j ) buffer[ j ] = static_cast<float>( Input( mFFTInputChannels[ i ], j + inputSize - bufferSize ) ); // Prepare the buffer. if( mFFTWindow == eNone ) for( int j = 0; j < bufferSize; ++j ) mFFT.Input( j ) = buffer[ j ]; else for( int j = 0; j < bufferSize; ++j ) mFFT.Input( j ) = buffer[ j ] * mWindow[ j ]; // Compute the power spectrum and visualize it if requested. mFFT.Compute(); if( mVisualizeFFT || mFFTOutputSignal == ePower ) { int maxIdx = mPowerSpectrum.Channels() - 1; double normFactor = 1.0 / bufferSize; mPowerSpectrum( maxIdx, 0 ) = mFFT.Output( 0 ) * mFFT.Output( 0 ) * normFactor; for( int k = 1; k < ( bufferSize + 1 ) / 2; ++k ) mPowerSpectrum( maxIdx - k, 0 ) = ( mFFT.Output( k ) * mFFT.Output( k ) + mFFT.Output( bufferSize - k ) * mFFT.Output( bufferSize - k ) ) * normFactor; if( bufferSize % 2 == 0 ) mPowerSpectrum( maxIdx - bufferSize / 2, 0 ) = mFFT.Output( bufferSize / 2 ) * mFFT.Output( bufferSize / 2 ) * normFactor; } if( mVisualizeFFT ) mVisualizations[ i ].Send( mPowerSpectrum ); if( mFFTOutputSignal == ePower ) { for( int j = 0; j < Output.Elements(); ++j ) Output( i, j ) = mPowerSpectrum( mPowerSpectrum.Channels() - 1 - j, 0 ); } else if( mFFTOutputSignal == eHalfcomplex ) { double normFactor = 1.0 / ::sqrt( 1.0 * bufferSize ); for( int j = 0; j < Output.Elements(); ++j ) Output( i, j ) = mFFT.Output( j ) * normFactor; } } }
//////////////////////////////////////////////////////////////////////////////// // MatlabEngine::DoubleMatrix definitions // //////////////////////////////////////////////////////////////////////////////// MatlabEngine::DoubleMatrix::DoubleMatrix( const GenericSignal& inSignal ) : vector<vector<double> >( inSignal.Channels(), vector<double>( inSignal.Elements() ) ) { for( size_t channel = 0; channel < size(); ++channel ) for( size_t sample = 0; sample < ( *this )[ channel ].size(); ++sample ) ( *this )[ channel ][ sample ] = inSignal( channel, sample ); }
void CustomFIRFilter::Process( const GenericSignal& Input, GenericSignal& Output ) { if( mBuffer.empty() || mFilter.size() == 0 ) Output = Input; else for( size_t channel = 0; channel < mBuffer.size(); ++channel ) { int bufferLength = static_cast<int>( mBuffer[channel].size() ), filterLength = static_cast<int>( mFilter.size() ), inputLength = Input.Elements(); // Move buffer content towards the buffer's begin. for( int sample = 0; sample < bufferLength - inputLength; ++sample ) mBuffer[channel][sample] = mBuffer[channel][sample + inputLength]; // Copy current input into the buffer's end. for( int sample = 0; sample < inputLength; ++sample ) mBuffer[channel][bufferLength - inputLength + sample] = Input( channel, sample ); // Compute buffer's convolution with coefficient vector. DataVector result( 0.0, inputLength ); for( int sample = 0; sample < inputLength; ++sample ) result[sample] = inner_product( &mFilter[0], &mFilter[filterLength], &mBuffer[channel][sample], 0.0 ); // Compute output. for( int sample = 0; sample < inputLength; ++sample ) Output( channel, sample ) = result[sample]; } }
// The Process() function is called from the main thread in regular intervals. void BufferedADC::Process( const GenericSignal&, GenericSignal& Output ) { this->OnProcess(); AcquisitionBuffer& buffer = mpBuffers[mReadCursor]; ++mReadCursor %= mSourceBufferSize; Lock lock( buffer ); if( !IsAcquiring() ) { bcierr_ << ( mError.empty() ? "Acquisition Error" : mError ); if( State( "Running" ) ) State( "Running" ) = 0; return; } if( buffer.Signal.Channels() == Output.Channels() ) Output = buffer.Signal; else { const LabelIndex& labels = mAcquisitionProperties.ChannelLabels(); for( int el = 0; el < Output.Elements(); ++el ) { for( int ch = 0; ch < Output.Channels(); ++ch ) Output( ch, el ) = buffer.Signal( ch, el ); for( int ch = Output.Channels(); ch < buffer.Signal.Channels(); ++ch ) State( labels[ch].c_str() + 1 )( el ) = static_cast<State::ValueType>( buffer.Signal( ch, el ) ); } } State( "SourceTime" ) = buffer.TimeStamp; }
void BCI2000OutputFormat::Write( ostream& os, const GenericSignal& inSignal, const StateVector& inStatevector ) { switch( mInputProperties.Type() ) { case SignalType::int16: case SignalType::float32: case SignalType::int32: // Note that the order of Elements and Channels differs from the one in the // socket protocol. for( int j = 0; j < inSignal.Elements(); ++j ) { for( int i = 0; i < inSignal.Channels(); ++i ) inSignal.WriteValueBinary( os, i, j ); os.write( reinterpret_cast<const char*>( inStatevector( min( j, inStatevector.Samples() - 1 ) ).Data() ), inStatevector.Length() ); } break; default: bcierr << "Unsupported signal data type" << endl; } }
void LinearClassifier::Process( const GenericSignal& Input, GenericSignal& Output ) { for( int ch = 0; ch < Output.Channels(); ++ch ) for( int el = 0; el < Output.Elements(); ++el ) Output( ch, el ) = 0.0; for( size_t i = 0; i < mWeights.size(); ++i ) Output( mOutputChannels[ i ], 0 ) += Input( mInputChannels[ i ], mInputElements[ i ] ) * mWeights[ i ]; }
// ************************************************************************** // Function: ADReadDataBlock // Purpose: This function is called within fMain->MainDataAcqLoop() // it fills the already initialized array RawEEG with values // and DOES NOT RETURN, UNTIL ALL DATA IS ACQUIRED // Parameters: N/A // Returns: 0 ... on error // 1 ... no error // ************************************************************************** void ModularEEGADC::Process( const GenericSignal&, GenericSignal& signal ) { int value; const long maxvalue = ( 1L << 15 ) - 1, minvalue = - ( 1L << 15 ); // generate the sine wave and write it into the signal for (int sample=0; sample<signal.Elements(); sample++) { read_channels(devicehandle,protocol); for (int channel=0; channel<signal.Channels(); channel++) { value = PACKET.buffer[channel]; if( value > maxvalue ) value = maxvalue; if( value < minvalue ) value = minvalue; signal(channel, sample) = (short)value; } } mCount=mCount+signal.Elements(); }
void EDFFileWriterBase::PutBlock( const GenericSignal& inSignal, const StateVector& inStatevector ) { for( int i = 0; i < inSignal.Channels(); ++i ) for( int j = 0; j < inSignal.Elements(); ++j ) GDF::Num<T>( inSignal( i, j ) ).WriteToStream( OutputStream() ); for( size_t i = 0; i < mStateNames.size(); ++i ) GDF::PutField< GDF::Num<GDF::int16> >( OutputStream(), inStatevector.StateValue( mStateNames[ i ] ) ); }
// ************************************************************************** // Function: Process // Purpose: This function is called within the data acquisition loop // it fills its output signal with values // and does not return until all data has been acquired. // Parameters: References to input signal (ignored) and output signal. // Returns: N/A // ************************************************************************** void NicoletOneADC::Process( const GenericSignal&, GenericSignal& Output ) { // Grab the data for each channel and sample, push it into the output bool newData = false; while( !newData ) { // Grab new information from thread bool newTsInfo = false; newData = !( mNT->ExtractData( Output.Channels(), Output.Elements(), mDataBlock, newTsInfo ) ); // If we have a new data block, we can fill out sample block if( newData ) for( int sample = 0; sample < Output.Elements(); ++sample ) for( int channel = 0; channel < Output.Channels(); ++channel ) Output( channel, sample ) = mDataBlock[channel][sample]; // If we have new TsInfo we need to see if its still valid. if( newTsInfo ) { // We need to check the signal properties to verify that all is well. // Check the number of channels int numChannels = 0; while( mNT->GetNumChannels( &numChannels ) ) Sleep( 10 ); if( mChannels != numChannels ) bcierr << "The number of channels reported has changed. Experiment is ending. Device is reporting " << numChannels << " channels" << endl; // Check Sampling Rate double rate = 0.0f; while( mNT->GetSampleRate( &rate ) ) Sleep( 10 ); if( mSamplingRate != (int)rate ) bcierr << "Sampling Rate has changed. Experiment is ending. Device is reporting " << (int)rate << " Hz." << endl; } // Needed? Sleep( 10 ); } }
void SpatialFilter::Process( const GenericSignal& Input, GenericSignal& Output ) { // Actually perform Spatial Filtering on the input and write it into the output signal. for( int sample = 0; sample < Input.Elements(); ++sample ) for( int outChannel = 0; outChannel < Output.Channels(); ++outChannel ) { double value = 0; for( int inChannel = 0; inChannel < Input.Channels(); ++inChannel ) value += mFilterMatrix[ outChannel ][ inChannel ] * Input( inChannel, sample ); Output( outChannel, sample ) = value; } }
void RDAClientADC::DoAcquire( GenericSignal& Output ) { if( !mConnection.ReceiveData( Output ) ) Error( "Lost connection to VisionRecorder software" ); else if( mAddMarkerChannel ) { int from = Output.Channels() - 2, to = from + 1; for( int el = 0; el < Output.Elements(); ++el ) Output( to, el ) = Output( from, el ); } }
void BrainVisionGDRConverter::OutputSignal( const GenericSignal& inSignal, long /*inSamplePos*/ ) { Idle(); for( int sample = 0; sample < inSignal.Elements(); ++sample ) for( int channel = 0; channel < inSignal.Channels(); ++channel ) { float value = inSignal( channel, sample ); mDataFile.write( reinterpret_cast<const char*>( &value ), sizeof( value ) ); } if( !mDataFile ) bcierr << "Error writing data file" << endl; }
void AlignmentFilter::Process( const GenericSignal& Input, GenericSignal& Output ) { if( mAlign ) // Perform the alignment on the input and write it into the output signal. for( int channel = 0; channel < Input.Channels(); ++channel ) for( int sample = 0; sample < Input.Elements(); ++sample ) { Output( channel, sample ) = Input( channel, sample ) * mWeightCur[ channel ] + mWeightPrev[ channel ] * mPrevSample[ channel ]; mPrevSample[ channel ] = Input( channel, sample ); } else // No alignment. Output = Input; }
void RDAClientADC::Process( const GenericSignal&, GenericSignal& Output ) { for( int sample = 0; sample < Output.Elements(); ++sample ) for( int channel = 0; channel < Output.Channels(); ++channel ) { if( !mInputQueue ) { bcierr << "Lost connection to VisionRecorder software" << endl; return; } Output( channel, sample ) = mInputQueue.front(); mInputQueue.pop(); } }
void BAlertADC::Process( const GenericSignal& Input, GenericSignal& Output ) { float fBuffer[768]; if(BAlertWaitForData(fBuffer,mSampleBlockSize)) { int i=0; for( int el = 0; el < Output.Elements(); el++ ) for( int ch = 0; ch < Output.Channels(); ch++ ) Output( ch, el ) = fBuffer[ i++ ]; } else { bcierr<< "ABM: Data acquisition failed..." << endl; } }
void ASCIIConverter::OutputSignal( const GenericSignal& inSignal, long long /*inSamplePos*/ ) { Idle(); for( int sample = 0; sample < inSignal.Elements(); ++sample ) { for( int channel = 0; channel < inSignal.Channels(); ++channel ) { double value = inSignal( channel, sample ); mDataFile << ' ' << value; } for( size_t state = 0; state < mStateValues.size(); ++state ) mDataFile << ' ' << mStateValues[ state ]; mDataFile << '\n'; } if( !mDataFile ) bcierr << "Error writing data file" << endl; }
void ConnectorInput::Process( const GenericSignal& Input, GenericSignal& Output ) { try { Output = Input; string buffer; while( mConnection && mConnection.rdbuf()->in_avail() > 0 ) buffer += mConnection.get(); istringstream iss( buffer ); string name; while( iss >> name ) { double value; string remainder; if( !std::getline( iss >> value, remainder ) || !remainder.empty() ) throw bciexception( "Malformed input, expected state name, followed by a single number as a value," "space-separated, and terminated with a newline character" ); bool match = false; for( vector<string>::const_iterator i = mInputFilters.begin(); i != mInputFilters.end() && !match; ++i ) match = match || WildcardMatch( *i, name, false ); if( match ) { if( name.find( "Signal(" ) == 0 ) { istringstream iss( name.substr( name.find( '(' ) ) ); char ignore; int channel = 0, element = 0; if( !( iss >> ignore >> channel >> ignore >> element >> ignore ) ) throw bciexception( "Incorrect Signal index syntax: " << name ); if( channel >= Input.Channels() || element >= Input.Elements() ) throw bciexception( "Received signal index out-of-bounds: " << name ); Output( channel, element ) = value; } else { if( !States->Exists( name ) ) throw bciexception( "Ignoring value for non-existent " << name << " state" ); State( name.c_str() ) = static_cast<int>( value ); } } }
void ComplexDemodulator::Process( const GenericSignal& Input, GenericSignal& Output ) { for( int channel = 0; channel < Input.Channels(); ++channel ) { for( int sample = 0; sample < Input.Elements(); ++sample ) { mSignalBuffer[ channel ].push_back( Input( channel, sample ) ); mSignalBuffer[ channel ].erase( mSignalBuffer[ channel ].begin() ); } for( size_t band = 0; band < mCoefficients.size(); ++band ) { Output( channel, band ) = norm( inner_product( mSignalBuffer[ channel ].begin(), mSignalBuffer[ channel ].end(), mCoefficients[ band ].begin(), complex<double>( 0.0, 0.0 ) ) ); } } }
void AverageDisplay::Process( const GenericSignal& Input, GenericSignal& Output ) { size_t targetCode = State( "TargetCode" ); if( targetCode == 0 && targetCode != mLastTargetCode ) { size_t targetIndex = find( mTargetCodes.begin(), mTargetCodes.end(), mLastTargetCode ) - mTargetCodes.begin(); if( targetIndex == mTargetCodes.size() ) mTargetCodes.push_back( mLastTargetCode ); // End of the current target code run. for( size_t i = 0; i < mChannelIndices.size(); ++i ) { for( int power = 0; power <= maxPower; ++power ) { // - If the target code occurred for the first time, adapt the power sums. if( mPowerSums[ power ][ i ].size() <= targetIndex ) mPowerSums[ power ][ i ].resize( targetIndex + 1 ); // - Update power sum sizes. if( mPowerSums[ power ][ i ][ targetIndex ].size() < mSignalOfCurrentRun[ i ].size() ) mPowerSums[ power ][ i ][ targetIndex ].resize( mSignalOfCurrentRun[ i ].size(), 0 ); } #ifdef SET_BASELINE if( mBaselineSamples[ i ] > 0 ) mBaselines[ i ] /= mBaselineSamples[ i ]; #endif // SET_BASELINE // - Compute the power sum entries. for( size_t j = 0; j < mSignalOfCurrentRun[ i ].size(); ++j ) { #ifdef SET_BASELINE mSignalOfCurrentRun[ i ][ j ] -= mBaselines[ i ]; #endif // SET_BASELINE float summand = 1.0; for( size_t power = 0; power < maxPower; ++power ) { mPowerSums[ power ][ i ][ targetIndex ][ j ] += summand; summand *= mSignalOfCurrentRun[ i ][ j ]; } mPowerSums[ maxPower ][ i ][ targetIndex ][ j ] += summand; } } // - Clear target run buffer. for( size_t i = 0; i < mSignalOfCurrentRun.size(); ++i ) mSignalOfCurrentRun[ i ].clear(); #ifdef SET_BASELINE for( size_t i = 0; i < mBaselines.size(); ++i ) { mBaselineSamples[ i ] = 0; mBaselines[ i ] = 0; } #endif // SET_BASELINE // - Compute and display the averages. for( size_t channel = 0; channel < mVisualizations.size(); ++channel ) { size_t numTargets = mPowerSums[ maxPower ][ channel ].size(), numSamples = numeric_limits<size_t>::max(); for( size_t target = 0; target < numTargets; ++target ) if( mPowerSums[ maxPower ][ channel ][ target ].size() < numSamples ) numSamples = mPowerSums[ maxPower ][ channel ][ target ].size(); // To minimize user confusion, always send target averages in ascending order // of target codes. This ensures that colors in the display don't depend // on the order of target codes in the task sequence once all target codes // occurred. // We cannot, however, avoid color changes when yet unknown target codes // occur. // // The map is automatically sorted by its "key", so all we need to do // is to put the target codes and their indices into it, using the // target code as "key" and the index as "value", and later iterate over // the map to get the indices sorted by their associated target code. map<int, int> targetCodesToIndex; for( size_t target = 0; target < numTargets; ++target ) targetCodesToIndex[ mTargetCodes[ target ] ] = target; GenericSignal average( numTargets, numSamples ); LabelList labels; for( map<int, int>::const_iterator target = targetCodesToIndex.begin(); target != targetCodesToIndex.end(); ++target ) { for( size_t sample = 0; sample < numSamples; ++sample ) // If everything behaves as we believe it will, // a division by zero is impossible. // If it occurs nevertheless, the logic is messed up. average( target->second, sample ) = mPowerSums[ 1 ][ channel ][ target->second ][ sample ] / mPowerSums[ 0 ][ channel ][ target->second ][ sample ]; ostringstream oss; oss << "Target " << target->first; string targetName = OptionalParameter( "TargetNames", target->first ); if( targetName != "" ) oss << " (" << targetName << ")"; labels.push_back( Label( target->second, oss.str() ) ); } mVisualizations[ channel ].Send( CfgID::ChannelLabels, labels ); ostringstream oss; oss << ( Parameter( "SampleBlockSize" ) / Input.Elements() / Parameter( "SamplingRate" ) ) << "s"; mVisualizations[ channel ].Send( CfgID::SampleUnit, oss.str() ); mVisualizations[ channel ].Send( average ); } } if( targetCode != 0 ) { // Store the current signal to the end of the run buffer. for( size_t i = 0; i < mChannelIndices.size(); ++i ) { size_t signalCursorPos = mSignalOfCurrentRun[ i ].size(); mSignalOfCurrentRun[ i ].resize( signalCursorPos + Input.Elements() ); for( int j = 0; j < Input.Elements(); ++j ) mSignalOfCurrentRun[ i ][ signalCursorPos + j ] = Input( mChannelIndices[ i ], j ); } #ifdef SET_BASELINE if( OptionalState( "BaselineInterval" ) || OptionalState( "Baseline" ) ) { for( size_t i = 0; i < mChannelIndices.size(); ++i ) { mBaselineSamples[ i ] += Input.Elements(); for( int j = 0; j < Input.Elements(); ++j ) mBaselines[ i ] += Input( mChannelIndices[ i ], j ); } } #endif // SET_BASELINE } mLastTargetCode = targetCode; Output = Input; }
void ARFilter::Process( const GenericSignal& Input, GenericSignal& Output ) { for( int channel = 0; channel < Input.Channels(); ++channel ) { DataVector& buf = mBuffer[ channel ]; // Shift buffer contents left: size_t i = 0, j = Input.Elements(); while( j < buf.size() ) buf[ i++ ] = buf[ j++ ]; // Fill the rightmost part of the buffer with new input: j = Input.Elements() - ( buf.size() - i ); while( i < buf.size() ) buf[ i++ ] = Input( channel, j++ ); const DataVector* inputData = &buf; switch( mDetrend ) { case none: break; case mean: inputData = &Detrend::MeanDetrend( buf ); break; case linear: inputData = &Detrend::LinearDetrend( buf ); break; default: bcierr << "Unknown detrend option" << endl; } const Ratpoly<Complex>& transferFunction = mMEMPredictor.TransferFunction( *inputData ); switch( mOutputType ) { case SpectralAmplitude: { const std::valarray<float>& spectrum = mTransferSpectrum.Evaluate( transferFunction ); for( size_t bin = 0; bin < spectrum.size(); ++bin ) Output( channel, bin ) = sqrt( spectrum[ bin ] ); } break; case SpectralPower: { const std::valarray<float>& spectrum = mTransferSpectrum.Evaluate( transferFunction ); for( size_t bin = 0; bin < spectrum.size(); ++bin ) Output( channel, bin ) = spectrum[ bin ]; } break; case ARCoefficients: { const Polynomial<Complex>::Vector& coeff = transferFunction.Denominator().Coefficients(); for( size_t i = 1; i < coeff.size(); ++i ) Output( channel, i - 1 ) = coeff[ i ].real(); } break; default: bcierr << "Unknown output type" << endl; } } }
void P3TemporalFilter::Process( const GenericSignal& Input, GenericSignal& Output ) { Output = GenericSignal( mOutputProperties ); if( mEpochsToAverage > 0 ) { int curStimulusCode = State( "StimulusCode" ); mStimulusTypes[ curStimulusCode ] = State( "StimulusType" ); // If the StimulusBegin state is available, use it to detect stimulus onset. // Otherwise, check whether StimulusCode has just switched to nonzero. bool stimulusOnset = curStimulusCode > 0 && ( OptionalState( "StimulusBegin", 0 ) || mPreviousStimulusCode == 0 ); if( stimulusOnset ) { // First block of stimulus presentation -- create a new epoch buffer. bcidbg( 3 ) << "New epoch for stimulus code #" << curStimulusCode << endl; mEpochs[ curStimulusCode ].insert( new EpochBuffer( mOutputProperties ) ); } mPreviousStimulusCode = curStimulusCode; State( "StimulusCodeRes" ) = 0; State( "StimulusTypeRes" ) = 0; for( EpochMap::iterator i = mEpochs.begin(); i != mEpochs.end(); ++i ) { int stimulusCode = i->first; EpochSet obsoleteEpochs; for( EpochSet::iterator j = i->second.begin(); j != i->second.end(); ++j ) { ( *j )->Process( Input ); if( ( *j )->EpochDone() ) { // Move buffer data into the epoch sum associated with the stimulus code, // and note the epoch buffer for later disposal. bcidbg( 3 ) << "Epoch done for stimulus code #" << stimulusCode << endl; if( mEpochSums[ stimulusCode ] == NULL ) { bcidbg( 2 ) << "Allocating result buffer for stimulus code #" << stimulusCode << endl; mEpochSums[ stimulusCode ] = new DataSum( mOutputProperties ); } mEpochSums[ stimulusCode ]->Add( ( *j )->Data() ); obsoleteEpochs.insert( *j ); if( mEpochSums[ stimulusCode ]->Count() == mEpochsToAverage ) { // When the number of required epochs is reached, copy the buffer average // into the output signal, and set states appropriately. bcidbg( 2 ) << "Reporting average for stimulus code #" << i->first << endl; for( int channel = 0; channel < Output.Channels(); ++channel ) for( int sample = 0; sample < Output.Elements(); ++sample ) Output( channel, sample ) = ( *mEpochSums[ stimulusCode ] )( channel, sample ) / mEpochsToAverage; State( "StimulusCodeRes" ) = stimulusCode; State( "StimulusTypeRes" ) = mStimulusTypes[ stimulusCode ]; *mEpochSums[ stimulusCode ] = DataSum( mOutputProperties ); if( mVisualize && i->first - 1 < mVisSignal.Channels() ) { for( int sample = 0; sample < Output.Elements(); ++sample ) mVisSignal( stimulusCode - 1, sample ) = Output( mTargetERPChannel, sample ); mVis.Send( mVisSignal ); } } } } for( EpochSet::iterator j = obsoleteEpochs.begin(); j != obsoleteEpochs.end(); ++j ) i->second.erase( *j ); // Epochs will be deallocated from the obsoleteEpochs class destructor. } } }
void FFTFilter::Process( const GenericSignal& inputSignal, GenericSignal& outputSignal ) { for( size_t i = 0; i < mFFTInputChannels.size(); ++i ) { // Copy the input signal values to the value buffer. vector<float>& buffer = mValueBuffers[ i ]; int inputSize = inputSignal.Elements(), bufferSize = buffer.size(); // Move old values towards the beginning of the buffer, if any. for( int j = 0; j < bufferSize - inputSize; ++j ) buffer[ j ] = buffer[ j + inputSize ]; // Copy new values to the end of the buffer; // the buffer size may be greater of smaller than the input size. for( int j = ::max( 0, bufferSize - inputSize ); j < bufferSize; ++j ) buffer[ j ] = inputSignal( mFFTInputChannels[ i ], j + inputSize - bufferSize ); // Prepare the buffer. if( mFFTWindow == eNone ) for( int j = 0; j < bufferSize; ++j ) mFFT.Input( j ) = buffer[ j ]; else for( int j = 0; j < bufferSize; ++j ) mFFT.Input( j ) = buffer[ j ] * mWindow[ j ]; // Compute the power spectrum and visualize it if requested. mFFT.Transform(); GenericSignal& spectrum = mSpectra[ i ]; if( mVisualizeFFT || mFFTOutputSignal == ePower ) { float normFactor = 1.0 / bufferSize; spectrum( 0, 0 ) = mFFT.Output( 0 ) * mFFT.Output( 0 ) * normFactor; for( int k = 1; k < ( bufferSize + 1 ) / 2; ++k ) spectrum( k, 0 ) = ( mFFT.Output( k ) * mFFT.Output( k ) + mFFT.Output( bufferSize - k ) * mFFT.Output( bufferSize - k ) ) * normFactor; if( bufferSize % 2 == 0 ) spectrum( bufferSize / 2, 0 ) = mFFT.Output( bufferSize / 2 ) * mFFT.Output( bufferSize / 2 ) * normFactor; } else if( mFFTOutputSignal == eHalfcomplex ) { float normFactor = 1.0 / ::sqrt( 1.0 * bufferSize ); for( int k = 0; k < bufferSize; ++k ) spectrum( k, 0 ) = mFFT.Output( k ) * normFactor; } if( mVisualizeFFT ) mVisualizations[ i ].Send( spectrum ); } switch( mFFTOutputSignal ) { case eInput: outputSignal = inputSignal; break; case ePower: case eHalfcomplex: for( size_t channel = 0; channel < mSpectra.size(); ++channel ) for( int element = 0; element < mSpectra[ channel ].Channels(); ++element ) outputSignal( channel, element ) = mSpectra[ channel ]( element, 0 ); break; default: assert( false ); } }
const GenericSignal& BCI2000Viewer::ConstructDisplaySignal( long inPos, long inLength ) { static GenericSignal result; if( mFile.IsOpen() ) { QApplication::setOverrideCursor( Qt::WaitCursor ); int i = 1; vector<StateRef> states; for( ; i < ui->channelList->count() && ( ui->channelList->item( i )->flags() & Qt::ItemIsUserCheckable ); ++i ) if( ui->channelList->item( i )->checkState() == Qt::Checked ) states.push_back( mFile.State( ui->channelList->item( i )->text().toLocal8Bit().constData() ) ); vector<int> channels; int base = ++i; for( ; i < ui->channelList->count() && ( ui->channelList->item( i )->flags() & Qt::ItemIsUserCheckable ); ++i ) if( ui->channelList->item( i )->checkState() == Qt::Checked ) channels.push_back( i - base ); GenericSignal signal( channels.size() + states.size(), inLength ), statevalues( states.size(), inLength ); long sampleInFile = inPos; for( long sample = 0; sample < signal.Elements() && sampleInFile < mFile.NumSamples(); ++sample, ++sampleInFile ) { for( int channelIdx = 0; channelIdx < static_cast<int>( channels.size() ); ++channelIdx ) signal( channelIdx, sample ) = mFile.CalibratedValue( channels[ channelIdx ], sampleInFile ); mFile.ReadStateVector( sampleInFile ); for( size_t i = 0; i < states.size(); ++i ) statevalues( i, sample ) = states[i]; } if( FilterActive() ) { mFilter.Reset(); result = GenericSignal( signal.Properties() ); // run the filter twice to avoid transient artifacts mFilter.Process( signal, result ); mFilter.Process( signal, result ); } else { result = signal; } if( mRemoveMean ) { for( size_t ch = 0; ch < channels.size(); ++ch ) { double mean = 0.0; for( int sample = 0; sample < result.Elements(); ++sample ) mean += result( ch, sample ); mean /= result.Elements(); for( int sample = 0; sample < result.Elements(); ++sample ) result( ch, sample ) -= mean; } } for( int ch = 0; ch < statevalues.Channels(); ++ch ) for( int sample = 0; sample < statevalues.Elements(); ++sample ) result( ch + channels.size(), sample ) = statevalues( ch, sample ); QApplication::restoreOverrideCursor(); } else { result = GenericSignal( 0, 0 ); } return result; }