v2.0.0
Loading...
Searching...
No Matches
sts_adjacency.cpp
Go to the documentation of this file.
1//=============================================================================================================
24
25//=============================================================================================================
26// INCLUDES
27//=============================================================================================================
28
29#include "sts_adjacency.h"
30
31#include <fiff/fiff_ch_info.h>
32
33#include <algorithm>
34#include <vector>
35#include <cmath>
36
37//=============================================================================================================
38// USED NAMESPACES
39//=============================================================================================================
40
41using namespace STSLIB;
42using namespace FIFFLIB;
43using namespace Eigen;
44
45//=============================================================================================================
46
47SparseMatrix<int> StatsAdjacency::chain(int n)
48{
49 std::vector<Triplet<int>> triplets;
50 triplets.reserve(2 * static_cast<std::size_t>(std::max(n - 1, 0)));
51 for (int i = 0; i + 1 < n; ++i) {
52 triplets.emplace_back(i, i + 1, 1);
53 triplets.emplace_back(i + 1, i, 1);
54 }
55 SparseMatrix<int> adjacency(std::max(n, 0), std::max(n, 0));
56 adjacency.setFromTriplets(triplets.begin(), triplets.end());
57 return adjacency;
58}
59
60//=============================================================================================================
61
62SparseMatrix<int> StatsAdjacency::combine(const QList<SparseMatrix<int>>& dims)
63{
64 // Adapted from mne.stats.combine_adjacency (MNE-Python, BSD-3-Clause).
65 Index total = 1;
66 for (const SparseMatrix<int>& dim : dims)
67 total *= dim.rows();
68 std::vector<Triplet<int>> triplets;
69 triplets.reserve(static_cast<std::size_t>(total));
70 for (Index k = 0; k < total; ++k)
71 triplets.emplace_back(static_cast<int>(k), static_cast<int>(k), 1);
72
73 // stride of dimension d = product of the sizes after it
74 Index stride = total;
75 for (const SparseMatrix<int>& dim : dims) {
76 const Index n = dim.rows();
77 stride /= n;
78 const Index outer = total / (n * stride);
79 for (int c = 0; c < dim.outerSize(); ++c) {
80 for (SparseMatrix<int>::InnerIterator it(dim, c); it; ++it) {
81 if (it.row() == it.col() || it.value() == 0)
82 continue;
83 for (Index o = 0; o < outer; ++o) {
84 for (Index s = 0; s < stride; ++s) {
85 const Index base = o * n * stride + s;
86 triplets.emplace_back(static_cast<int>(base + it.row() * stride), static_cast<int>(base + it.col() * stride), 1);
87 }
88 }
89 }
90 }
91 }
92 SparseMatrix<int> combined(total, total);
93 combined.setFromTriplets(triplets.begin(), triplets.end(), [](int a, int) { return a; });
94 return combined;
95}
96
97//=============================================================================================================
98// DEFINE METHODS
99//=============================================================================================================
100
101SparseMatrix<int> StatsAdjacency::fromChannelPositions(const FiffInfo& info, const QStringList& picks)
102{
103 // Gather channel indices and 3D positions
104 std::vector<int> chIdx;
105 if (picks.isEmpty()) {
106 for (int i = 0; i < info.chs.size(); ++i) {
107 chIdx.push_back(i);
108 }
109 } else {
110 for (int i = 0; i < info.chs.size(); ++i) {
111 if (picks.contains(info.chs[i].ch_name)) {
112 chIdx.push_back(i);
113 }
114 }
115 }
116
117 const int nCh = static_cast<int>(chIdx.size());
118 if (nCh == 0) {
119 return SparseMatrix<int>(0, 0);
120 }
121
122 // Extract 3D positions
123 MatrixXd pos(nCh, 3);
124 for (int i = 0; i < nCh; ++i) {
125 const Vector3f& r0 = info.chs[chIdx[i]].chpos.r0;
126 pos(i, 0) = static_cast<double>(r0(0));
127 pos(i, 1) = static_cast<double>(r0(1));
128 pos(i, 2) = static_cast<double>(r0(2));
129 }
130
131 // Compute pairwise distances and find nearest-neighbor distance for each channel
132 std::vector<double> nnDist(nCh, std::numeric_limits<double>::max());
133 MatrixXd distMat(nCh, nCh);
134 for (int i = 0; i < nCh; ++i) {
135 distMat(i, i) = 0.0;
136 for (int j = i + 1; j < nCh; ++j) {
137 double d = (pos.row(i) - pos.row(j)).norm();
138 distMat(i, j) = d;
139 distMat(j, i) = d;
140 if (d < nnDist[i])
141 nnDist[i] = d;
142 if (d < nnDist[j])
143 nnDist[j] = d;
144 }
145 }
146
147 // Median nearest-neighbor distance
148 std::vector<double> sortedNN = nnDist;
149 std::sort(sortedNN.begin(), sortedNN.end());
150 double medianNN;
151 if (nCh % 2 == 0) {
152 medianNN = (sortedNN[nCh / 2 - 1] + sortedNN[nCh / 2]) / 2.0;
153 } else {
154 medianNN = sortedNN[nCh / 2];
155 }
156
157 double threshold = 3.0 * medianNN;
158
159 // Build adjacency
160 std::vector<Triplet<int>> triplets;
161 for (int i = 0; i < nCh; ++i) {
162 for (int j = i + 1; j < nCh; ++j) {
163 if (distMat(i, j) <= threshold) {
164 triplets.emplace_back(i, j, 1);
165 triplets.emplace_back(j, i, 1);
166 }
167 }
168 }
169
170 SparseMatrix<int> adj(nCh, nCh);
171 adj.setFromTriplets(triplets.begin(), triplets.end());
172 return adj;
173}
174
175//=============================================================================================================
176
177SparseMatrix<int> StatsAdjacency::fromSourceSpace(const MatrixX3i& tris, int nVertices)
178{
179 std::vector<Triplet<int>> triplets;
180
181 for (int t = 0; t < tris.rows(); ++t) {
182 int v0 = tris(t, 0);
183 int v1 = tris(t, 1);
184 int v2 = tris(t, 2);
185
186 // Mark all 3 pairs as adjacent (symmetric)
187 triplets.emplace_back(v0, v1, 1);
188 triplets.emplace_back(v1, v0, 1);
189 triplets.emplace_back(v0, v2, 1);
190 triplets.emplace_back(v2, v0, 1);
191 triplets.emplace_back(v1, v2, 1);
192 triplets.emplace_back(v2, v1, 1);
193 }
194
195 SparseMatrix<int> adj(nVertices, nVertices);
196 adj.setFromTriplets(triplets.begin(), triplets.end());
197 return adj;
198}
199
200//=============================================================================================================
201
203 const MatrixX3i& tris, int nVertices, int nTimes)
204{
205 // Build spatial adjacency first
206 SparseMatrix<int> spatialAdj = fromSourceSpace(tris, nVertices);
207
208 const int nTotal = nVertices * nTimes;
209 std::vector<Triplet<int>> triplets;
210
211 // Reserve approximate capacity: spatial edges * nTimes + temporal edges
212 triplets.reserve(static_cast<size_t>(spatialAdj.nonZeros()) * nTimes + static_cast<size_t>(nVertices) * (nTimes - 1) * 2);
213
214 // Spatial neighbors repeated for each time point
215 // Linear index: v * nTimes + t
216 for (int t = 0; t < nTimes; ++t) {
217 for (int k = 0; k < spatialAdj.outerSize(); ++k) {
218 for (SparseMatrix<int>::InnerIterator it(spatialAdj, k); it; ++it) {
219 int row = static_cast<int>(it.row()) * nTimes + t;
220 int col = static_cast<int>(it.col()) * nTimes + t;
221 triplets.emplace_back(row, col, 1);
222 }
223 }
224 }
225
226 // Temporal neighbors: vertex v at time t adjacent to v at t-1 and t+1
227 for (int v = 0; v < nVertices; ++v) {
228 for (int t = 0; t < nTimes - 1; ++t) {
229 int idx0 = v * nTimes + t;
230 int idx1 = v * nTimes + t + 1;
231 triplets.emplace_back(idx0, idx1, 1);
232 triplets.emplace_back(idx1, idx0, 1);
233 }
234 }
235
236 SparseMatrix<int> adj(nTotal, nTotal);
237 adj.setFromTriplets(triplets.begin(), triplets.end());
238 return adj;
239}
FIFF channel descriptor record (FIFF_CH_INFO): per-channel logical/scanner numbers,...
Construction of the sensor- and source-space neighbourhood graphs that define the cluster support for...
FIFF file I/O, in-memory data structures and high-level readers/writers.
Statistical testing (t-tests, F-tests, cluster permutation, multiple comparison correction).
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
QList< FiffChInfo > chs
static Eigen::SparseMatrix< int > fromSourceSpace(const Eigen::MatrixX3i &tris, int nVertices)
static Eigen::SparseMatrix< int > chain(int n)
static Eigen::SparseMatrix< int > fromChannelPositions(const FIFFLIB::FiffInfo &info, const QStringList &picks=QStringList())
static Eigen::SparseMatrix< int > combine(const QList< Eigen::SparseMatrix< int > > &dims)
static Eigen::SparseMatrix< int > fromSourceSpaceTemporal(const Eigen::MatrixX3i &tris, int nVertices, int nTimes)