MNE-CPP 0.1.9
A Framework for Electrophysiology
Loading...
Searching...
No Matches
filter.cpp
Go to the documentation of this file.
1//=============================================================================================================
36//=============================================================================================================
37// INCLUDES
38//=============================================================================================================
39
40#include "filter.h"
41
42#include <utils/mnemath.h>
43#include <fiff/fiff_raw_data.h>
44#include <fiff/fiff_file.h>
45
46//=============================================================================================================
47// QT INCLUDES
48//=============================================================================================================
49
50#include <QDebug>
51
52//=============================================================================================================
53// EIGEN INCLUDES
54//=============================================================================================================
55
56#include <Eigen/Dense>
57#include <Eigen/Core>
58
59//=============================================================================================================
60// USED NAMESPACES
61//=============================================================================================================
62
63using namespace RTPROCESSINGLIB;
64using namespace Eigen;
65using namespace FIFFLIB;
66using namespace UTILSLIB;
67
68//=============================================================================================================
69// DEFINE GLOBAL RTPROCESSINGLIB METHODS
70//=============================================================================================================
71
72bool RTPROCESSINGLIB::filterFile(QIODevice &pIODevice,
73 QSharedPointer<FiffRawData> pFiffRawData,
74 int type,
75 double dCenterfreq,
76 double bandwidth,
77 double dTransition,
78 double dSFreq,
79 int iOrder,
80 int designMethod,
81 const RowVectorXi& vecPicks,
82 bool bUseThreads)
83{
84 // Normalize cut off frequencies to nyquist
85 dCenterfreq = dCenterfreq/(dSFreq/2.0);
86 bandwidth = bandwidth/(dSFreq/2.0);
87 dTransition = dTransition/(dSFreq/2.0);
88
89 // create filter
90 FilterKernel filter = FilterKernel("filter_kernel",
91 type,
92 iOrder,
93 dCenterfreq,
94 bandwidth,
95 dTransition,
96 dSFreq,
97 designMethod);
98
99 return filterFile(pIODevice,
100 pFiffRawData,
101 filter,
102 vecPicks,
103 bUseThreads);
104}
105
106//=============================================================================================================
107
108bool RTPROCESSINGLIB::filterFile(QIODevice &pIODevice,
109 QSharedPointer<FiffRawData> pFiffRawData,
110 const FilterKernel& filterKernel,
111 const RowVectorXi& vecPicks,
112 bool bUseThreads)
113{
114 int iOrder = filterKernel.getFilterOrder();
115
116 RowVectorXd cals;
117 SparseMatrix<double> mult;
118 RowVectorXi sel;
119 FiffStream::SPtr outfid = FiffStream::start_writing_raw(pIODevice, pFiffRawData->info, cals);
120
121 //Setup reading parameters
122 fiff_int_t from = pFiffRawData->first_samp;
123 fiff_int_t to = pFiffRawData->last_samp;
124
125 // slice input data into data junks with proper length so that the slices are always >= the filter order
126 float fFactor = 2.0f;
127 int iSize = fFactor * iOrder;
128 int residual = (to - from) % iSize;
129 while(residual < iOrder) {
130 fFactor = fFactor - 0.1f;
131 iSize = fFactor * iOrder;
132 residual = (to - from) % iSize;
133
134 if((iSize < iOrder)) {
135 qInfo() << "[Filter::filterData] Sliced data block size is too small. Filtering whole block at once.";
136 iSize = to - from;
137 break;
138 }
139 }
140
141 float quantum_sec = iSize/pFiffRawData->info.sfreq;
142 fiff_int_t quantum = ceil(quantum_sec*pFiffRawData->info.sfreq);
143
144 // Read, filter and write the data
145 bool first_buffer = true;
146
147 fiff_int_t first, last;
148 MatrixXd matData, matDataOverlap;
149 MatrixXd times;
150
151 for(first = from; first < to; first+=quantum) {
152 last = first+quantum-1;
153 if (last > to) {
154 last = to;
155 }
156
157 if (!pFiffRawData->read_raw_segment(matData, times, mult, first, last, sel)) {
158 qWarning("[Filter::filterData] Error during read_raw_segment\n");
159 return false;
160 }
161
162 qInfo() << "Filtering and writing block" << first << "to" << last;
163
164 if (first_buffer) {
165 if (first > 0) {
166 outfid->write_int(FIFF_FIRST_SAMPLE,&first);
167 }
168 first_buffer = false;
169 }
170
171 matData = filterDataBlock(matData,
172 vecPicks,
173 filterKernel,
174 bUseThreads);
175
176 if(first == from) {
177 outfid->write_raw_buffer(matData.block(0,iOrder/2,matData.rows(),matData.cols()-iOrder), cals);
178 } else if(first + quantum >= to) {
179 matData.block(0,0,matData.rows(),iOrder) += matDataOverlap;
180 outfid->write_raw_buffer(matData.block(0,0,matData.rows(),matData.cols()-iOrder), cals);
181 } else {
182 matData.block(0,0,matData.rows(),iOrder) += matDataOverlap;
183 outfid->write_raw_buffer(matData.block(0,0,matData.rows(),matData.cols()-iOrder), cals);
184 }
185
186 matDataOverlap = matData.block(0,matData.cols()-iOrder,matData.rows(),iOrder);
187 }
188
189 outfid->finish_writing_raw();
190
191 return true;
192}
193
194//=============================================================================================================
195
196MatrixXd RTPROCESSINGLIB::filterData(const MatrixXd& mataData,
197 int type,
198 double dCenterfreq,
199 double bandwidth,
200 double dTransition,
201 double dSFreq,
202 int iOrder,
203 int designMethod,
204 const RowVectorXi& vecPicks,
205 bool bUseThreads,
206 bool bKeepOverhead)
207{
208 // Check for size of data
209 if(mataData.cols() < iOrder){
210 qWarning() << QString("[Filter::filterData] Filter length/order is bigger than data length. Returning.");
211 return mataData;
212 }
213
214 // Normalize cut off frequencies to nyquist
215 dCenterfreq = dCenterfreq/(dSFreq/2.0);
216 bandwidth = bandwidth/(dSFreq/2.0);
217 dTransition = dTransition/(dSFreq/2.0);
218
219 // create filter
220 FilterKernel filter = FilterKernel("filter_kernel",
221 type,
222 iOrder,
223 dCenterfreq,
224 bandwidth,
225 dTransition,
226 dSFreq,
227 designMethod);
228
229 return filterData(mataData,
230 filter,
231 vecPicks,
232 bUseThreads,
233 bKeepOverhead);
234}
235
236//=============================================================================================================
237
238MatrixXd RTPROCESSINGLIB::filterData(const MatrixXd& mataData,
239 const FilterKernel& filterKernel,
240 const RowVectorXi& vecPicks,
241 bool bUseThreads,
242 bool bKeepOverhead)
243{
244 int iOrder = filterKernel.getFilterOrder();
245
246 // Check for size of data
247 if(mataData.cols() < iOrder){
248 qWarning() << "[Filter::filterData] Filter length/order is bigger than data length. Returning.";
249 return mataData;
250 }
251
252 // Create output matrix with size of input matrix
253 MatrixXd matDataOut(mataData.rows(), mataData.cols()+iOrder);
254 matDataOut.setZero();
255 MatrixXd sliceFiltered;
256
257 // slice input data into data junks with proper length so that the slices are always >= the filter order
258 float fFactor = 2.0f;
259 int iSize = fFactor * iOrder;
260 int residual = mataData.cols() % iSize;
261 while(residual < iOrder) {
262 fFactor = fFactor - 0.1f;
263 iSize = fFactor * iOrder;
264 residual = mataData.cols() % iSize;
265
266 if(iSize < iOrder) {
267 iSize = mataData.cols();
268 break;
269 }
270 }
271
272 if(mataData.cols() > iSize) {
273 int from = 0;
274 int numSlices = ceil(float(mataData.cols())/float(iSize)); //calculate number of data slices
275
276 for (int i = 0; i < numSlices; i++) {
277 if(i == numSlices-1) {
278 //catch the last one that might be shorter than the other blocks
279 iSize = mataData.cols() - (iSize * (numSlices -1));
280 }
281
282 // Filter the data block. This will return data with a fitler delay of iOrder/2 in front and back
283 sliceFiltered = filterDataBlock(mataData.block(0,from,mataData.rows(),iSize),
284 vecPicks,
285 filterKernel,
286 bUseThreads);
287
288 // Perform overlap add
289 if(i == 0) {
290 matDataOut.block(0,0,mataData.rows(),sliceFiltered.cols()) += sliceFiltered;
291 } else {
292 matDataOut.block(0,from,mataData.rows(),sliceFiltered.cols()) += sliceFiltered;
293 }
294
295 from += iSize;
296 }
297 } else {
298 matDataOut = filterDataBlock(mataData,
299 vecPicks,
300 filterKernel,
301 bUseThreads);
302 }
303
304 if(bKeepOverhead) {
305 return matDataOut;
306 } else {
307 return matDataOut.block(0,iOrder/2,matDataOut.rows(),mataData.cols());
308 }
309}
310
311//=============================================================================================================
312
313MatrixXd RTPROCESSINGLIB::filterDataBlock(const MatrixXd& mataData,
314 const RowVectorXi& vecPicks,
315 const FilterKernel& filterKernel,
316 bool bUseThreads)
317{
318 int iOrder = filterKernel.getFilterOrder();
319
320 // Check for size of data
321 if(mataData.cols() < iOrder){
322 qWarning() << QString("[Filter::filterDataBlock] Filter length/order is bigger than data length. Returning.");
323 return mataData;
324 }
325
326 // Setup filters to the correct length, so we do not have to do this everytime we call the FFT filter function
327 FilterKernel filterKernelSetup = filterKernel;
328 filterKernelSetup.prepareFilter(mataData.cols());
329
330 // Do the concurrent filtering
331 RowVectorXi vecPicksNew = vecPicks;
332 if(vecPicksNew.cols() == 0) {
333 vecPicksNew = RowVectorXi::LinSpaced(mataData.rows(), 0, mataData.rows());
334 }
335
336 // Generate QList structure which can be handled by the QConcurrent framework
337 QList<FilterObject> timeData;
338
339 // Only select channels specified in vecPicksNew
340 FilterObject data;
341 for(qint32 i = 0; i < vecPicksNew.cols(); ++i) {
342 data.filterKernel = filterKernelSetup;
343 data.iRow = vecPicksNew[i];
344 data.vecData = mataData.row(vecPicksNew[i]);
345 timeData.append(data);
346 }
347
348 // Copy in data from last data block. This is necessary in order to also delay channels which are not filtered
349 MatrixXd matDataOut(mataData.rows(), mataData.cols()+iOrder);
350 matDataOut.setZero();
351 matDataOut.block(0, iOrder/2, mataData.rows(), mataData.cols()) = mataData;
352
353 if(bUseThreads) {
354 QFuture<void> future = QtConcurrent::map(timeData,
355 filterChannel);
356 future.waitForFinished();
357 } else {
358 for(int i = 0; i < timeData.size(); ++i) {
359 filterChannel(timeData[i]);
360 }
361 }
362
363 // Do the overlap add method and store in matDataOut
364 RowVectorXd tempData;
365
366 for(int r = 0; r < timeData.size(); r++) {
367 // Write the newly calculated filtered data to the filter data matrix. This data has a delay of iOrder/2 in front and back
368 matDataOut.row(timeData.at(r).iRow) = timeData.at(r).vecData;
369 }
370
371 return matDataOut;
372}
373
374//=============================================================================================================
375
377{
378 //channelDataTime.vecData = channelDataTime.first.at(i).applyConvFilter(channelDataTime.vecData, true);
379 channelDataTime.filterKernel.applyFftFilter(channelDataTime.vecData, true); //FFT Convolution for rt is not suitable. FFT make the signal filtering non causal.
380}
381
382//=============================================================================================================
383// DEFINE MEMBER METHODS
384//=============================================================================================================
385
386MatrixXd FilterOverlapAdd::calculate(const MatrixXd& mataData,
387 int type,
388 double dCenterfreq,
389 double bandwidth,
390 double dTransition,
391 double dSFreq,
392 int iOrder,
393 int designMethod,
394 const RowVectorXi& vecPicks,
395 bool bFilterEnd,
396 bool bUseThreads,
397 bool bKeepOverhead)
398{
399 // Check for size of data
400 if(mataData.cols() < iOrder){
401 qWarning() << QString("[Filter::filterData] Filter length/order is bigger than data length. Returning.");
402 return mataData;
403 }
404
405 // Normalize cut off frequencies to nyquist
406 dCenterfreq = dCenterfreq/(dSFreq/2.0);
407 bandwidth = bandwidth/(dSFreq/2.0);
408 dTransition = dTransition/(dSFreq/2.0);
409
410 // create filter
411 FilterKernel filter = FilterKernel("filter_kernel",
412 type,
413 iOrder,
414 dCenterfreq,
415 bandwidth,
416 dTransition,
417 dSFreq,
418 designMethod);
419
420 return calculate(mataData,
421 filter,
422 vecPicks,
423 bFilterEnd,
424 bUseThreads,
425 bKeepOverhead);
426}
427
428//=============================================================================================================
429
430MatrixXd FilterOverlapAdd::calculate(const MatrixXd& mataData,
431 const FilterKernel& filterKernel,
432 const RowVectorXi& vecPicks,
433 bool bFilterEnd,
434 bool bUseThreads,
435 bool bKeepOverhead)
436{
437 int iOrder = filterKernel.getFilterOrder();
438
439 // Check for size of data
440 if(mataData.cols() < iOrder){
441 qWarning() << "[Filter::filterData] Filter length/order is bigger than data length. Returning.";
442 return mataData;
443 }
444
445 // Init overlaps from last block
446 if(m_matOverlapBack.cols() != iOrder || m_matOverlapBack.rows() < mataData.rows()) {
447 m_matOverlapBack.resize(mataData.rows(), iOrder);
448 m_matOverlapBack.setZero();
449 }
450
451 if(m_matOverlapFront.cols() != iOrder || m_matOverlapFront.rows() < mataData.rows()) {
452 m_matOverlapFront.resize(mataData.rows(), iOrder);
453 m_matOverlapFront.setZero();
454 }
455
456 // Create output matrix with size of input matrix
457 MatrixXd matDataOut(mataData.rows(), mataData.cols()+iOrder);
458 matDataOut.setZero();
459 MatrixXd sliceFiltered;
460
461 // slice input data into data junks with proper length so that the slices are always >= the filter order
462 float fFactor = 2.0f;
463 int iSize = fFactor * iOrder;
464 int residual = mataData.cols() % iSize;
465 while(residual < iOrder) {
466 fFactor = fFactor - 0.1f;
467 iSize = fFactor * iOrder;
468 residual = mataData.cols() % iSize;
469
470 if(iSize < iOrder) {
471 iSize = mataData.cols();
472 break;
473 }
474 }
475
476 if(mataData.cols() > iSize) {
477 int from = 0;
478 int numSlices = ceil(float(mataData.cols())/float(iSize)); //calculate number of data slices
479
480 for (int i = 0; i < numSlices; i++) {
481 if(i == numSlices-1) {
482 //catch the last one that might be shorter than the other blocks
483 iSize = mataData.cols() - (iSize * (numSlices -1));
484 }
485
486 // Filter the data block. This will return data with a fitler delay of iOrder/2 in front and back
487 sliceFiltered = filterDataBlock(mataData.block(0,from,mataData.rows(),iSize),
488 vecPicks,
489 filterKernel,
490 bUseThreads);
491
492 if(i == 0) {
493 matDataOut.block(0,0,mataData.rows(),sliceFiltered.cols()) += sliceFiltered;
494 } else {
495 matDataOut.block(0,from,mataData.rows(),sliceFiltered.cols()) += sliceFiltered;
496 }
497
498 if(bFilterEnd && (i == 0)) {
499 matDataOut.block(0,0,matDataOut.rows(),iOrder) += m_matOverlapBack;
500 } else if (!bFilterEnd && (i == numSlices-1)) {
501 matDataOut.block(0,matDataOut.cols()-iOrder,matDataOut.rows(),iOrder) += m_matOverlapFront;
502 }
503
504 from += iSize;
505 }
506 } else {
507 matDataOut = filterDataBlock(mataData,
508 vecPicks,
509 filterKernel,
510 bUseThreads);
511
512 if(bFilterEnd) {
513 matDataOut.block(0,0,matDataOut.rows(),iOrder) += m_matOverlapBack;
514 } else {
515 matDataOut.block(0,matDataOut.cols()-iOrder,matDataOut.rows(),iOrder) += m_matOverlapFront;
516 }
517 }
518
519 // Refresh the overlap matrix with the new calculated filtered data
520 m_matOverlapBack = matDataOut.block(0,matDataOut.cols()-iOrder,matDataOut.rows(),iOrder);
521 m_matOverlapFront = matDataOut.block(0,0,matDataOut.rows(),iOrder);
522
523 if(bKeepOverhead) {
524 return matDataOut;
525 } else {
526 return matDataOut.block(0,0,matDataOut.rows(),mataData.cols());
527 }
528}
529
530//=============================================================================================================
531
533{
534 m_matOverlapBack.resize(0,0);
535 m_matOverlapFront.resize(0,0);
536}
Filter declarations.
RTPROCESINGSHARED_EXPORT Eigen::MatrixXd filterData(const Eigen::MatrixXd &matData, int type, double dCenterfreq, double dBandwidth, double dTransition, double dSFreq, int iOrder=1024, int designMethod=FilterKernel::m_designMethods.indexOf(FilterParameter("Cosine")), const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), bool bUseThreads=true, bool bKeepOverhead=false)
RTPROCESINGSHARED_EXPORT bool filterFile(QIODevice &pIODevice, QSharedPointer< FIFFLIB::FiffRawData > pFiffRawData, int type, double dCenterfreq, double dBandwidth, double dTransition, double dSFreq, int iOrder=4096, int designMethod=FilterKernel::m_designMethods.indexOf(FilterParameter("Cosine")), const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), bool bUseThreads=true)
RTPROCESINGSHARED_EXPORT Eigen::MatrixXd filterDataBlock(const Eigen::MatrixXd &mataData, const Eigen::RowVectorXi &vecPicks, const RTPROCESSINGLIB::FilterKernel &filterKernel, bool bUseThreads=true)
RTPROCESINGSHARED_EXPORT void filterChannel(FilterObject &channelDataTime)
Definition filter.cpp:376
FiffRawData class declaration.
Header file describing the numerical values used in fif files.
#define FIFF_FIRST_SAMPLE
Definition fiff_file.h:461
MNEMath class declaration.
static FiffStream::SPtr start_writing_raw(QIODevice &p_IODevice, const FiffInfo &info, Eigen::RowVectorXd &cals, Eigen::MatrixXi sel=defaultMatrixXi, bool bResetRange=true)
QSharedPointer< FiffStream > SPtr
Eigen::MatrixXd calculate(const Eigen::MatrixXd &matData, int type, double dCenterfreq, double dBandwidth, double dTransition, double dSFreq, int iOrder=1024, int designMethod=FilterKernel::m_designMethods.indexOf(FilterParameter("Cosine")), const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), bool bFilterEnd=true, bool bUseThreads=true, bool bKeepOverhead=false)
The FilterKernel class provides methods to create/design a FIR filter kernel.
void applyFftFilter(Eigen::RowVectorXd &vecData, bool bKeepOverhead=false)
void prepareFilter(int iDataSize)