64QVector<int> goodChannelsOfKind(
const FiffInfo& info,
int kind)
67 for (
int i = 0; i < info.
nchan; ++i) {
68 if (info.
chs[i].kind == kind && !info.
bads.contains(info.
ch_names.value(i, info.
chs[i].ch_name)))
75QList<FiffProj> exgProjectors(
const MatrixXd& data,
const FiffInfo& info,
double sf,
const QVector<int>& events,
78 QList<FiffProj> projs = info.
projs;
79 if (events.isEmpty()) {
80 qWarning() <<
"[ArtifactDetect] No" << mode <<
"events found";
86 picks += goodChannelsOfKind(info, kind);
88 std::sort(picks.begin(), picks.end());
89 RowVectorXi sel(picks.size());
90 MatrixXd picked(picks.size(), data.cols());
91 for (
int c = 0; c < picks.size(); ++c) {
93 picked.row(c) = data.row(picks[c]);
99 QList<FiffProj> active = info.
projs;
106 QVector<double> limits(picks.size(), 0.0);
107 for (
int c = 0; c < picks.size(); ++c) {
118 const int first =
static_cast<int>(std::round(p.
dTmin * sf));
119 const int last =
static_cast<int>(std::round(p.
dTmax * sf));
120 MatrixXd sum = MatrixXd::Zero(picks.size(), last - first + 1);
122 for (
int ev : events) {
123 if (ev + first < 0 || ev + last >= filtered.cols()) {
126 const MatrixXd epoch = projector * filtered.middleCols(ev + first, last - first + 1);
128 for (
int c = 0; c < picks.size() && good; ++c) {
129 good = limits[c] <= 0.0 || epoch.row(c).maxCoeff() - epoch.row(c).minCoeff() <= limits[c];
137 qWarning() <<
"[ArtifactDetect] No good" << mode <<
"epochs found";
142 evoked.
info = pickedInfo;
144 evoked.
data = sum / nave;
145 evoked.
times = RowVectorXf::LinSpaced(last - first + 1,
static_cast<float>(first / sf),
static_cast<float>(last / sf));
148 proj.desc = mode +
"-" + proj.desc;
157QVector<int> ArtifactDetect::qrsDetector(
const RowVectorXd& filtered,
double dSFreq,
double dThreshold,
double dTStart)
160 const int winSize =
static_cast<int>(std::nearbyint(60.0 * dSFreq / 120.0));
161 const int init =
static_cast<int>(dSFreq);
162 const int nStart =
static_cast<int>(dSFreq * dTStart);
163 const RowVectorXd ecgAbs = filtered.tail(std::max<Index>(filtered.size() - nStart, 0)).cwiseAbs();
164 const int nPoints =
static_cast<int>(ecgAbs.size());
165 if (nPoints < 3 * init || init < 1) {
166 qWarning() <<
"ArtifactDetect::detectEcg: need at least 3 s of data after tstart.";
169 const double initMax = (ecgAbs.segment(0, init).maxCoeff() + ecgAbs.segment(init, init).maxCoeff() + ecgAbs.segment(2 * init, init).maxCoeff()) / 3.0;
171 std::vector<double> threshRuns;
172 if (dThreshold < 0.0) {
173 for (
int k = 0; k < 16; ++k)
174 threshRuns.push_back(0.3 + 0.05 * k);
176 threshRuns.push_back(dThreshold);
179 QVector<QVector<int>> cleanEvents;
180 for (
double threshValue : threshRuns) {
181 const double thresh1 = initMax * threshValue;
183 std::vector<double> rms;
184 for (
int ii = 0; ii < nPoints - winSize;) {
185 const auto window = ecgAbs.segment(ii, winSize);
186 if (window(0) > thresh1) {
188 window.maxCoeff(&maxTime);
189 times.append(ii +
static_cast<int>(maxTime));
190 rms.push_back(std::sqrt(window.squaredNorm() / winSize));
202 mean /=
static_cast<double>(rms.size());
205 var += (r - mean) * (r - mean);
206 const double rmsThresh = mean + std::sqrt(var /
static_cast<double>(rms.size())) * 2.5;
208 for (
int k = 0; k < times.size(); ++k) {
209 if (rms[
static_cast<std::size_t
>(k)] < rmsThresh)
210 events.append(times[k] + nStart);
212 if (!events.isEmpty())
213 cleanEvents.append(events);
215 if (cleanEvents.isEmpty())
219 std::vector<double> rates;
220 std::vector<double> plausible;
221 for (
const QVector<int>& events : std::as_const(cleanEvents)) {
222 rates.push_back(60.0 * events.size() / (filtered.size() / dSFreq));
223 if (rates.back() >= 40.0 && rates.back() <= 160.0)
224 plausible.push_back(rates.back());
227 if (!plausible.empty()) {
228 std::sort(plausible.begin(), plausible.end());
229 const std::size_t m = plausible.size() / 2;
230 ideal = plausible.size() % 2 ? plausible[m] : 0.5 * (plausible[m - 1] + plausible[m]);
232 std::size_t best = 0;
233 for (std::size_t k = 1; k < rates.size(); ++k) {
234 if (std::abs(rates[k] - ideal) < std::abs(rates[best] - ideal))
237 return cleanEvents[
static_cast<int>(best)];
250 if (matData.cols() == 0)
254 const QVector<int> ecgIdx = goodChannelsOfKind(fiffInfo,
FIFFV_ECG_CH);
255 if (!ecgIdx.isEmpty()) {
256 ecg = matData.row(ecgIdx.first());
258 QVector<int> mags, grads;
259 for (
int i : goodChannelsOfKind(fiffInfo,
FIFFV_MEG_CH))
261 const QVector<int>& meg = mags.isEmpty() ? grads : mags;
263 qWarning() <<
"ArtifactDetect::detectEcg: no ECG channel and no MEG channels to synthesise one from.";
266 ecg = RowVectorXd::Zero(matData.cols());
268 ecg += matData.row(i);
269 ecg /=
static_cast<double>(meg.size());
284 const QVector<int> eogIdx = goodChannelsOfKind(fiffInfo,
FIFFV_EOG_CH);
285 if (eogIdx.isEmpty() || matData.cols() == 0) {
286 qWarning() <<
"ArtifactDetect::detectEog: No EOG channel found.";
291 MatrixXd eog(eogIdx.size(), matData.cols());
292 for (
int k = 0; k < eogIdx.size(); ++k)
293 eog.row(k) = matData.row(eogIdx[k]);
296 broad.rowwise().squaredNorm().maxCoeff(&best);
299 const double mean = filtered.mean();
300 const int extrema = std::abs(filtered.maxCoeff() - mean) > std::abs(filtered.minCoeff() - mean) ? 1 : -1;
301 const int nStart =
static_cast<int>(dSFreq * params.
dTStart);
304 const VectorXd tail = filtered.tail(std::max<Index>(filtered.size() - nStart, 0)).transpose();
306 events.append(peak.first + nStart);
315 return exgProjectors(matData, fiffInfo, dSFreq,
detectEcg(matData, fiffInfo, dSFreq, detect), proj,
"ECG");
323 return exgProjectors(matData, fiffInfo, dSFreq,
detectEog(matData, fiffInfo, dSFreq, detect), proj,
"EOG");
FIFF channel descriptor record (FIFF_CH_INFO): per-channel logical/scanner numbers,...
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
constexpr auto FIFFV_ECG_CH
constexpr auto FIFF_UNIT_T
constexpr auto FIFFV_EOG_CH
Single averaged evoked response: time axis, samples, baseline, channel info and processing history.
Local-maxima peak detection in 1-D signals with prominence filtering.
Discoverable design / apply façade over the UTILSLIB::FilterKernel FIR engine.
Declaration of ArtifactDetect — ECG and EOG physiological artifact event detection.
FIFF file I/O, in-memory data structures and high-level readers/writers.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
DSPSHARED_EXPORT QList< QPair< int, double > > peakFinderMne(const Eigen::VectorXd &data, double thresh=std::numeric_limits< double >::quiet_NaN(), int extrema=1)
Noise-tolerant extrema search of mne.preprocessing.peak_finder.
SSP parameters of compute_proj_ecg / compute_proj_eog; ecg() and eog() give the MNE-Python defaults.
ArtifactDetectProjParams ProjParams
static QVector< int > detectEcg(const Eigen::MatrixXd &matData, const FIFFLIB::FiffInfo &fiffInfo, double dSFreq, const EcgParams ¶ms=EcgParams())
static QList< FIFFLIB::FiffProj > computeProjEcg(const Eigen::MatrixXd &matData, const FIFFLIB::FiffInfo &fiffInfo, double dSFreq, const ProjParams &proj=ProjParams::ecg(), const EcgParams &detect=EcgParams())
ArtifactDetectEogParams EogParams
ArtifactDetectEcgParams EcgParams
static QVector< int > detectEog(const Eigen::MatrixXd &matData, const FIFFLIB::FiffInfo &fiffInfo, double dSFreq, const EogParams ¶ms=EogParams())
static QList< FIFFLIB::FiffProj > computeProjEog(const Eigen::MatrixXd &matData, const FIFFLIB::FiffInfo &fiffInfo, double dSFreq, const ProjParams &proj=ProjParams::eog(), const EogParams &detect=EogParams())
static Eigen::MatrixXd filterDataZeroDouble(const Eigen::MatrixXd &matData, double dSFreq, double dLFreq, double dHFreq, FirWindow window=FirWindow::Hann)
Per-channel FIFF descriptor: identifiers, kind, calibration, coil type, channel-frame coil position a...
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
FiffInfo pick_info(const Eigen::RowVectorXi &sel=defaultVectorXi) const
Single SSP projection item: kind, active flag, desired flag and the named projection vector matrix.
static fiff_int_t make_projector(const QList< FiffProj > &projs, const QStringList &ch_names, Eigen::MatrixXd &proj, const QStringList &bads=defaultQStringList, Eigen::MatrixXd &U=defaultMatrixXd)
static QList< FiffProj > compute_from_evoked(const FiffEvoked &evoked, int nGrad=2, int nMag=2, int nEeg=2)