56QVector<QPair<int, int>> findContiguousSegments(
const VectorXi& mask)
58 QVector<QPair<int, int>> segs;
59 const int n =
static_cast<int>(mask.size());
64 while (i < n && mask(i))
66 segs.append({start, i - 1});
78void removeShortSegments(QVector<QPair<int, int>>& segs,
int minSamples)
82 QVector<QPair<int, int>> filtered;
83 for (
const auto& seg : segs) {
84 if (seg.second - seg.first + 1 >= minSamples)
96 int best = std::numeric_limits<int>::max();
97 for (
long long p5 = 1; p5 < 2LL * n + 1; p5 *= 5) {
98 for (
long long p35 = p5; p35 < 2LL * n + 1; p35 *= 3) {
102 best =
static_cast<int>(std::min<long long>(best, v));
115 const MatrixXd& data,
123 const Index nTimes = data.cols();
124 if (data.rows() == 0 || nTimes == 0)
127 RowVectorXi picks = info.
pick_types(QStringLiteral(
"mag"),
false,
false);
128 if (picks.size() == 0)
129 picks = info.
pick_types(QStringLiteral(
"grad"),
false,
false);
130 if (picks.size() == 0)
132 if (picks.size() == 0) {
133 qWarning(
"annotateMusclZscore: no MEG or EEG channels found");
136 MatrixXd sub(picks.size(), nTimes);
137 for (Index i = 0; i < picks.size(); ++i)
138 sub.row(i) = data.row(picks(i));
141 const int nFft = nextFastLen(
static_cast<int>(nTimes));
142 RowVectorXd score = RowVectorXd::Zero(nTimes);
143 for (Index i = 0; i < band.rows(); ++i) {
145 const double mean = env.mean();
146 const double sd = std::sqrt((env.array() - mean).square().mean());
148 score += (env.array() - mean).matrix() / sd;
150 score /= std::sqrt(
static_cast<double>(band.rows()));
155 VectorXi mask(nTimes);
156 for (Index i = 0; i < nTimes; ++i)
157 mask(i) = score(i) > params.
dThreshold ? 1 : 0;
160 for (
const auto& seg : findContiguousSegments((1 - mask.array()).matrix())) {
161 if (seg.second - seg.first + 1 < minGood)
162 mask.segment(seg.first, seg.second - seg.first + 1).setOnes();
164 for (
const auto& seg : findContiguousSegments(mask)) {
165 const int last = std::min(seg.second + 1,
static_cast<int>(nTimes) - 1);
166 annot.
append(seg.first / sfreq, (last - seg.first) / sfreq, QStringLiteral(
"BAD_muscle"));
174 const MatrixXd& data,
180 const Eigen::Index nCh = data.rows();
181 const Eigen::Index nTimes = data.cols();
183 if (nCh == 0 || nTimes == 0)
186 const bool checkPeakMax = std::isfinite(params.
dPeakMax);
187 const bool checkPeakMin = std::isfinite(params.
dPeakMin);
188 const bool checkFlat = params.
dFlatMin > 0.0;
189 const int minSamples =
static_cast<int>(std::round(params.
dMinDuration * sfreq));
192 if (checkPeakMax || checkPeakMin) {
193 for (Eigen::Index ch = 0; ch < nCh; ++ch) {
194 const QString chName = (ch < info.
ch_names.size()) ? info.
ch_names[
static_cast<int>(ch)] : QString(
"CH%1").arg(ch);
196 VectorXi mask(nTimes);
197 for (Eigen::Index s = 0; s < nTimes; ++s) {
198 const double val = data(ch, s);
199 mask(
static_cast<int>(s)) = ((checkPeakMax && val > params.
dPeakMax) ||
200 (checkPeakMin && val < params.
dPeakMin))
205 auto segs = findContiguousSegments(mask);
206 removeShortSegments(segs, minSamples);
208 for (
const auto& seg : segs) {
209 const double onset =
static_cast<double>(seg.first) / sfreq;
210 const double duration =
static_cast<double>(seg.second - seg.first + 1) / sfreq;
218 const int winSamples = std::max(1,
static_cast<int>(std::round(params.
dWindowSec * sfreq)));
220 for (Eigen::Index ch = 0; ch < nCh; ++ch) {
221 const QString chName = (ch < info.
ch_names.size()) ? info.
ch_names[
static_cast<int>(ch)] : QString(
"CH%1").arg(ch);
223 VectorXi mask = VectorXi::Zero(
static_cast<int>(nTimes));
225 for (Eigen::Index s = 0; s <= nTimes - winSamples; ++s) {
226 const auto seg = data.block(ch, s, 1, winSamples);
227 const double p2p = seg.maxCoeff() - seg.minCoeff();
229 for (
int j =
static_cast<int>(s); j < static_cast<int>(s) + winSamples; ++j)
234 auto segs = findContiguousSegments(mask);
235 removeShortSegments(segs, minSamples);
237 for (
const auto& seg : segs) {
238 const double onset =
static_cast<double>(seg.first) / sfreq;
239 const double duration =
static_cast<double>(seg.second - seg.first + 1) / sfreq;
240 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...
Continuous-data annotation of muscle and amplitude artefacts.
Discoverable design / apply façade over the UTILSLIB::FilterKernel FIR engine.
ConnectivityAec — Amplitude Envelope Correlation connectivity metric.
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(), Eigen::RowVectorXd *scores=nullptr)
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 Eigen::VectorXd hilbertEnvelope(const Eigen::VectorXd &signal, int nFft=0)
static Eigen::MatrixXd filterData(const Eigen::MatrixXd &matData, double dSFreq, double dLFreq, double dHFreq)
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,...
Eigen::RowVectorXi pick_types(const QString meg, bool eeg=false, bool stim=false, const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList) const