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