Computer Assisted Medical Intervention Tool Kit version 6.0
 
Loading...
Searching...
No Matches
TestTransformationManager.h
Go to the documentation of this file.
1/*****************************************************************************
2 * $CAMITK_LICENCE_BEGIN$
3 *
4 * CamiTK - Computer Assisted Medical Intervention ToolKit
5 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
6 *
7 * Visit http://camitk.imag.fr for more information
8 *
9 * This file is part of CamiTK.
10 *
11 * CamiTK is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License version 3
13 * only, as published by the Free Software Foundation.
14 *
15 * CamiTK is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License version 3 for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * version 3 along with CamiTK. If not, see <http://www.gnu.org/licenses/>.
22 *
23 * $CAMITK_LICENCE_END$
24 ****************************************************************************/
25
26#ifndef CAMITK_TESTTRANSFORMATIONMANAGER_H
27#define CAMITK_TESTTRANSFORMATIONMANAGER_H
28
29#include <QTest>
30
31#include <ranges>
32
33#include "FrameOfReference.h"
34#include "Transformation.h"
36#include "MeshComponent.h"
37#include "ImageComponent.h"
39#include "Application.h"
40#include "InteractiveViewer.h"
41#include "ExtensionManager.h"
42#include "Action.h"
43#include "Log.h"
44
45#include <vtkPolyData.h>
46#include <vtkSphereSource.h>
47#include <vtkCamera.h>
48#include <vtkMatrix4x4.h>
49#include <vtkTransform.h>
50
51namespace camitk {
52// For better test error messages
53char* toString(const FrameOfReference& frame);
54char* toString(const Transformation& tr);
55QString toString(const vtkSmartPointer<vtkMatrix4x4>& m);
56}
57
58using namespace camitk;
59
60class TestTransformationManager: public QObject {
61 Q_OBJECT
62
66 auto createFramesAndTransformation(QString frame1, QString frame2) {
67 std::shared_ptr<FrameOfReference> fr1 = TransformationManager::addFrameOfReference(frame1);
68 std::shared_ptr<FrameOfReference> fr2 = TransformationManager::addFrameOfReference(frame2);
69
70 std::shared_ptr<Transformation> tr = TransformationManager::addTransformation(fr1, fr2);
71 return std::make_tuple(fr1, fr2, tr);
72 }
73
77 bool isEqual(vtkSmartPointer<vtkMatrix4x4> m1, vtkSmartPointer<vtkMatrix4x4> m2) {
78 if (m1 == nullptr || m2 == nullptr) {
79 return false;
80 }
81 for (int i = 0; i < 16; ++i) {
82 if (m1->GetElement(i / 4, i % 4) != m2->GetElement(i / 4, i % 4)) {
83 return false;
84 }
85 }
86 return true;
87 }
88
92 MeshComponent* createSphere(float radius = 1.0) {
93 vtkSmartPointer<vtkSphereSource> sphereSource = vtkSmartPointer<vtkSphereSource>::New();
94 sphereSource->SetRadius(radius);
95 vtkSmartPointer<vtkPointSet> sphere(sphereSource->GetOutput());
96 return new MeshComponent(sphere, QString("Sphere %1").arg(radius));
97 }
98
99 ImageComponent* createImage(QString name) {
100 vtkNew<vtkImageData> imageData;
101 imageData->SetDimensions(5, 5, 5);
102 imageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
103 unsigned char* scalars = static_cast<unsigned char*>(imageData->GetScalarPointer());
104 memset(scalars, 255, 5 * 5 * 5);
105 return new ImageComponent(imageData, name);
106 }
107
108 int countDefaultIdentityToWorldTransformations() {
109 int nbDefault = 0;
112 nbDefault++;
113 }
114 }
115 return nbDefault;
116 }
117
118private slots:
119
121 void initTestCase() {
122 // show all log messages in the console
124 // no log messages will generate a QMessageBox
126 // no time stamp for reproducible log diff
128 }
129
131 void cleanupTestCase() {
132 // smart pointers take care of cleaning up
133 }
134
136 void cleanup() {
137 // Remove all components
139 for (auto* comp : components) {
140 // Do not ask the user if the component was not saved
141 comp->setModified(false);
142 Application::close(comp); // as there is no main window refresh won't be called here... (see below)
143 }
144
145 // Set all viewers' frames to nullptr (so their frames are unused)
146 for (Viewer* viewer : Application::getViewers()) {
147 // Forcing a refresh() to cleanup actors InteractiveGeometryViewer of closed components
148 // is required as not cleaning up the actorMap generates a segfault during application exit.
149 // Explanation:
150 // - during exit, the InteractiveGeometryViewerExtension is deleting all its viewers.
151 // - During the InteractiveGeometryViewer destruction, the cleaning up of the viewer's
152 // actorMap decreases the vtkSmartPointer number of references to the vtkOpenGLActor to zero
153 // - The vtkOpenGLActor has therefore to be deleted long after the data (vtkPointSet in Geometry)
154 // was deleted in the memory (this was done when the component was deleted).
155 // This generates a segfault.
156 // This is probably due to some reference count bug in VTK (e.g. the vtkSmartPointer<vtkPointSet> does
157 // not registered that it as some actors still being used), or due to our misusage of VTK (there is
158 // only one mapper in Geometry that is shared by multiple actors: surface, wireframe and points,
159 // see Geometry::getActor)
160 viewer->refresh();
161 }
162 // cleanup transformationManager
164 // qDebug() << "After cleanup, nb of frames " << TransformationManager::getFramesOfReference().size() << "\n";
165 }
166
168 void createFrame() {
169 // create world frame
170 TransformationManager::getWorldFrame(); // world frame is index 0 and color is white (tested in test worldFrame()...)
171
172 // Create Frame with a name
173 std::shared_ptr<FrameOfReference> fr = TransformationManager::addFrameOfReference("myFrame", "Test frame");
174 QVERIFY(fr != nullptr);
175 QVERIFY(fr->getName() == "myFrame");
176 QVERIFY(fr->getDescription() == "Test frame");
177 // Calling getIndex must initialize index to 1
178 QVERIFY(fr->getIndex() == 1);
179 // subsequent calls to getColor and getIndex should not modify the index
180 fr->getColor();
181 QVERIFY(fr->getIndex() == 1);
182
183 // Create a frame from a QVariant
185 QVariantMap{ {"frames", QVariantList{
186 QVariantMap{ {"anatomicalOrientation", QVariant()},
187 {"description", "Data frame for component 'Mesh'"},
188 {"dimensions", 3},
189 {"name", "Mesh (Data Frame)"},
190 {
191 "units", QVariantList({
192 "",
193 "",
194 "",
195 "",
196 ""})
197 },
198 {"uuid", QUuid("04ef78c0-8a8d-4d52-b888-b7ac2ef79c36")}}
199 }
200 }});
201
202 fr = TransformationManager::getFrameOfReferenceOwnership(QUuid("04ef78c0-8a8d-4d52-b888-b7ac2ef79c36"));
203
204 QCOMPARE(fr->getName(), "Mesh (Data Frame)");
205 QCOMPARE(fr->getDescription(), "Data frame for component 'Mesh'");
206 QCOMPARE(fr->getUuid(), QUuid("04ef78c0-8a8d-4d52-b888-b7ac2ef79c36"));
207 QCOMPARE(fr->getNumberOfDimensions(), 3);
208
209 // Calling getColor() must initialize index to the next available value (i.e., 2)
210 fr->getColor();
211 // subsequent calls to getIndex should not modify the index
212 QVERIFY(fr->getIndex() == 2);
213
214 // Create a copy of a FrameOfReference
215 std::shared_ptr<FrameOfReference> fr3 = TransformationManager::addFrameOfReference(*fr);
216 QVERIFY(fr3 != nullptr);
217 QVERIFY(fr3 != fr);
218 QVERIFY(fr3->getUuid() != fr->getUuid());
219 QVERIFY(fr3->getIndex() > fr->getIndex());
220 QVERIFY(fr3->getIndex() == 3);
221 QVERIFY(fr3->getColor().isValid());
222 QVERIFY(fr3->getColor() != fr->getColor());
223
224 // Set dimensions
225 QVERIFY(fr->getNumberOfDimensions() == 3);
226 fr->setNumberOfDimensions(4);
227 QVERIFY(fr->getNumberOfDimensions() == 4);
228
229 // Set Unit
230 fr->setUnit(0, "mm");
231 fr->setUnit(3, "s");
232 QVERIFY(fr->getUnit(0) == "mm");
233 QVERIFY(fr->getUnit(3) == "s");
234 QVERIFY(fr->getUnit(1) == "");
235
236 // Check that we cannot set a UUID to a frame which already has one
237 QVERIFY(!fr->setUuid(QUuid::createUuid()));
238
239 // Now set it to null, and try again (it should work)
240 QVariantMap frameBackup = fr->toVariant().toMap();
241 frameBackup["uuid"] = QUuid(); // Set to null
242 fr->fromVariant(frameBackup);
243 QVERIFY(fr->getUuid().isNull());
244 QVERIFY(fr->setUuid(QUuid::createUuid()));
245 QVERIFY(!fr->getUuid().isNull());
246
247 // Set Anatomical orientation
248 fr->setAnatomicalOrientation("LPS");
249 QVERIFY(fr->getAnatomicalOrientationLabel(0, true) == "L");
250 QVERIFY(fr->getAnatomicalOrientationLabel(1, true) == "P");
251 QVERIFY(fr->getAnatomicalOrientationLabel(2, true) == "S");
252
253 // Save state
254 QVariant frameVariant = fr->toVariant();
255
256 // Test other three-letter code
257 fr->setAnatomicalOrientation("RAI");
258 QVERIFY(fr->getAnatomicalOrientationLabel(0, false) == "L");
259 QVERIFY(fr->getAnatomicalOrientationLabel(1, false) == "P");
260 QVERIFY(fr->getAnatomicalOrientationLabel(2, false) == "S");
261
262 // Test custom label
263 fr->getAnatomicalOrientation().setMinLabel(0, "Xmin");
264 QVERIFY(fr->getAnatomicalOrientation().getMinLabel(0) == "Xmin");
265 QVERIFY(fr->getAnatomicalOrientationLabel(0, true) == "Xmin");
266
267 // Restore from variant
268 fr->fromVariant(frameVariant);
269 QVERIFY(fr->getAnatomicalOrientationLabel(0, true) == "L");
270
271 // Test '+' convention
272 fr->setAnatomicalOrientation("RAI+");
273 QVERIFY(fr->getAnatomicalOrientationLabel(0, false) == "R");
274
275 }
276
278 void createIdentityTransformation() {
279 auto [frame1, frame2, tr] = createFramesAndTransformation("Frame1", "Frame2");
280 QVERIFY(frame1 != nullptr && frame2 != nullptr);
281
282 QVERIFY(tr != nullptr);
283 QVERIFY(tr->getFrom() == frame1.get());
284 QVERIFY(tr->getTo() == frame2.get());
285
286 // Check that it is an identity transform
287 vtkMatrix4x4* matrix = tr->getMatrix();
288 QVERIFY(matrix != nullptr);
289 QVERIFY(matrix->IsIdentity());
290
291 // Try to create a transformation between a frame and itself (should fail)
292 std::shared_ptr<Transformation> invalidTr = TransformationManager::addTransformation(frame1, frame1);
293 QVERIFY(invalidTr == nullptr);
294 QVERIFY(!TransformationManager::getTransformation(frame1.get(), frame1.get()));
295
297 }
298
300 void createSpecificTransformation() {
301 auto [frame1, frame2, tr] = createFramesAndTransformation("Frame3", "Frame4");
302 auto m = vtkSmartPointer<vtkMatrix4x4>::New();
303 m->SetElement(1, 1, 2.0); // Scaling axis X
304 m->SetElement(1, 3, 12.0); // Translation axis Y
305 m->SetElement(3, 3, -5.0); // Translation axis Z
307 vtkMatrix4x4* m2 = tr->getTransform()->GetMatrix();
308 // Default is identity
309 QVERIFY(m2->GetElement(2, 2) == 1 && m2->GetElement(3, 1) == 0);
310 // Check values that were set manually
311 QCOMPARE(m2->GetElement(1, 1), 2.0);
312 QCOMPARE(m2->GetElement(1, 3), 12.0);
313 QCOMPARE(m2->GetElement(3, 3), -5.0);
314
315 // Set name and description
316 tr->setName("New name");
317 QCOMPARE(tr->getName(), "New name");
318 tr->setDescription("New description");
319 QCOMPARE(tr->getDescription(), "New description");
320
321 // Check that this transformation has no sources
322 QVERIFY(!TransformationManager::hasSources(tr.get()));
323 }
324
325
327 void invertTransformation() {
328 int nbTrBefore = TransformationManager::getTransformations().size();
329
330 // Create two frames and a transformation
331 auto [frame1, frame2, tr] = createFramesAndTransformation("Frame5", "Frame6");
332 QVERIFY(tr != nullptr);
333
334 int nbTrAfter = TransformationManager::getTransformations().size();
335
336 // Check that there are two additional transformations in the TransformationManager: the new transform and its inverse
337 QVERIFY(nbTrBefore + 2 == nbTrAfter);
338
339 // Check that it is identity
340 QVERIFY(tr->getMatrix()->IsIdentity());
341
342 // Check that the inverse was added too
343 QVERIFY(TransformationManager::getTransformation(frame2.get(), frame1.get()) != nullptr);
344
345 // Get the inverse transformation
346 Transformation* tr2 = TransformationManager::getTransformation(frame2.get(), frame1.get());
347 QVERIFY(tr2 != nullptr);
348
349 // Check it is identity
350 QVERIFY(tr2->getMatrix()->IsIdentity());
351
352 // Check that this transformation has a source
354
355 // Check that it has one source
356 QVERIFY(TransformationManager::getSources(tr2).size() == 1);
357
358 // Check that it is present in the transformations computed from tr
359 //QVERIFY(TransformationManager::getTransformationsComputedFrom(tr.get()).contains(tr2));
360
361 // Now add a scaling
362 tr->getTransform()->Scale(1, 1, 4);
363 // qDebug() << "Direct matrix " << toString(tr->getMatrix());
364 // qDebug() << "Inverse matrix " << toString(tr2->getMatrix());
365 QCOMPARE(tr2->getMatrix()->GetElement(2, 2), 0.25);
366
367 // Set a translation using setTransform and check if the inverse is updated
368 auto vtkTr = vtkSmartPointer<vtkTransform>::New();
369 vtkTr->Identity();
370 vtkTr->Translate(10.0, 0, 0);
372 QCOMPARE(tr2->getMatrix()->GetElement(0, 3), -10.0);
373 // qDebug() << toString(tr2->getMatrix());
374
375 // Set a translation using setMatrix and check if the inverse is updated
376 auto vtkMat = vtkSmartPointer<vtkMatrix4x4>::New();
377 vtkMat->Identity();
378 vtkMat->SetElement(1, 3, 123.0);
379 TransformationManager::updateTransformation(tr.get(), vtkMat.Get());
380 QCOMPARE(tr2->getMatrix()->GetElement(1, 3), -123.0);
381 }
382
384 void combineTransformations() {
385 // Add four frames and two transformations f1->f2, f3->f4, then add a Transformation f2->f3 and get f1->f4 transformation
386 auto [frame1, frame2, tr1] = createFramesAndTransformation("Frame7", "Frame8");
387 QVERIFY(tr1 != nullptr);
388 tr1->getTransform()->Translate(1, 2, 3);
389 auto [frame3, frame4, tr3] = createFramesAndTransformation("Frame9", "Frame10");
390 tr3->getTransform()->Translate(1, -5, 30);
391 Transformation* tr2 = TransformationManager::addTransformation(frame2, frame3).get();
392 tr2->getTransform()->Translate(8, 3, -13);
393 // Check that the path is found
394 QVERIFY(TransformationManager::hasPath(frame1.get(), frame4.get()));
395 // Check that we get the Transformation with combined translations
396 Transformation* tr4 = TransformationManager::getTransformation(frame1.get(), frame4.get());
397 QVERIFY(tr4 != nullptr);
398 QCOMPARE(tr4->getTransform()->GetNumberOfConcatenatedTransforms(), 3);
399 QCOMPARE(tr4->getMatrix()->GetElement(0, 3), 10.0);
400 QCOMPARE(tr4->getMatrix()->GetElement(1, 3), 0.0);
401 QCOMPARE(tr4->getMatrix()->GetElement(2, 3), 20.0);
402
403 // Now update tr2, check that tr4 is updated
404 // Update the Transform
405 tr2->getTransform()->Translate(-8, 0, 0); // Becomes a translation (0, 3, -13)
406 QCOMPARE(tr4->getMatrix()->GetElement(0, 3), 2.0); // Combined translation should be 2
407
408 // Update the Matrix of the Transform
409 auto vtkMat = vtkSmartPointer<vtkMatrix4x4>::New();
410 vtkMat->Identity();
411 vtkMat->SetElement(0, 3, 98.0);
413 QCOMPARE(tr4->getMatrix()->GetElement(0, 3), 100.0); // Combined translation should be 100
414 }
415
417 void findTransformationsPath() {
418 // Create 8 frames and transformations fr1->fr2<-fr3<-fr4 fr5->fr6->fr2 fr7->fr8
419 auto [frame1, frame2, tr1_2] = createFramesAndTransformation("Frame11", "Frame12");
420 auto [frame4, frame3, tr4_3] = createFramesAndTransformation("Frame14", "Frame13");
421 auto [frame5, frame6, tr5_6] = createFramesAndTransformation("Frame15", "Frame16");
422 auto [frame7, frame8, tr7_8] = createFramesAndTransformation("Frame17", "Frame18");
423
424 // Find a non-existing tranformation
425 QVERIFY(!TransformationManager::hasPath(frame1.get(), frame4.get()));
426 QVERIFY(!TransformationManager::hasPath(frame8.get(), frame2.get()));
427
428 // Try to find an impossible transformation
429 QVERIFY(!TransformationManager::hasPath(frame1.get(), nullptr));
430 QVERIFY(!TransformationManager::hasPath(nullptr, frame1.get()));
431 QVERIFY(TransformationManager::hasPath(frame1.get(), frame1.get()));
432
433 // Try to compute a path, it should fail
434 QVERIFY(TransformationManager::getTransformation(frame8.get(), frame2.get()) == nullptr);
435 // Try again to check if there is a path (to detect possible side effects of getTransformation)
436 QVERIFY(!TransformationManager::hasPath(frame8.get(), frame2.get()));
437
438 Transformation* tr3_2 = TransformationManager::addTransformation(frame3, frame2).get();
439 Transformation* tr6_2 = TransformationManager::addTransformation(frame6, frame2).get();
440
441 // Find an existing Transformation
442 Transformation* wantedTransform = TransformationManager::getTransformation(frame1.get(), frame2.get());
443 QVERIFY(wantedTransform != nullptr);
444 wantedTransform = TransformationManager::getTransformation(frame6.get(), frame2.get());
445 QVERIFY(wantedTransform != nullptr);
446
447 // Get a complex path with inversions fr5->fr4 and check sources
448 Transformation* tr5_4 = TransformationManager::getTransformation(frame5.get(), frame4.get());
449 QVERIFY(tr5_4 != nullptr);
450
451 // Try to get a path which does not exist fr1->fr8
452 Transformation* tr1_8 = TransformationManager::getTransformation(frame1.get(), frame8.get());
453 QVERIFY(tr1_8 == nullptr);
454
455 // Multipath should fail: adding a path that creates a circle fr5->fr1
456 Transformation* tr5_1 = TransformationManager::addTransformation(frame5, frame1).get();
457 QVERIFY(tr5_1 == nullptr);
458 }
459
463 void worldFrame() {
464 int nbTrBefore = TransformationManager::getTransformations().size();
465 QVERIFY(nbTrBefore == 0);
466 int nbFrBefore = TransformationManager::getFramesOfReference().size();
467 QVERIFY(nbFrBefore == 0);
468
469 // check world frame is created with index 0 and white color
471 QVERIFY(TransformationManager::getFramesOfReference().size() == 1);
472 QVERIFY(TransformationManager::getFramesOfReference().at(0)->getIndex() == 0);
473 QVERIFY(QVariant(TransformationManager::getFramesOfReference().at(0)->getColor()).toString() == "#ffffff");
475
476 // Create a Component
477 auto component = std::make_unique<MeshComponent>(vtkSmartPointer<vtkPolyData>::New(), "EmptyMeshComponent");
478 QVERIFY(component->getFrame() != nullptr);
479
480 // Create a second one
481 auto component2 = std::make_unique<MeshComponent>(vtkSmartPointer<vtkPolyData>::New(), "EmptyMeshComponent2");
482 QVERIFY(component2->getFrame() != nullptr);
483
484 // Should have added 2 frames
485 int nbFrAfter = TransformationManager::getFramesOfReference().size();
486 QCOMPARE(nbFrBefore + 2, nbFrAfter);
487
488 // This should add a default identity transform from both components' frames to world frame
489 TransformationManager::ensurePathToWorld(component->getFrame());
490 TransformationManager::ensurePathToWorld(component2->getFrame());
491 int nbTrAfter = TransformationManager::getTransformations().size();
492 // 2 transformations (default identity to world frame) and their 2 inverse added (4 in total)
493 QCOMPARE(nbTrBefore + 4, nbTrAfter);
494
495 // Check if the default transformation is identity
497 QVERIFY(trWorldFrame != nullptr);
498 QVERIFY(trWorldFrame->getMatrix()->IsIdentity());
499
500 // Try to replace it and check if the default identity was replaced
501 vtkSmartPointer<vtkTransform> vtkTr2_1 = vtkSmartPointer<vtkTransform>::New();
502 vtkTr2_1->Translate(1, 2, 3);
503 std::shared_ptr<Transformation> tr2_1 = TransformationManager::addTransformation(component2->getFrame(), component->getFrame(), vtkTr2_1);
504 // If it was replaced correctly, this should not be null
505 QVERIFY(tr2_1 != nullptr);
506 }
507
509 void viewerFrame() {
510
511 // Create a Component
512 auto component = std::make_unique<MeshComponent>(vtkSmartPointer<vtkPolyData>::New(), "EmptyMeshComponent");
513 QVERIFY(component->getFrame() != nullptr);
514
515 // Create a frame for the viewer
516 std::shared_ptr<FrameOfReference> viewerFr = TransformationManager::addFrameOfReference("Viewer", "");
517 QVERIFY(viewerFr != nullptr);
518
519 // This will set an identity transformation between worldFrame and the component's main frame
520 // compFrame ---default---> worldFrame
521 TransformationManager::ensurePathToWorld(component->getFrame());
522
523 // There is no transformation path from viewerFr to worldFrame, a default one (identity) should be generated
524 // viewerFr ---default---> worldFrame
528 QVERIFY(viewer2world->getMatrix()->IsIdentity());
530
531 // check that we cannot get ownership of a default identity to world
533 QVERIFY(TransformationManager::getTransformationOwnership(viewer2world->getUuid()) == nullptr);
534 QVERIFY(TransformationManager::getTransformationOwnership(viewer2world) == nullptr);
535
536 // Add a Transformation, check that it was added (replacing the default ones)
537 // viewerFr ------> compFrame
538 auto comp2viewerTransform = vtkSmartPointer<vtkTransform>::New();
539 comp2viewerTransform->Translate(1, 2, 3);
540 Transformation* comp2viewerAdded = TransformationManager::addTransformation(viewerFr.get(), component->getFrame(), comp2viewerTransform).get();
541 QVERIFY(comp2viewerAdded != nullptr);
542
543 // Check that we get it when we ask for it again
544 Transformation* viewer2comp = TransformationManager::getTransformation(viewerFr.get(), component->getFrame());
545 QCOMPARE(viewer2comp->getMatrix()->GetElement(0, 3), 1);
546 QCOMPARE(viewer2comp->getMatrix()->GetElement(1, 3), 2);
547 QCOMPARE(viewer2comp->getMatrix()->GetElement(2, 3), 3);
548 // And its inverse works too
549 Transformation* comp2viewer = TransformationManager::getTransformation(component->getFrame(), viewerFr.get());
550 QCOMPARE(comp2viewer->getMatrix()->GetElement(0, 3), -1);
551 QCOMPARE(comp2viewer->getMatrix()->GetElement(1, 3), -2);
552 QCOMPARE(comp2viewer->getMatrix()->GetElement(2, 3), -3);
553
554 // The default transformations to world were removed
557
558 // Ask for a transformation from Component to World (recreating compFrame ---default---> worldFrame)
559 TransformationManager::ensurePathToWorld(component->getFrame());
560 QVERIFY(TransformationManager::getTransformation(component->getFrame(), TransformationManager::getWorldFrame()) != nullptr);
561
562 // Ask for the composite viewerFr------>worldFrame = viewerFrame --> compFrame --> worldFrame
564 QVERIFY(viewer2world != nullptr);
565 QCOMPARE(viewer2world->getMatrix()->GetElement(0, 3), 1);
566 QCOMPARE(viewer2world->getMatrix()->GetElement(1, 3), 2);
567 QCOMPARE(viewer2world->getMatrix()->GetElement(2, 3), 3);
568
569 }
570
572 void checkTransformationToWorld() {
573
574 // Ensure World Frame exists
576 QVERIFY(worldFrame != nullptr);
577
578 // Create a basic sphere Component
579 Component* comp = createSphere(2.0);
580
581 // Check it has a valid frame
582 const FrameOfReference* sphereFrame = comp->getFrame();
583 QVERIFY(sphereFrame != nullptr);
584
585 // Check there is no transformation yet to the world
586 QVERIFY(TransformationManager::getTransformationOwnership(sphereFrame, worldFrame) == nullptr);
587
588 // Create a default one
591
592 // Check it exists, and it is default
593 QVERIFY(sphere2world != nullptr);
594 QVERIFY(TransformationManager::isDefaultIdentityToWorld(sphere2world) == true);
595
596 // Check its inverse was also created and is also in the default identity to world set
598 QVERIFY(TransformationManager::getSources(TransformationManager::getTransformation(worldFrame, sphereFrame)).size() == 1);
599
600 // Now try to create a translation
601 auto vtkSphere2worldTranslation = vtkSmartPointer<vtkTransform>::New();
602 vtkSphere2worldTranslation->Translate(1, 2, 3);
603 auto sphere2worldTranslation = TransformationManager::addTransformation(sphereFrame, TransformationManager::getWorldFrame(), vtkSphere2worldTranslation).get();
604
605 // Check it was created
606 QVERIFY(sphere2worldTranslation != nullptr);
607
608 // Check it is still a Translation
609 QCOMPARE(sphere2worldTranslation->getMatrix()->GetElement(0, 3), 1);
610
611 // Check if it is default
612 QVERIFY(TransformationManager::isDefaultIdentityToWorld(sphere2worldTranslation) == false);
613
614 // Check if its inverse exists and is default
615 QVERIFY(TransformationManager::getTransformation(worldFrame, sphereFrame) != nullptr);
617
618 // Check its inverse is a Translation
619 QCOMPARE(TransformationManager::getTransformation(worldFrame, sphereFrame)->getMatrix()->GetElement(0, 3), -1);
620
621 }
622
624 void checkRemoveTransformation() {
625 auto [frameA, frameB, trA_B] = createFramesAndTransformation("FrameA", "FrameB");
626 auto [frameD, frameC, trD_C] = createFramesAndTransformation("FrameD", "FrameC");
627 auto [frameF, frameE, trF_E] = createFramesAndTransformation("FrameF", "FrameE");
632
633 // Create composite not using trE_A
634 QVERIFY(TransformationManager::getTransformation(frameB.get(), frameC.get()) != nullptr);
635
636 // Creating trE_A
637 auto matrixTransl10 = vtkMatrix4x4::New();
638 matrixTransl10->SetElement(0, 3, 10.0);
639 auto trE_A = TransformationManager::addTransformation(frameE, frameA, matrixTransl10);
640
641 // Create composite transformations using trE_A
642 QVERIFY(TransformationManager::getTransformation(frameF.get(), frameA.get()) != nullptr);
643 QVERIFY(TransformationManager::getTransformation(frameF.get(), frameB.get()) != nullptr);
644 QVERIFY(TransformationManager::getTransformation(frameF.get(), frameD.get()) != nullptr);
645 QVERIFY(TransformationManager::getTransformation(frameB.get(), frameE.get()) != nullptr);
646 // This one does not need trE_A, but will be computed with it
647 QVERIFY(TransformationManager::getTransformation(frameA.get(), frameD.get()) != nullptr);
648 // for (auto myTr : TransformationManager::getSources(TransformationManager::getTransformation(frameA.get(), frameD.get()))) {
649 // qDebug() << myTr->getName();
650 // }
652 // Check that all dependants have been removed
653 QVERIFY(TransformationManager::getTransformationOwnership(frameF.get(), frameA.get()) == nullptr);
654 QVERIFY(TransformationManager::getTransformationOwnership(frameF.get(), frameB.get()) == nullptr);
655 QVERIFY(TransformationManager::getTransformationOwnership(frameF.get(), frameD.get()) == nullptr);
656 QVERIFY(TransformationManager::getTransformationOwnership(frameB.get(), frameE.get()) == nullptr);
657
658 // Check they cannot be recomputed (there is no path)
659 QVERIFY(TransformationManager::getTransformation(frameF.get(), frameA.get()) == nullptr);
660 QVERIFY(TransformationManager::getTransformation(frameF.get(), frameB.get()) == nullptr);
661 QVERIFY(TransformationManager::getTransformation(frameF.get(), frameD.get()) == nullptr);
662 QVERIFY(TransformationManager::getTransformation(frameB.get(), frameE.get()) == nullptr);
663
664 // These ones should still be there or recomputable
665 QVERIFY(TransformationManager::getTransformation(frameB.get(), frameC.get()) != nullptr);
666 QVERIFY(TransformationManager::getTransformationOwnership(frameA.get(), frameD.get()) == nullptr);
667 QVERIFY(TransformationManager::getTransformation(frameA.get(), frameD.get()) != nullptr);
668
669 }
670
671
673 void checkUpdateTransformation() {
674 // Create multiple transformations A ---> B --default--> World <--default-- C <--- D
675 // ^
676 // |
677 // +------default-- E <--- F
678 auto [frameA, frameB, trA_B] = createFramesAndTransformation("FrameA", "FrameB");
679 auto [frameD, frameC, trD_C] = createFramesAndTransformation("FrameD", "FrameC");
680 auto [frameF, frameE, trF_E] = createFramesAndTransformation("FrameF", "FrameE");
683 QUuid trB_World_Uuid = trB_World->getUuid();
688
689 //-- 1. Try to update the default transformation B->World
690
691 // Check that they are all default
695 // Check that we cannot get shared ptr of a default identity to world
697
698 // Add a non-default
699 vtkMatrix4x4* matrixTransl10 = vtkMatrix4x4::New();
700 matrixTransl10->SetElement(0, 3, 10.0);
702 // New state:
703 // A ---> B --matrixTransl10--> World <--default-- C <--- D
704 // ^
705 // |
706 // +------default-- E <--- F
707
708 // Check it is not default anymore and a new transformation was created
710 QVERIFY(trB_World_Uuid != nonDefault_trB_World->getUuid());
711 QVERIFY(!TransformationManager::isDefaultIdentityToWorld(nonDefault_trB_World));
712
713 // Check World->B is not default either, and World->B is the inverse of the new transformation
715 QVERIFY(trWorld_B != nullptr);
717 QCOMPARE(trWorld_B->getMatrix()->GetElement(0, 3), -10.0);
718
719 //-- 2. Try to update the inverse default World->C
720
721 // Check the inverse is default and has one source
724
725 // Add a non-default inverse
726 std::shared_ptr<Transformation> trWorld_C = TransformationManager::addTransformation(TransformationManager::getWorldFrame(), frameC.get(), matrixTransl10);
727 // New state:
728 // A ---> B --[matrixTransl10]--> World <--[matrixTransl10^{-1}]-- C <--- D
729 // ^
730 // |
731 // +------default-- E <--- F
732
733 // We expect that C-> World is not default anymore, World->C is not default, and C->World is the inverse of the updated transformation
734 QVERIFY(trWorld_C != nullptr);
735 QVERIFY(trWorld_C.get() == TransformationManager::getTransformation(TransformationManager::getWorldFrame(), frameC.get()));
738 QCOMPARE(TransformationManager::getTransformation(frameC.get(), TransformationManager::getWorldFrame())->getMatrix()->GetElement(0, 3), -10.0);
739
740 //-- 3. get the Transformation from A to F (e.g. composite transformation) then try to update it
741
742 // Get the composite transformation
743 Transformation* trA_F = TransformationManager::getTransformation(frameA.get(), frameF.get());
744 // New state:
745 // A ---> B --[matrixTransl10]--> World <--[matrixTransl10^{-1}]-- C <--- D
746 // ^
747 // |
748 // +------default-- E <--- F
749 // trA_F = composition of trA_B, trB_World, trE_World^{-1}, trF_E^{-1}
750 QVERIFY(trA_F != nullptr);
751 // Should be A-->B-->World-->E-->F so 4 transformations
752 //qDebug().noquote() << TransformationManager::toString();
753 QCOMPARE(TransformationManager::getSources(trA_F).size(), 4);
756
757 // Set values in the matriw
758 vtkMatrix4x4* matrixTransl15 = vtkMatrix4x4::New();
759 matrixTransl15->SetElement(0, 3, 15.0);
760 TransformationManager::addTransformation(frameA, frameF, matrixTransl15);
761 trA_F = TransformationManager::getTransformation(frameA.get(), frameF.get());
762 // New state:
763 // A ---> B --[matrixTransl10}--> World <--[matrixTransl10^{-1}]-- C <--- D
764 // |
765 // |
766 // |
767 // +-----[matrixTransl15]-----+---> F --> E
768 //qDebug().noquote() << TransformationManager::toString();
769
770 // We expect A to F to have no source
771 QVERIFY(trA_F != nullptr);
774 QCOMPARE(TransformationManager::getSources(trA_F).size(), 0);
775
776 // the default E->World that was used to compose A to C should have been removed
777 // re-create it
779 //qDebug().noquote() << TransformationManager::toString();
780 QVERIFY(trE_World != nullptr);
783 QCOMPARE(TransformationManager::getSources(trE_World).size(), 4); // It is now a composite of 4 transformations
784
785 Transformation* trE_D = TransformationManager::getTransformation(frameE.get(), frameD.get());
786 QVERIFY(trE_D != nullptr);
787 QCOMPARE(TransformationManager::getSources(trE_D).size(), 6); // 6 because all sources of E->World, World->C, C->D are included
788 // for (auto ti : TransformationManager::getSources(trE_D)) {
789 // qDebug() << ti->getName() << ", ";
790 // }
791
793 QCOMPARE(TransformationManager::getSources(trF_World).size(), 3); // F -> A -> B -> World
794
795 // Should be a translation of -5 (E -- id --> F -- -15 --> A -- id --> B -- +10 --> World)
796 QCOMPARE(trE_World->getMatrix()->GetElement(0, 3), -5.0);
797
798 // Replace matrix translation 15 by matrix translation 10 in A --> F
799 TransformationManager::updateTransformation(frameA.get(), frameF.get(), matrixTransl10);
800
801 // The E->World transformation should now be identity (automatically updated because it depends on trA_F)
802 QCOMPARE(trE_World->getMatrix()->GetElement(0, 3), 0.0);
803
804 //-- 4. get the Transformation from A to D, get its inverse, update its inverse
805 Transformation* trA_D = TransformationManager::getTransformation(frameA.get(), frameD.get());
806 QVERIFY(trA_D != nullptr);
807 Transformation* trD_A = TransformationManager::getTransformation(frameD.get(), frameA.get());
808 QVERIFY(trD_A != nullptr);
809 // This should fail, there is a non-default path already from D to A
810 QVERIFY(TransformationManager::updateTransformation(trD_A, matrixTransl15) == false);
811
812 //-- 5. Add trG_H but no trH_World and ask for trA_G which should return nullptr
813 auto [frameG, frameH, trG_H] = createFramesAndTransformation("FrameG", "FrameH");
814 QVERIFY(TransformationManager::getTransformation(frameA.get(), frameG.get()) == nullptr);
815 }
816
818 void checkViewerFrame() {
819 // Load all the viewer extensions in order to use the InteractiveGeometryViewer factory (Application::getNewViewer)
821
822 // Create a new custom Geometry Viewer
823 InteractiveViewer* viewer = dynamic_cast<InteractiveViewer*>(Application::getNewViewer("3D Viewer Test", "InteractiveGeometryViewer")); // will be deleted when the InteractiveGeometryViewer extension is deleted (during exit)
824 QVERIFY(viewer != nullptr);
825
826 // Initialize the widget and show the widget in order to build the widget (and its frame)
827 // and force the new viewer frame to be truly visible so that calling setFrame(..)
828 // (that itself calls refresh(..)) really updates of the actor transformation, the aim of this test
829 viewer->getWidget();
830
831 // Register the viewer so that it can be used in comp->setVisibility(...)
832 // and be automatically called during Application::refresh()
834
835 // Create a basic sphere Component
836 Component* comp = createSphere(2.0); // will be deleted in cleanup()
837 QVERIFY(comp != nullptr);
838 // as MeshComponent does not create a default identity to world itself, its frame is not linked to world before visualization
840
841 // Ensure our component is visible in the custom new viewer
842 comp->setVisibility(viewer->getName(), true);
843 viewer->getWidget()->show();
844 viewer->refresh();
845 // refresh must have ensure a path from the viewer and component to world
847
848 // Set its main frame as worldFrame to get: dataFrame ---(Identity)---> MainFrame ---(identity)---> WorldFrame
851
852 // Create a viewerFrame
853 std::shared_ptr<FrameOfReference> viewerFrame = TransformationManager::addFrameOfReference("Viewer Frame", "");
854 QVERIFY(viewerFrame != nullptr);
855 // there is no path yet
857
858 // Set the viewer frame
859 viewer->setFrame(viewerFrame);
860 // now there should be a path (which is a default)
863
864 // We create invertAxes transformation to have: MainFrame[=worldFrame] ---(invertAxes)---> viewerFrame
865 vtkSmartPointer<vtkTransform> invertAxes = vtkSmartPointer<vtkTransform>::New();
866 invertAxes->Scale(1, -1, 1); // Reverse axis Y
867 invertAxes->Translate(1, 2, 3);
868 Transformation* trMain2Vtk = TransformationManager::addTransformation(comp->getFrame(), viewerFrame.get(), invertAxes).get();
869 QVERIFY(trMain2Vtk != nullptr);
870
871 // Set anatomical orientation information in the viewerFrame
872 viewerFrame->setAnatomicalOrientation("RAI");
873 viewer->refresh();
874
875 // Put the vtkActor from the component in the viewer
876 // -> Check that there is at least the surface actor
877 vtkSmartPointer<vtkActor> actor = comp->getActor(InterfaceGeometry::Surface);
878 QVERIFY(actor != nullptr);
879
880 // Check that the User transformation is correctly set in the actor
881 vtkLinearTransform* userTransform = actor->GetUserTransform();
882 QVERIFY(userTransform != nullptr);
883 // This should be: dataFrame ------> ViewerFrame
884 // = dataFrame ---(identity)---> MainFrame ---(invertAxes)---> ViewerFrame
885 // = invertAxes
886 QVERIFY(isEqual(userTransform->GetMatrix(), invertAxes->GetMatrix()));
887
888 // set different orientations (XY, XZ, YZ, Axial, Coronal, Sagittal) and check all the resulting camera transformations
889 //QFAIL("What happens when changing camera orientation is not checked");
890 // Change the viewer's frame to the Component's frame
892 // Check that the UserView transformation is correctly set in the RendererWidget's camera to Identity
893 userTransform = actor->GetUserTransform();
894 QVERIFY(userTransform == nullptr || userTransform->GetMatrix()->IsIdentity());
895 }
896
898 void checkResetFrame() {
899 // -- 1. create mesh m image i
900 MeshComponent* m = createSphere(3.14);
901 ImageComponent* i = createImage("i");
902
903 // -- 2. store all frames and transformation shared ptr
904 std::shared_ptr<FrameOfReference> frM, frIMain, frIData, frIArbitrary;
905 std::shared_ptr<Transformation> trIData_IMain, trIArbitrary_IData;
910 trIData_IMain = TransformationManager::getTransformationOwnership(frIData.get(), frIMain.get());
911 i->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(1.0, 0.0, 0.0));
913 QVERIFY(frM != nullptr && frIMain != nullptr && frIData != nullptr && frIArbitrary != nullptr && trIData_IMain != nullptr && trIArbitrary_IData != nullptr);
914
915 // -- 3. resetFrame
916 m->resetFrame();
917 QVERIFY(m->getFrame() != frM.get());
918
919 i->resetFrame();
920 QVERIFY(i->getFrame() != frIMain.get());
921 QVERIFY(i->getDataFrame() != frIData.get());
922 QVERIFY(i->getArbitrarySlices()->getArbitraryFrame() != frIArbitrary.get());
923 std::shared_ptr<Transformation> new_trIData_IMain = TransformationManager::getTransformationOwnership(i->getDataFrame(), i->getFrame());
924 QVERIFY(new_trIData_IMain != nullptr);
925 QVERIFY(new_trIData_IMain != trIData_IMain);
926 std::shared_ptr<Transformation> new_trIArbitrary_IData = TransformationManager::getTransformationOwnership(i->getArbitrarySlices()->getArbitraryTransformation());
927 QVERIFY(new_trIArbitrary_IData != nullptr);
928 QVERIFY(new_trIArbitrary_IData != trIArbitrary_IData);
929 QVERIFY(isEqual(new_trIArbitrary_IData->getMatrix(), trIArbitrary_IData->getMatrix()));
930 }
931
933 void checkSetFrame() {
934 // -- 1. create two meshes m1, m2 and two images i1, i2
935 MeshComponent* m1 = createSphere(3.14);
936 ImageComponent* i1 = createImage("i1");
937 MeshComponent* m2 = createSphere(2.71);
938 ImageComponent* i2 = createImage("i2");
939
940 // -- 2. store all frames and transformation shared ptr
941 std::shared_ptr<FrameOfReference> frM1, frI1Main, frI1Data, frI1Arbitrary;
942 std::shared_ptr<Transformation> trI1Data_I1Main, trI1Arbitrary_I1Data;
947 trI1Data_I1Main = TransformationManager::getTransformationOwnership(frI1Data.get(), frI1Main.get());
948 i1->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(1.0, 0.0, 0.0));
950
951 // -- 3.setFrame
952 // Set mesh 1 frame from mesh 2
954 QVERIFY(m1->getFrame() != frM1.get());
955 QVERIFY(m1->getFrame() == m2->getFrame());
956
957 // Set mesh 1 frame from image 1
959 QVERIFY(m1->getFrame() != frM1.get());
960 QVERIFY(m1->getFrame() != m2->getFrame());
961 QVERIFY(m1->getFrame() == frI1Main.get());
962
963 // Set image 1 frame from mesh 2
965 QVERIFY(i1->getFrame() != frI1Main.get());
966 QVERIFY(i1->getFrame() == m2->getFrame());
967 QVERIFY(i1->getDataFrame() == frI1Data.get());
968 QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() == frI1Arbitrary.get());
969 std::shared_ptr<Transformation> new_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
970 QVERIFY(new_trI1Data_I1Main != trI1Data_I1Main);
971 // Check the main Transformation
972 QVERIFY(i1->getMainTransformation()->getFrom() == i1->getDataFrame());
973 QVERIFY(i1->getMainTransformation()->getTo() == m2->getFrame());
974 // Check the Arbitrary Transformation
975 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation() == trI1Arbitrary_I1Data.get());
976 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == frI1Arbitrary.get());
978
979 // Setimage 1 frame from image 2
981 // Only main frame and main transformation should be modified
982 QVERIFY(i1->getFrame() != frI1Main.get());
983 QVERIFY(i1->getFrame() == i2->getFrame());
984 QVERIFY(i1->getDataFrame() == frI1Data.get());
985 QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() == frI1Arbitrary.get());
986 // Main Transformation
987 std::shared_ptr<Transformation> new2_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
988 QVERIFY(new2_trI1Data_I1Main != nullptr);
989 QVERIFY(new2_trI1Data_I1Main != trI1Data_I1Main);
990 QVERIFY(new2_trI1Data_I1Main != new_trI1Data_I1Main);
991 QVERIFY(new2_trI1Data_I1Main.get() == i1->getMainTransformation());
992 // Arbitrary Transformation
993 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation() == trI1Arbitrary_I1Data.get());
994 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == frI1Arbitrary.get());
995 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getTo() == frI1Data.get());
996 }
997
999 void checkSetFrameFrom() {
1000 // -- 1. create two meshes m1, m2 and two images i1, i2
1001 MeshComponent* m1 = createSphere(3.14);
1002 ImageComponent* i1 = createImage("i1");
1003 MeshComponent* m2 = createSphere(2.71);
1004 ImageComponent* i2 = createImage("i2");
1005
1006 // -- 2. store all frames and transformation shared ptr
1007 std::shared_ptr<FrameOfReference> frM1, frI1Main, frI1Data, frI1Arbitrary;
1008 std::shared_ptr<Transformation> trI1Data_I1Main, trI1Arbitrary_I1Data;
1013 trI1Data_I1Main = TransformationManager::getTransformationOwnership(frI1Data.get(), frI1Main.get());
1014 i1->getArbitrarySlices()->setPropertyValue("Rotation", QVector3D(1.0, 0.0, 0.0));
1016
1017 // -- 3. setFrameFrom
1018 // Set frame of m1 from m2
1019 m1->setFrameFrom(m2);
1020 QVERIFY(m1->getFrame() != frM1.get());
1021 QVERIFY(m1->getFrame() == m2->getFrame());
1022
1023 // Set frame of m1 from i1
1024 m1->setFrameFrom(i1);
1025 QVERIFY(m1->getFrame() != frM1.get());
1026 QVERIFY(m1->getFrame() != m2->getFrame());
1027 QVERIFY(m1->getFrame() == frI1Main.get());
1028
1029 // Set frame of image i1 from mesh m2
1030 i1->setFrameFrom(m2);
1031 // main frame should have changed
1032 QVERIFY(i1->getFrame() != frI1Main.get());
1033 QVERIFY(i1->getFrame() == m2->getFrame());
1034 // data and arbitrary frames should be identical
1035 QVERIFY(i1->getDataFrame() == frI1Data.get());
1036 QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() == frI1Arbitrary.get());
1037 // Arbitrary Transformation should be identical
1038 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation() == trI1Arbitrary_I1Data.get());
1039 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getTo() == frI1Data.get());
1040 QVERIFY(i1->getArbitrarySlices()->getArbitraryTransformation()->getFrom() == frI1Arbitrary.get());
1041 // Main Transformation should link new main frame to data frame
1042 QVERIFY(i1->getMainTransformation()->getTo() == m2->getFrame());
1043 QVERIFY(i1->getMainTransformation()->getFrom() == frI1Data.get());
1044 // The new one should not be the old one
1045 std::shared_ptr<Transformation> new_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
1046 QVERIFY(new_trI1Data_I1Main != trI1Data_I1Main);
1047 // But it should be the one stored in the ImageComponent
1048 QVERIFY(i1->getMainTransformation() == new_trI1Data_I1Main.get());
1049
1050 // Set frames and transformations of image i1 from image i2
1051 i1->setFrameFrom(i2);
1052 QVERIFY(i1->getFrame() != frI1Main.get());
1053 QVERIFY(i1->getFrame() == i2->getFrame());
1054 QVERIFY(i1->getDataFrame() != frI1Data.get());
1055 QVERIFY(i1->getDataFrame() == i2->getDataFrame());
1056 // Should have created a new Arbitrary Frame
1057 QVERIFY(i1->getArbitrarySlices()->getArbitraryFrame() != frI1Arbitrary.get());
1058 // New Main Transformation should be different from before
1059 std::shared_ptr<Transformation> new2_trI1Data_I1Main = TransformationManager::getTransformationOwnership(i1->getDataFrame(), i1->getFrame());
1060 QVERIFY(new2_trI1Data_I1Main != nullptr);
1061 QVERIFY(new2_trI1Data_I1Main != trI1Data_I1Main);
1062 QVERIFY(new2_trI1Data_I1Main != new_trI1Data_I1Main);
1063 QVERIFY(i1->getMainTransformation() == new2_trI1Data_I1Main.get());
1064 // Arbitrary transform should have the same matrix as before
1065 QVERIFY(isEqual(trI1Arbitrary_I1Data->getMatrix(), i1->getArbitrarySlices()->getArbitraryTransformation()->getMatrix()));
1066 // But it should go from the new Arbitrary frame to the new data frame (the one from i2)
1069
1070 // back to initial state using setFramesAndTransformation()
1071 i1->setFramesAndTransformation(frI1Main, frI1Data, trI1Data_I1Main);
1072 QVERIFY(i1->getFrame() != i2->getFrame());
1073 QVERIFY(i1->getDataFrame() != i2->getDataFrame());
1074 QVERIFY(isEqual(trI1Arbitrary_I1Data->getMatrix(), i1->getArbitrarySlices()->getArbitraryTransformation()->getMatrix()));
1075 }
1076
1078 void checkPreferredDefaultIdentityToWorldLink() {
1079 auto [frameA, frameB, trA2B] = createFramesAndTransformation("FrameA", "FrameB");
1080 auto [frameC, frameD, trC2D] = createFramesAndTransformation("FrameC", "FrameD");
1083 QCOMPARE(countDefaultIdentityToWorldTransformations(), 4); // A, C and their inverse
1084
1086
1087 // check all default were delete by the new transformation
1088 QCOMPARE(countDefaultIdentityToWorldTransformations(), 0);
1089
1091 QCOMPARE(countDefaultIdentityToWorldTransformations(), 2); // D and its inverse
1092
1093 // check the default is still the same
1095 QCOMPARE(countDefaultIdentityToWorldTransformations(), 2); // D and its inverse
1097
1098 // now we want a default from B not D
1100 QCOMPARE(countDefaultIdentityToWorldTransformations(), 2); // B and its inverse
1103 }
1104
1106 void checkGetAllFrames() {
1107 ImageComponent* myImage = createImage("My Image");
1108 MeshComponent* mySphere = createSphere();
1109
1110 // There should be only one frame for a Mesh
1111 QCOMPARE(mySphere->getAllFrames().uniqueKeys().size(), 1);
1112
1113 // There should be only two frames for an Image (without children), data and main
1114 QCOMPARE(myImage->getAllFrames(false).uniqueKeys().size(), 2);
1115 // There should be three frames for an Image with its children: data, main, arbitrary
1116 QCOMPARE(myImage->getAllFrames(true).uniqueKeys().size(), 3);
1117 // There should be three frames for an Image with its children (default call is with children): data, main, arbitrary
1118 QCOMPARE(myImage->getAllFrames().uniqueKeys().size(), 3);
1119 // Check that data frame is used by ImageComponent, as well as 3 singleImageComponent, an arbitrarySingleImageComponent and a VolumeRendering
1120 QCOMPARE(myImage->getAllFrames().values(myImage->getDataFrame()).size(), 6);
1121 // Check that the main frame is used only by the ImageComponent itself
1122 QCOMPARE(myImage->getAllFrames().values(myImage->getFrame()).size(), 1);
1123
1124 // There should be one main transformation, and one arbitrary in the child for the Image
1125 // Test without children
1126 QCOMPARE(myImage->getAllTransformations(false).uniqueKeys().size(), 1);
1127 // Test with children
1128 QCOMPARE(myImage->getAllTransformations(true).uniqueKeys().size(), 2);
1129 // Default should be with children
1130 QCOMPARE(myImage->getAllTransformations().uniqueKeys().size(), 2);
1131
1132 // There should be no Transformation for a Mesh
1133 QCOMPARE(mySphere->getAllTransformations().uniqueKeys().size(), 0);
1134
1135 // Let's add the image as a child of the mesh
1136 mySphere->addChild(myImage);
1137
1138 // Test again
1139 QCOMPARE(mySphere->getAllTransformations(true).uniqueKeys().size(), 2);
1140 QCOMPARE(mySphere->getAllFrames(true).uniqueKeys().size(), 4);
1141 }
1142
1143};
1144
1145// } // namespace camitk
1146
1147#endif // CAMITK_TESTTRANSFORMATIONMANAGER_H
Definition TestTransformationManager.h:60
static const ViewerList getViewers()
get all the viewers registered in the application (note: the returned ViewerList is guaranteed to be ...
Definition Application.cpp:1139
static const ComponentList & getTopLevelComponents()
get the current application wide list of instantiated top-level Components.
Definition Application.cpp:1268
static Viewer * getNewViewer(QString name, QString className)
instantiate a new viewer of the given name and given class name (Viewer inheriting class).
Definition Application.cpp:1119
static bool close(Component *component, bool blockRefresh=false)
Close a Component: if it has been changed, ask the user for more information, then if everything is o...
Definition Application.cpp:755
static bool registerViewer(Viewer *)
register a viewer in the viewer list (therefore allowing it to be refreshed by the main window automa...
Definition Application.cpp:1046
const Transformation * getArbitraryTransformation() const
Returns the arbitrary transformation (from arbitraryFrame to main frame of this component,...
Definition ArbitrarySingleImageComponent.h:150
const FrameOfReference * getArbitraryFrame() const
Get the arbitrary frame that is located in the chosen arbitrary position.
Definition ArbitrarySingleImageComponent.h:155
A Component represents something that could be included in the explorer view, the interactive 3D view...
Definition sdk/libraries/core/component/Component.h:304
virtual QMultiMap< const Transformation *, Component * > getAllTransformations(bool includeChildrenTransformations=true) override
Get all Transformation owned by this object.
Definition sdk/libraries/core/component/Component.cpp:804
virtual bool setPropertyValue(const QString &name, QVariant newValue) override
set the property QVariant value (same as setProperty(const char*, newValue)) but check if it exists f...
Definition sdk/libraries/core/component/Component.cpp:551
virtual void setFrame(const std::shared_ptr< FrameOfReference > &frame) override
Set the FrameOfReference of this object.
Definition sdk/libraries/core/component/Component.h:788
virtual QMultiMap< const FrameOfReference *, Component * > getAllFrames(bool includeChildrenFrames=true) override
Get all FrameOfReference owned by this object.
Definition sdk/libraries/core/component/Component.cpp:792
virtual void resetFrame() override
Reset this object's FrameOfReference, that is call setFrame with a newly created frame of reference.
Definition sdk/libraries/core/component/Component.cpp:820
virtual void setFrameFrom(const InterfaceFrame *) override
Modify this object's frame using the given object's frame.
Definition sdk/libraries/core/component/Component.cpp:815
virtual const FrameOfReference * getFrame() const override
Get the pointer to this object's FrameOfReference.
Definition sdk/libraries/core/component/Component.h:795
virtual void setVisibility(QString, bool)
set the visibility inside the viewer of the given name (the viewer needs to be a registered viewer)
Definition sdk/libraries/core/component/Component.cpp:222
vtkSmartPointer< vtkPointSet > vtkSmartPointer< vtkAlgorithmOutput > getActor
Definition sdk/libraries/core/component/Component.h:593
void addChild(InterfaceNode *) override
add a child Component (sub item in the hierarchy), and modify the child's parent to be equal to this ...
Definition sdk/libraries/core/component/Component.cpp:571
static void autoload()
Autoload component, action and viewer extensions (dlls) as well as registered CamiTK extension file.
Definition ExtensionManager.cpp:46
@ VIEWER
Viewer extensions: manages the presentation and user interaction logic.
Definition ExtensionManager.h:68
FrameOfReference is only a label for an abstract coordinate system.
Definition FrameOfReference.h:71
The manager of the Image Volume data.
Definition ImageComponent.h:76
const FrameOfReference * getDataFrame() const
get the data FrameOfReference (i.e., the vtkImageData frame)
Definition ImageComponent.h:244
virtual void setFrameFrom(const InterfaceFrame *) override
Definition ImageComponent.cpp:914
virtual void resetFrame() override
Reset this object's FrameOfReference, that is call setFrame with a newly created frame of reference.
Definition ImageComponent.cpp:925
virtual void setFramesAndTransformation(const std::shared_ptr< FrameOfReference > &mainFrame, const std::shared_ptr< FrameOfReference > &dataFrame, const std::shared_ptr< Transformation > &mainTransformation)
Definition ImageComponent.cpp:880
virtual QMultiMap< const Transformation *, Component * > getAllTransformations(bool includeChildrenTransformations=true) override
Get all Transformation owned by this image.
Definition ImageComponent.cpp:958
virtual void setFrame(const std::shared_ptr< FrameOfReference > &frame) override
set the main FrameOfReference overriden to manage subcomponents
Definition ImageComponent.cpp:891
virtual QMultiMap< const FrameOfReference *, Component * > getAllFrames(bool includeChildrenFrames=true) override
Get all FrameOfReference owned by this image.
Definition ImageComponent.cpp:944
virtual Transformation * getMainTransformation() const
Get main Transformation (data -> main)
Definition ImageComponent.h:311
ArbitrarySingleImageComponent * getArbitrarySlices()
Returns the arbitrary slice.
Definition ImageComponent.cpp:489
InteractiveViewer is used to view 3D objects and slices (anything that provides either a InterfaceBit...
Definition InteractiveViewer.h:112
void refresh(Viewer *whoIsAsking=nullptr) override
Definition InteractiveViewer.cpp:414
QWidget * getWidget() override
get the InteractiveViewer widget (QTreeWidget).
Definition InteractiveViewer.cpp:252
virtual void setFrame(const std::shared_ptr< FrameOfReference > &frame)
Definition InteractiveViewer.cpp:816
QString getName() const
get the scene name
Definition InteractiveViewer.cpp:409
@ Surface
the surface is visible
Definition InterfaceGeometry.h:67
virtual void setTimeStampInformation(bool showTimeStamp)=0
By default a logger should always show the time-stamp in the form of "yyyy-MM-dd HH:mm:ss....
virtual void setMessageBoxLevel(LogLevel level)=0
Set the lowest log level that will open modal message box for messages instead of (silently/undisrupt...
virtual void setLogLevel(LogLevel level)=0
Sets Current verbosity level of the log.
@ TRACE
all types of messages are logged
Definition InterfaceLogger.h:65
@ NONE
No message is logged.
Definition InterfaceLogger.h:61
static InterfaceLogger * getLogger()
get the current application logger
Definition Log.cpp:50
Basic component to manage any kind of mesh.
Definition MeshComponent.h:53
static bool preferredDefaultIdentityToWorldLink(const FrameOfReference *frame)
Call this method when you prefer (for visualization purpose only) to have a direct link to world from...
Definition TransformationManager.cpp:397
static void ensurePathToWorld(const FrameOfReference *frame)
Make sure there is a Transformation from the given Frame to the world Frame.
Definition TransformationManager.cpp:385
static bool isDefaultIdentityToWorld(const Transformation *)
Is the transformation a default one ? This means that it was created as Identity by default and might...
Definition TransformationManager.cpp:320
static std::shared_ptr< FrameOfReference > getFrameOfReferenceOwnership(const QUuid &uuid)
FrameOfReference Management.
Definition TransformationManager.cpp:137
static bool hasSources(const Transformation *)
Was this Transformation computed from others or not?
Definition TransformationManager.cpp:791
static bool updateTransformation(const FrameOfReference *from, const FrameOfReference *to, vtkSmartPointer< vtkTransform > vtkTr)
Modify the Transformation between the two frames by setting its vtkTransform.
Definition TransformationManager.cpp:781
static bool isCompositeTransformation(const Transformation *tr)
Is this transformation composed of two or more transformations ?
Definition TransformationManager.cpp:822
static QVector< FrameOfReference * > getFramesOfReference()
Get a list of all stored FrameOfReference.
Definition TransformationManager.cpp:204
static void fromVariant(const QVariant &)
Load Frame and Transformation data from a QVariant.
Definition TransformationManager.cpp:857
static std::shared_ptr< Transformation > addTransformation(const QVariant &)
Create and register a new Transformation from a QVariant (usually from a JSON representation in a ....
Definition TransformationManager.cpp:533
static const FrameOfReference * getWorldFrame()
Get the WorldFrame.
Definition TransformationManager.cpp:116
static bool hasPath(const FrameOfReference *from, const FrameOfReference *to)
Transformation Management.
Definition TransformationManager.cpp:213
static QVector< Transformation * > getTransformations()
Returns the list of all transformations managed in the system, independents or not.
Definition TransformationManager.cpp:128
static std::shared_ptr< Transformation > getTransformationOwnership(const Transformation *)
Get the shared_ptr that owns the given Transformation.
Definition TransformationManager.cpp:356
static Transformation * getTransformation(const FrameOfReference *from, const FrameOfReference *to)
Get a transformation if it exists or compute it if a path exists between the frames.
Definition TransformationManager.cpp:325
static std::shared_ptr< FrameOfReference > addFrameOfReference(QString name, QString description="")
Add a FrameOfReference with a name and description This is the standard way to create a new FrameOfRe...
Definition TransformationManager.cpp:168
static void cleanupFramesAndTransformations()
Remove transformations and frames that are unused (i.e.
Definition TransformationManager.cpp:630
static bool removeTransformation(std::shared_ptr< Transformation > &tr)
Remove an existing transformation between the two frames.
Definition TransformationManager.cpp:671
static QVector< Transformation * > getSources(const Transformation *)
Get the list of sources used to compute the provided Transformation.
Definition TransformationManager.cpp:875
Transformation represents a geometrical transformation between two FrameOfReferences.
Definition Transformation.h:83
const FrameOfReference * getFrom() const
Get the FrameOfReference the Transformation starts from (origin)
Definition Transformation.h:272
QUuid getUuid() const override
Get the unique identifier of this Transformation.
Definition Transformation.h:267
vtkSmartPointer< vtkTransform > getTransform() const
Get the internal vtkTransform (linear transformation) or a nullptr.
Definition Transformation.h:247
vtkMatrix4x4 * getMatrix() const
Get the internal 4x4 matrix if the Transformation is linear, otherwise nullptr.
Definition Transformation.cpp:78
const FrameOfReference * getTo() const
Get the FrameOfReference that the Transformation goes to (destination)
Definition Transformation.h:282
Viewer is an abstract class that is the base class for all viewers.
Definition Viewer.h:182
Definition Action.cpp:40
char * toString(const FrameOfReference &frame)
Definition TestTransformationManager.cpp:31