v2.0.0
Loading...
Searching...
No Matches
polhemus_coregistration.h
Go to the documentation of this file.
1//=============================================================================================================
40
41#ifndef UTILS_POLHEMUS_COREGISTRATION_H
42#define UTILS_POLHEMUS_COREGISTRATION_H
43
44//=============================================================================================================
45// INCLUDES
46//=============================================================================================================
47
48#include "../utils_global.h"
49#include "acquired_points.h"
50#include "polhemus_connection.h"
51
52//=============================================================================================================
53// QT INCLUDES
54//=============================================================================================================
55
56#include <QMatrix4x4>
57#include <QObject>
58#include <QQuaternion>
59#include <QSettings>
60#include <QVector3D>
61
62#include <vector>
63
64//=============================================================================================================
65// DEFINE NAMESPACE UTILSLIB
66//=============================================================================================================
67
68namespace UTILSLIB
69{
70
71//=============================================================================================================
85{
86 Q_OBJECT
87
88public:
89 explicit PolhemusCoregistration(QObject* parent = nullptr);
90 ~PolhemusCoregistration() override = default;
91
92 //=========================================================================================================
93 // Station assignment
94 //=========================================================================================================
95
96 void setTrackerStation(int station);
97 void setPenStation(int station);
98 void setProbeStation(int station);
99
100 int trackerStation() const
101 {
102 return m_trackerStation;
103 }
104 int penStation() const
105 {
106 return m_penStation;
107 }
108 int probeStation() const
109 {
110 return m_probeStation;
111 }
112
113 //=========================================================================================================
114 // Pen tip offset (sensor body frame → tip)
115 //=========================================================================================================
116
117 void setPenTipOffset(const QVector3D& offset)
118 {
119 m_penTipOffset = offset;
120 }
121 QVector3D penTipOffset() const
122 {
123 return m_penTipOffset;
124 }
126 {
127 m_tipOffsetEnabled = on;
128 }
129 bool tipOffsetEnabled() const
130 {
131 return m_tipOffsetEnabled;
132 }
133
134 //=========================================================================================================
135 // Axis mirroring — compensate for transmitter placement
136 //=========================================================================================================
137
151 {
152 m_mirrorX = mirrorX;
153 m_mirrorY = mirrorY;
154 }
155 bool mirrorX() const
156 {
157 return m_mirrorX;
158 }
159 bool mirrorY() const
160 {
161 return m_mirrorY;
162 }
163
164 //=========================================================================================================
165 // Pivot calibration — determine pen tip offset by pivoting pen around its tip
166 //=========================================================================================================
167
168 enum class PivotState
169 {
170 Idle,
171 WaitingForStart,
172 Collecting,
173 Done
174 };
175
176 void startPivotCalibration();
177 void cancelPivotCalibration();
179 {
180 return m_pivotState;
181 }
183 {
184 return static_cast<int>(m_pivotPositions.size());
185 }
186 float pivotResidualMm() const
187 {
188 return m_pivotResidualMm;
189 }
190
191 //=========================================================================================================
192 // Tracker-to-device calibration offset
193 //=========================================================================================================
194
202 void setTrackerToDeviceOffset(const QVector3D& translation,
203 const QQuaternion& rotation);
204
206 {
207 return m_offsetTranslation;
208 }
209 QQuaternion trackerToDeviceRotation() const
210 {
211 return m_offsetRotation;
212 }
213
214 //=========================================================================================================
215 // Optical path calibration — determine the microscope optical axis
216 // relative to the tracker sensor.
217 //
218 // Workflow:
219 // 1. Move the OPMI to a position, aim at a point on the head surface,
220 // mark the focus point with the stylus pen → captureOpticalCalibSample().
221 // 2. Repeat at 2–3 different positions / focal distances.
222 // 3. Call solveOpticalCalibration() to fit a 3D line through the focus
223 // points in the tracker's local frame, yielding the optical axis
224 // direction and the optical center offset from the tracker.
225 //=========================================================================================================
226
228 {
229 QVector3D trackerPos;
230 QQuaternion trackerOri;
231 QVector3D focusPoint;
232 };
233
239
242 {
243 return static_cast<int>(m_opticalCalibSamples.size());
244 }
245
247 void clearOpticalCalibSamples();
248
259 bool captureObjectiveCenter();
260
263 {
264 return m_hasObjectiveCenter;
265 }
266
268 void clearObjectiveCenter();
269
275 QVector3D objectiveCenterLocal() const
276 {
277 return m_objectiveCenterLocal;
278 }
279
288 bool solveOpticalCalibration();
289
301 {
302 m_knownTrackerToObjectiveDist = metres;
303 }
305 {
306 return m_knownTrackerToObjectiveDist;
307 }
308
311 {
312 return m_opticalCalibValid;
313 }
314
320 QVector3D opticalAxisLocal() const
321 {
322 return m_opticalAxisLocal;
323 }
324
330 QVector3D opticalCenterLocal() const
331 {
332 return m_opticalCenterLocal;
333 }
334
341 {
342 return m_opticalCalibResidualMm;
343 }
344
351 {
352 return m_opticalCalibDepthSpreadMm;
353 }
354
368 bool applyOpticalAxisFineAdjust(const QVector3D& targetWorldPos,
369 float& correctionDeg);
370
373 {
374 return m_opticalFineAdjustApplied;
375 }
376
379 {
380 return m_opticalFineAdjustDeg;
381 }
382
384 void clearOpticalFineAdjust();
385
393 bool opticalRayInWorld(QVector3D& origin, QVector3D& direction) const;
394
404 bool opticalUpInWorld(QVector3D& up) const;
405
406 //=========================================================================================================
407 // Digitizer connection
408 //=========================================================================================================
409
410 void setConnection(PolhemusConnection* conn);
412 {
413 return m_pConn;
414 }
415
416 //=========================================================================================================
417 // Digitized-point store
418 //=========================================================================================================
419
421 {
422 return m_pPoints;
423 }
424
425 //=========================================================================================================
426 // Explicit capture API
427 //=========================================================================================================
428
429 bool captureCurrentPenPositionAsFiducial(FiducialId id);
430 bool captureCurrentPenPositionAsHeadShape();
431
432 //=========================================================================================================
433 // Model (BEM / FIFF) fiducials — target landmarks for SVD coregistration
434 //=========================================================================================================
435
436 void setModelFiducial(FiducialId id, const QVector3D& posInModel);
437 bool hasModelFiducial(FiducialId id) const;
438 bool hasAllModelFiducials() const;
439 QVector3D modelFiducial(FiducialId id) const;
440
441 bool hasAllPenFiducials() const;
442
443 //=========================================================================================================
444 // Vertex (CZ / top of head) — 4th reference point for orientation validation
445 //=========================================================================================================
446
447 void setModelVertex(const QVector3D& pos)
448 {
449 m_modelVertex = pos;
450 m_hasModelVertex = true;
451 }
452 bool hasModelVertex() const
453 {
454 return m_hasModelVertex;
455 }
456 QVector3D modelVertex() const
457 {
458 return m_modelVertex;
459 }
460
461 bool captureCurrentPenPositionAsVertex();
462 bool hasPenVertex() const
463 {
464 return m_hasPenVertex;
465 }
466 QVector3D penVertex() const
467 {
468 return m_penVertex;
469 }
470
471 //=========================================================================================================
472 // Registration
473 //=========================================================================================================
474
482 bool computeRegistration();
483
488 void resetRegistration();
489
490 //=========================================================================================================
491 // Session persistence — save/restore registration state across app restarts
492 //=========================================================================================================
493
494 void saveSessionState(QSettings& settings, const QString& prefix = QStringLiteral("polhemus")) const;
495 bool restoreSessionState(QSettings& settings, const QString& prefix = QStringLiteral("polhemus"));
496
497 bool registrationValid() const
498 {
499 return m_registrationValid;
500 }
501
502 //=========================================================================================================
503 // Current state accessors
504 //=========================================================================================================
505
506 QMatrix4x4 deviceToWorld() const
507 {
508 return m_deviceToWorld;
509 }
510 QMatrix4x4 headToWorld() const
511 {
512 return m_headToWorld;
513 }
514 QMatrix4x4 headToDevice() const
515 {
516 return m_headToDevice;
517 }
518 QMatrix4x4 worldToModel() const
519 {
520 return m_worldToModel;
521 }
522 QVector3D penPosition() const
523 {
524 return m_penPosition;
525 }
526 QQuaternion penOrientation() const
527 {
528 return m_penOrientation;
529 }
531 {
532 return m_havePenPos;
533 }
534
535 QVector3D probePosition() const
536 {
537 return m_probePosition;
538 }
539 QQuaternion probeOrientation() const
540 {
541 return m_probeOrientation;
542 }
544 {
545 return m_haveProbePos;
546 }
547
548signals:
549 void devicePoseChanged(const QMatrix4x4& deviceToWorld);
550 void penPoseChanged(const QVector3D& position, const QQuaternion& orientation);
551 void probePoseChanged(const QVector3D& position, const QQuaternion& orientation);
552 void penButtonPressed(const QVector3D& position, const QQuaternion& orientation);
555 void pivotCalibrationDone(const QVector3D& offset, float residualMm);
556 void pivotSampleCollected(int sampleCount, float angularSpanDeg);
558
559private slots:
560 void onPointReceived(int station, const QVector3D& position, const QQuaternion& orientation);
561 void onPenButtonPressedFromConn(int station, const QVector3D& position, const QQuaternion& orientation);
562
563private:
564 QMatrix4x4 buildDevicePose(const QVector3D& trackerPos,
565 const QQuaternion& trackerOri) const;
566 QMatrix4x4 buildHeadFrame() const;
567
568 int m_trackerStation = 2;
569 int m_penStation = 1;
570 int m_probeStation = 3;
571
572 QVector3D m_offsetTranslation;
573 QQuaternion m_offsetRotation;
574 QVector3D m_penTipOffset; // tip offset in sensor body frame (metres)
575 bool m_tipOffsetEnabled = false;
576 bool m_mirrorX = false; // negate X of incoming Polhemus positions
577 bool m_mirrorY = false; // negate Y of incoming Polhemus positions
578
579 PolhemusConnection* m_pConn = nullptr;
580 AcquiredPoints* m_pPoints = nullptr;
581
582 QVector3D m_penPosition;
583 QQuaternion m_penOrientation;
584 bool m_havePenPos = false;
585
586 QVector3D m_probePosition;
587 QQuaternion m_probeOrientation;
588 bool m_haveProbePos = false;
589
590 QMatrix4x4 m_deviceToWorld;
591 QMatrix4x4 m_headToWorld;
592 QMatrix4x4 m_headToDevice;
593 QMatrix4x4 m_worldToModel;
594 bool m_registrationValid = false;
595
596 // Pen fiducials (captured from stylus in Polhemus world frame)
597 QVector3D m_penFid[4]; // indexed by FiducialId (1..3)
598 bool m_hasPenFid[4] = {};
599
600 // Model fiducials (from FIFF / BEM, in MRI/surface-RAS frame)
601 QVector3D m_modelFid[4]; // indexed by FiducialId (1..3)
602 bool m_hasModelFid[4] = {};
603
604 // Vertex / CZ (top of head) for orientation validation
605 QVector3D m_modelVertex;
606 bool m_hasModelVertex = false;
607 QVector3D m_penVertex;
608 bool m_hasPenVertex = false;
609
610 // Pivot calibration state
611 PivotState m_pivotState = PivotState::Idle;
612 std::vector<QVector3D> m_pivotPositions;
613 std::vector<QQuaternion> m_pivotOrientations;
614 float m_pivotResidualMm = 0.0f;
615
616 // Optical path calibration state
617 std::vector<OpticalCalibSample> m_opticalCalibSamples;
618 QVector3D m_opticalAxisLocal; // optical axis direction in tracker body frame
619 QVector3D m_opticalCenterLocal; // optical center position in tracker body frame
620 float m_opticalCalibResidualMm = 0.0f;
621 float m_opticalCalibDepthSpreadMm = 0.0f;
622 bool m_opticalCalibValid = false;
623
624 // Directly captured objective center (in tracker-local frame, metres).
625 // When available, the solver uses this instead of the distance constraint.
626 QVector3D m_objectiveCenterLocal; // objective center in tracker body frame
627 bool m_hasObjectiveCenter = false;
628
629 // Fine adjustment — small correction rotation applied on top of the
630 // solved optical axis to compensate for residual misalignment between
631 // the tracked probe position and its appearance in the video.
632 QVector3D m_opticalAxisPreFineAdjust; // axis before fine-adjust (for undo)
633 float m_opticalFineAdjustDeg = 0.0f;
634 bool m_opticalFineAdjustApplied = false;
635
636 // Known tracker-to-objective distance (metres) — fallback constraint when
637 // no direct objective center capture is available.
638 float m_knownTrackerToObjectiveDist = 0.200f; // metres
639
640 bool solvePivotCalibration();
641};
642
643} // namespace UTILSLIB
644
645#endif // UTILS_POLHEMUS_COREGISTRATION_H
Shared digitised-point store for Polhemus digitizer sessions.
Polhemus digitizer connection abstraction.
Public umbrella header for UTILSLIB exposing the UTILSSHARED_EXPORT macro and build-info accessors.
#define UTILSSHARED_EXPORT
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
FiducialId
Identifier for the three cardinal fiducials.
In-memory store of all points captured during a session.
Polhemus digitizer connection (mock + serial-port backends).
~PolhemusCoregistration() override=default
void penPoseChanged(const QVector3D &position, const QQuaternion &orientation)
PolhemusConnection * connection() const
void penButtonPressed(const QVector3D &position, const QQuaternion &orientation)
void devicePoseChanged(const QMatrix4x4 &deviceToWorld)
void setKnownTrackerToObjectiveDistance(float metres)
Set the known perpendicular distance from tracker sensor to the objective center (metres).
void setModelVertex(const QVector3D &pos)
void pivotCalibrationDone(const QVector3D &offset, float residualMm)
void pivotStateChanged(PolhemusCoregistration::PivotState state)
bool captureOpticalCalibSample()
Record one calibration sample (current tracker pose + current pen position).
void setPenTipOffset(const QVector3D &offset)
PolhemusCoregistration(QObject *parent=nullptr)
void setAxisMirror(bool mirrorX, bool mirrorY)
Negate X and/or Y of incoming Polhemus positions.
void probePoseChanged(const QVector3D &position, const QQuaternion &orientation)
void pivotSampleCollected(int sampleCount, float angularSpanDeg)
QVector3D focusPoint
Stylus focus point in world frame (metres).
QVector3D trackerPos
Tracker position in world frame (metres).
QQuaternion trackerOri
Tracker orientation in world frame.