v2.0.0
Loading...
Searching...
No Matches
sourceestimatemanager.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
22#include "../core/viewstate.h"
23
24#include <QThread>
25#include <QDebug>
26#include <QSet>
27#include <cmath>
28
29//=============================================================================================================
30// DEFINE MEMBER METHODS
31//=============================================================================================================
32
34 : QObject(parent)
35{
36}
37
38//=============================================================================================================
39
44
45//=============================================================================================================
46
48{
49 if (m_stcWorker)
50 m_stcWorker->requestCancel();
51 if (m_loadingThread) {
52 m_loadingThread->quit();
53 m_loadingThread->wait();
54 }
55}
56
57//=============================================================================================================
58
59bool SourceEstimateManager::load(const QString &lhPath, const QString &rhPath,
60 const QMap<QString, std::shared_ptr<BrainSurface>> &surfaces,
61 const QString &activeSurfaceType)
62{
63 if (m_isLoading) {
64 qWarning() << "SourceEstimateManager: STC loading already in progress";
65 return false;
66 }
67
68 // Find surfaces for the active surface type
69 BrainSurface *lhSurface = nullptr;
70 BrainSurface *rhSurface = nullptr;
71
72 const QString lhKey = "lh_" + activeSurfaceType;
73 const QString rhKey = "rh_" + activeSurfaceType;
74
75 if (surfaces.contains(lhKey))
76 lhSurface = surfaces[lhKey].get();
77 if (surfaces.contains(rhKey))
78 rhSurface = surfaces[rhKey].get();
79
80 // Fallback: search for any lh_*/rh_* brain surface
81 if (!lhSurface || !rhSurface) {
82 for (auto it = surfaces.begin(); it != surfaces.end(); ++it) {
83 if (it.value() && it.value()->tissueType() == BrainSurface::TissueBrain) {
84 if (!lhSurface && it.key().startsWith("lh_")) {
85 lhSurface = it.value().get();
86 qDebug() << "SourceEstimateManager: Using fallback LH surface:" << it.key();
87 } else if (!rhSurface && it.key().startsWith("rh_")) {
88 rhSurface = it.value().get();
89 qDebug() << "SourceEstimateManager: Using fallback RH surface:" << it.key();
90 }
91 }
92 }
93 }
94
95 if (!lhSurface && !rhSurface) {
96 qWarning() << "SourceEstimateManager: No surfaces available for STC loading."
97 << "Active surface type:" << activeSurfaceType
98 << "Available keys:" << surfaces.keys();
99 return false;
100 }
101
102 // Clean up any previous loading thread
103 if (m_loadingThread) {
104 m_loadingThread->quit();
105 m_loadingThread->wait();
106 delete m_loadingThread;
107 m_loadingThread = nullptr;
108 }
109
110 // Create overlay for results
111 m_overlay = std::make_unique<SourceEstimateOverlay>();
112
113 // Create worker and thread
114 m_loadingThread = new QThread(this);
115 m_stcWorker = new StcLoadingWorker(lhPath, rhPath, lhSurface, rhSurface);
116 m_stcWorker->moveToThread(m_loadingThread);
117
118 connect(m_loadingThread, &QThread::started, m_stcWorker, &StcLoadingWorker::process);
120 connect(m_stcWorker, &StcLoadingWorker::finished, this, &SourceEstimateManager::onStcLoadingFinished);
121 connect(m_stcWorker, &StcLoadingWorker::finished, m_loadingThread, &QThread::quit);
122 connect(m_loadingThread, &QThread::finished, m_stcWorker, &QObject::deleteLater);
123
124 m_isLoading = true;
125 m_loadingThread->start();
126
127 return true;
128}
129
130//=============================================================================================================
131
133{
134 return m_overlay && m_overlay->isLoaded();
135}
136
137//=============================================================================================================
138
139void SourceEstimateManager::onStcLoadingFinished(bool success)
140{
141 m_isLoading = false;
142
143 if (!success || !m_stcWorker) {
144 qWarning() << "SourceEstimateManager: Async STC loading failed";
145 m_overlay.reset();
146 return;
147 }
148
149 // Transfer data from worker to overlay
150 if (m_stcWorker->hasLh()) {
151 m_overlay->setStcData(m_stcWorker->stcLh(), 0);
152 if (m_stcWorker->interpolationMatLh())
153 m_overlay->setInterpolationMatrix(m_stcWorker->interpolationMatLh(), 0);
154 }
155
156 if (m_stcWorker->hasRh()) {
157 m_overlay->setStcData(m_stcWorker->stcRh(), 1);
158 if (m_stcWorker->interpolationMatRh())
159 m_overlay->setInterpolationMatrix(m_stcWorker->interpolationMatRh(), 1);
160 }
161
162 m_overlay->updateThresholdsFromData();
163 emit thresholdsUpdated(m_overlay->thresholdMin(),
164 m_overlay->thresholdMid(),
165 m_overlay->thresholdMax());
166
167 if (m_overlay->isLoaded()) {
168 emit loaded(m_overlay->numTimePoints());
169 } else {
170 m_overlay.reset();
171 }
172}
173
174//=============================================================================================================
175
177 const QMap<QString, std::shared_ptr<BrainSurface>> &surfaces,
178 const SubView &singleView,
179 const QVector<SubView> &subViews)
180{
181 if (!m_overlay || !m_overlay->isLoaded()) return;
182
183 m_currentTimePoint = qBound(0, index, m_overlay->numTimePoints() - 1);
184
185 // Collect all distinct surface types used across single + multi views
186 QSet<QString> activeTypes;
187 activeTypes.insert(singleView.surfaceType);
188 for (int i = 0; i < subViews.size(); ++i)
189 activeTypes.insert(subViews[i].surfaceType);
190
191 // Apply source estimate to surfaces matching ANY active type
192 for (auto it = surfaces.begin(); it != surfaces.end(); ++it) {
193 for (const QString &type : activeTypes) {
194 if (it.key().endsWith(type)) {
195 m_overlay->applyToSurface(it.value().get(), m_currentTimePoint);
196 break;
197 }
198 }
199 }
200
201 emit timePointChanged(m_currentTimePoint, m_overlay->timeAtIndex(m_currentTimePoint));
202}
203
204//=============================================================================================================
205
207{
208 return (m_overlay && m_overlay->isLoaded()) ? m_overlay->tstep() : 0.0f;
209}
210
211//=============================================================================================================
212
214{
215 return (m_overlay && m_overlay->isLoaded()) ? m_overlay->tmin() : 0.0f;
216}
217
218//=============================================================================================================
219
221{
222 return (m_overlay && m_overlay->isLoaded()) ? m_overlay->numTimePoints() : 0;
223}
224
225//=============================================================================================================
226
228{
229 if (!m_overlay || !m_overlay->isLoaded()) return -1;
230
231 const float t0 = m_overlay->tmin();
232 const float dt = m_overlay->tstep();
233 const int numPts = m_overlay->numTimePoints();
234 if (numPts <= 0 || dt <= 0.0f) return -1;
235
236 const int idx = qRound((timeSec - t0) / dt);
237 return qBound(0, idx, numPts - 1);
238}
239
240//=============================================================================================================
241
242void SourceEstimateManager::setColormap(const QString &name)
243{
244 if (m_overlay)
245 m_overlay->setColormap(name);
246}
247
248//=============================================================================================================
249
250void SourceEstimateManager::setThresholds(float min, float mid, float max)
251{
252 if (m_overlay)
253 m_overlay->setThresholds(min, mid, max);
254
255 if (m_rtController)
256 m_rtController->setThresholds(min, mid, max);
257}
258
259//=============================================================================================================
260
261void SourceEstimateManager::startStreaming(const QMap<QString, std::shared_ptr<BrainSurface>> &surfaces,
262 const SubView &singleView,
263 const QVector<SubView> &subViews)
264{
265 Q_UNUSED(surfaces)
266 Q_UNUSED(singleView)
267 Q_UNUSED(subViews)
268
269 if (m_isStreaming) {
270 qDebug() << "SourceEstimateManager: Real-time streaming already active";
271 return;
272 }
273
274 if (!m_overlay || !m_overlay->isLoaded()) {
275 qWarning() << "SourceEstimateManager: Cannot start streaming — no source estimate loaded";
276 return;
277 }
278
279 // Create controller on first use
280 if (!m_rtController) {
281 m_rtController = std::make_unique<RtSourceDataController>(this);
282 connect(m_rtController.get(), &RtSourceDataController::newSmoothedDataAvailable,
284 }
285
286 // Propagate interpolation matrices from the overlay
287 m_rtController->setInterpolationMatrixLeft(m_overlay->interpolationMatLh());
288 m_rtController->setInterpolationMatrixRight(m_overlay->interpolationMatRh());
289
290 // Propagate current visualization parameters
291 m_rtController->setColormapType(m_overlay->colormap());
292 m_rtController->setThresholds(m_overlay->thresholdMin(),
293 m_overlay->thresholdMid(),
294 m_overlay->thresholdMax());
295 m_rtController->setSFreq(1.0 / m_overlay->tstep());
296
297 // Feed all STC time-points into the queue
298 const int nTimePoints = m_overlay->numTimePoints();
299 qDebug() << "SourceEstimateManager: Feeding" << nTimePoints << "time points into real-time queue";
300 m_rtController->clearData();
301
302 for (int t = 0; t < nTimePoints; ++t) {
303 Eigen::VectorXd col = m_overlay->sourceDataColumn(t);
304 if (col.size() > 0)
305 m_rtController->addData(col);
306 }
307
308 m_rtController->setStreamingState(true);
309 m_isStreaming = true;
310
311 qDebug() << "SourceEstimateManager: Real-time streaming started";
312}
313
314//=============================================================================================================
315
317{
318 if (!m_isStreaming) return;
319
320 if (m_rtController)
321 m_rtController->setStreamingState(false);
322
323 m_isStreaming = false;
324 qDebug() << "SourceEstimateManager: Real-time streaming stopped";
325}
326
327//=============================================================================================================
328
329void SourceEstimateManager::pushData(const Eigen::VectorXd &data)
330{
331 if (m_rtController)
332 m_rtController->addData(data);
333}
334
335//=============================================================================================================
336
338{
339 if (m_rtController)
340 m_rtController->setTimeInterval(msec);
341}
342
343//=============================================================================================================
344
346{
347 if (m_rtController)
348 m_rtController->setLoopState(enabled);
349}
350
351//=============================================================================================================
352
354{
355 return m_overlay.get();
356}
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
Colour-mapped source-time-course overlay that interpolates STC activation onto a cortical mesh and up...
Real-time source-estimate streaming controller that owns the data worker and the per-hemisphere inter...
Background worker that loads source-time-course (.stc) files and prepares per-hemisphere interpolatio...
Per-viewport state (camera preset, zoom, pan, visibility filter) and serialisation helpers.
Owns the source-time-course overlay together with its loader, real-time controller and target cortica...
Viewport subdivision holding its own camera, projection, and scissor rectangle.
Definition viewstate.h:139
QString surfaceType
Definition viewstate.h:141
Renderable cortical surface mesh with per-vertex color, curvature data, and GPU buffer management.
Color-mapped source estimate overlay that interpolates activation values onto a cortical surface mesh...
void pushData(const Eigen::VectorXd &data)
bool load(const QString &lhPath, const QString &rhPath, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces, const QString &activeSurfaceType)
void loadingProgress(int percent, const QString &message)
void setThresholds(float min, float mid, float max)
void setColormap(const QString &name)
void startStreaming(const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces, const SubView &singleView, const QVector< SubView > &subViews)
void timePointChanged(int index, float time)
void loaded(int numTimePoints)
void thresholdsUpdated(float min, float mid, float max)
const SourceEstimateOverlay * overlay() const
void setTimePoint(int index, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces, const SubView &singleView, const QVector< SubView > &subViews)
int closestIndex(float timeSec) const
void realtimeColorsAvailable(const QVector< uint32_t > &colorsLh, const QVector< uint32_t > &colorsRh)
SourceEstimateManager(QObject *parent=nullptr)
void newSmoothedDataAvailable(const QVector< uint32_t > &colorsLh, const QVector< uint32_t > &colorsRh)
Background worker that loads source estimate (STC) files and emits loaded data for visualization.
void progress(int percent, const QString &message)
void finished(bool success)