MNE-CPP  0.1.9
A Framework for Electrophysiology
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 
63 using namespace RTPROCESSINGLIB;
64 using namespace Eigen;
65 using namespace FIFFLIB;
66 using namespace UTILSLIB;
67 
68 //=============================================================================================================
69 // DEFINE GLOBAL RTPROCESSINGLIB METHODS
70 //=============================================================================================================
71 
72 bool 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 
108 bool 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 
196 MatrixXd 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 
238 MatrixXd 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 
313 MatrixXd 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 
386 MatrixXd 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 
430 MatrixXd 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 }
RTPROCESSINGLIB::FilterOverlapAdd::calculate
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)
FIFFLIB::FiffStream::SPtr
QSharedPointer< FiffStream > SPtr
Definition: fiff_stream.h:107
RTPROCESSINGLIB::FilterKernel
The FilterKernel class provides methods to create/design a FIR filter kernel.
Definition: filterkernel.h:132
FIFF_FIRST_SAMPLE
#define FIFF_FIRST_SAMPLE
Definition: fiff_file.h:461
RTPROCESSINGLIB::filterDataBlock
RTPROCESINGSHARED_EXPORT Eigen::MatrixXd filterDataBlock(const Eigen::MatrixXd &mataData, const Eigen::RowVectorXi &vecPicks, const RTPROCESSINGLIB::FilterKernel &filterKernel, bool bUseThreads=true)
filter.h
Filter declarations.
RTPROCESSINGLIB::filterChannel
RTPROCESINGSHARED_EXPORT void filterChannel(FilterObject &channelDataTime)
Definition: filter.cpp:376
RTPROCESSINGLIB::filterData
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)
RTPROCESSINGLIB::FilterKernel::applyFftFilter
void applyFftFilter(Eigen::RowVectorXd &vecData, bool bKeepOverhead=false)
Definition: filterkernel.cpp:180
RTPROCESSINGLIB::filterFile
RTPROCESINGSHARED_EXPORT bool filterFile(QIODevice &pIODevice, QSharedPointer< FIFFLIB::FiffRawData > pFiffRawData, const RTPROCESSINGLIB::FilterKernel &filterKernel, const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), bool bUseThreads=false)
RTPROCESSINGLIB::FilterObject
Definition: filter.h:78
RTPROCESSINGLIB::filterData
RTPROCESINGSHARED_EXPORT Eigen::MatrixXd filterData(const Eigen::MatrixXd &mataData, const RTPROCESSINGLIB::FilterKernel &filterKernel, const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), bool bUseThreads=true, bool bKeepOverhead=false)
RTPROCESSINGLIB::FilterOverlapAdd::reset
void reset()
Definition: filter.cpp:532
RTPROCESSINGLIB::FilterKernel::prepareFilter
void prepareFilter(int iDataSize)
Definition: filterkernel.cpp:140
fiff_file.h
Header file describing the numerical values used in fif files.
fiff_raw_data.h
FiffRawData class declaration.
RTPROCESSINGLIB::filterFile
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)
mnemath.h
MNEMath class declaration.