54QVector<QPair<int,int>> findContiguousSegments(
const VectorXi& mask)
56 QVector<QPair<int,int>> segs;
57 const int n =
static_cast<int>(mask.size());
62 while (i < n && mask(i))
64 segs.append({start, i - 1});
76void mergeCloseSegments(QVector<QPair<int,int>>& segs,
int gapSamples)
80 QVector<QPair<int,int>> merged;
81 merged.append(segs.first());
82 for (
int i = 1; i < segs.size(); ++i) {
83 if (segs[i].first - merged.last().second <= gapSamples) {
84 merged.last().second = segs[i].second;
86 merged.append(segs[i]);
96void removeShortSegments(QVector<QPair<int,int>>& segs,
int minSamples)
100 QVector<QPair<int,int>> filtered;
101 for (
const auto& seg : segs) {
102 if (seg.second - seg.first + 1 >= minSamples)
103 filtered.append(seg);
112QVector<int> findChannelsByKind(
const FiffInfo& info,
int kind)
114 QVector<int> indices;
115 for (
int i = 0; i < info.
chs.size(); ++i) {
116 if (info.
chs[i].kind == kind)
129 const MatrixXd& data,
135 const Eigen::Index nTimes = data.cols();
137 if (data.rows() == 0 || nTimes == 0)
141 QVector<int> chIdx = findChannelsByKind(info,
FIFFV_MEG_CH);
144 if (chIdx.isEmpty()) {
145 qWarning(
"annotateMusclZscore: no MEG or EEG channels found");
150 const int nCh = chIdx.size();
151 MatrixXd sub(nCh, nTimes);
152 for (
int i = 0; i < nCh; ++i)
153 sub.row(i) = data.row(chIdx[i]);
164 filtered = filtered.cwiseAbs();
167 MatrixXd zscores(nCh, nTimes);
168 int validChannels = 0;
169 for (
int ch = 0; ch < nCh; ++ch) {
170 const double mean = filtered.row(ch).mean();
171 const double variance = (filtered.row(ch).array() - mean).square().mean();
172 const double stddev = std::sqrt(variance);
173 if (stddev < 1e-30) {
174 zscores.row(ch).setZero();
177 zscores.row(ch) = (filtered.row(ch).array() - mean) / stddev;
181 if (validChannels == 0)
185 RowVectorXd avgZ = zscores.colwise().mean();
188 VectorXi mask(nTimes);
189 for (Eigen::Index i = 0; i < nTimes; ++i)
190 mask(
static_cast<int>(i)) = (avgZ(i) > params.
dThreshold) ? 1 : 0;
193 auto segs = findContiguousSegments(mask);
196 const int gapSamples =
static_cast<int>(std::round(params.
dMinGapSec * sfreq));
197 mergeCloseSegments(segs, gapSamples);
200 const int minSamples =
static_cast<int>(std::round(params.
dMinDuration * sfreq));
201 removeShortSegments(segs, minSamples);
204 for (
const auto& seg : segs) {
205 const double onset =
static_cast<double>(seg.first) / sfreq;
206 const double duration =
static_cast<double>(seg.second - seg.first + 1) / sfreq;
207 annot.
append(onset, duration, QStringLiteral(
"BAD_muscle"));
216 const MatrixXd& data,
222 const Eigen::Index nCh = data.rows();
223 const Eigen::Index nTimes = data.cols();
225 if (nCh == 0 || nTimes == 0)
228 const bool checkPeakMax = std::isfinite(params.
dPeakMax);
229 const bool checkPeakMin = std::isfinite(params.
dPeakMin);
230 const bool checkFlat = params.
dFlatMin > 0.0;
231 const int minSamples =
static_cast<int>(std::round(params.
dMinDuration * sfreq));
234 if (checkPeakMax || checkPeakMin) {
235 for (Eigen::Index ch = 0; ch < nCh; ++ch) {
236 const QString chName = (ch < info.
ch_names.size()) ? info.
ch_names[
static_cast<int>(ch)] : QString(
"CH%1").arg(ch);
238 VectorXi mask(nTimes);
239 for (Eigen::Index s = 0; s < nTimes; ++s) {
240 const double val = data(ch, s);
241 mask(
static_cast<int>(s)) = ((checkPeakMax && val > params.
dPeakMax) ||
242 (checkPeakMin && val < params.
dPeakMin)) ? 1 : 0;
245 auto segs = findContiguousSegments(mask);
246 removeShortSegments(segs, minSamples);
248 for (
const auto& seg : segs) {
249 const double onset =
static_cast<double>(seg.first) / sfreq;
250 const double duration =
static_cast<double>(seg.second - seg.first + 1) / sfreq;
258 const int winSamples = std::max(1,
static_cast<int>(std::round(params.
dWindowSec * sfreq)));
260 for (Eigen::Index ch = 0; ch < nCh; ++ch) {
261 const QString chName = (ch < info.
ch_names.size()) ? info.
ch_names[
static_cast<int>(ch)] : QString(
"CH%1").arg(ch);
263 VectorXi mask = VectorXi::Zero(
static_cast<int>(nTimes));
265 for (Eigen::Index s = 0; s <= nTimes - winSamples; ++s) {
266 const auto seg = data.block(ch, s, 1, winSamples);
267 const double p2p = seg.maxCoeff() - seg.minCoeff();
269 for (
int j =
static_cast<int>(s); j < static_cast<int>(s) + winSamples; ++j)
274 auto segs = findContiguousSegments(mask);
275 removeShortSegments(segs, minSamples);
277 for (
const auto& seg : segs) {
278 const double onset =
static_cast<double>(seg.first) / sfreq;
279 const double duration =
static_cast<double>(seg.second - seg.first + 1) / sfreq;
280 annot.
append(onset, duration, QStringLiteral(
"BAD_flat"), QStringList{chName});
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
Butterworth IIR filter design and application via numerically stable second-order sections.
Continuous-data annotation of muscle and amplitude artefacts.
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 FIFFLIB::FiffAnnotations annotateMusclZscore(const Eigen::MatrixXd &data, const FIFFLIB::FiffInfo &info, double sfreq, const AnnotateMusclParams ¶ms=AnnotateMusclParams())
Detect muscle artifacts via high-frequency z-score and annotate bad segments.
DSPSHARED_EXPORT FIFFLIB::FiffAnnotations annotateAmplitude(const Eigen::MatrixXd &data, const FIFFLIB::FiffInfo &info, double sfreq, const AnnotateAmplitudeParams ¶ms=AnnotateAmplitudeParams())
Annotate segments where amplitude exceeds thresholds or is too flat.
Parameters for muscle artifact annotation.
Parameters for amplitude-based annotation.
static QVector< IirBiquad > designButterworth(int iOrder, FilterType type, double dCutoffLow, double dCutoffHigh, double dSFreq)
static Eigen::MatrixXd applyZeroPhaseMatrix(const Eigen::MatrixXd &matData, const QVector< IirBiquad > &sos)
Container for FiffAnnotation entries with FIFF, JSON and CSV serializers.
void append(const FiffAnnotation &annotation)
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...