comparison OrthancStone/UnitTestsSources/VolumeRenderingTests.cpp @ 1877:a2955abe4c2e

skeleton for the RenderingPlugin
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 12 Jan 2022 08:23:38 +0100
parents UnitTestsSources/VolumeRenderingTests.cpp@7053b8a0aaec
children 07964689cb0b
comparison
equal deleted inserted replaced
1876:b1f510e601d2 1877:a2955abe4c2e
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
7 *
8 * This program is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation, either version 3 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 **/
22
23
24 #include "../Sources/Scene2D/CairoCompositor.h"
25 #include "../Sources/Scene2D/ColorTextureSceneLayer.h"
26 #include "../Sources/Scene2D/CopyStyleConfigurator.h"
27 #include "../Sources/Scene2D/MacroSceneLayer.h"
28 #include "../Sources/Scene2D/PolylineSceneLayer.h"
29 #include "../Sources/Toolbox/SubvoxelReader.h"
30 #include "../Sources/Volumes/DicomVolumeImageMPRSlicer.h"
31 #include "../Sources/Volumes/DicomVolumeImageReslicer.h"
32
33 #include <Images/ImageProcessing.h>
34 #include <Images/ImageTraits.h>
35 #include <OrthancException.h>
36
37 #include <gtest/gtest.h>
38
39
40
41 static float GetPixelValue(const Orthanc::ImageAccessor& image,
42 unsigned int x,
43 unsigned int y)
44 {
45 switch (image.GetFormat())
46 {
47 case Orthanc::PixelFormat_Grayscale8:
48 return Orthanc::ImageTraits<Orthanc::PixelFormat_Grayscale8>::GetFloatPixel(image, x, y);
49
50 case Orthanc::PixelFormat_Float32:
51 return Orthanc::ImageTraits<Orthanc::PixelFormat_Float32>::GetFloatPixel(image, x, y);
52
53 case Orthanc::PixelFormat_RGB24:
54 {
55 Orthanc::PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel;
56 Orthanc::ImageTraits<Orthanc::PixelFormat_RGB24>::GetPixel(pixel, image, x, y);
57 return pixel.red_;
58 }
59
60 case Orthanc::PixelFormat_BGRA32:
61 {
62 Orthanc::PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel;
63 Orthanc::ImageTraits<Orthanc::PixelFormat_BGRA32>::GetPixel(pixel, image, x, y);
64 return pixel.red_;
65 }
66
67 default:
68 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
69 }
70 }
71
72
73 static bool IsConstImage(float value,
74 const Orthanc::ImageAccessor& image)
75 {
76 for (unsigned int y = 0; y < image.GetHeight(); y++)
77 {
78 for (unsigned int x = 0; x < image.GetWidth(); x++)
79 {
80 if (!OrthancStone::LinearAlgebra::IsNear(value, GetPixelValue(image, x, y)))
81 {
82 return false;
83 }
84 }
85 }
86
87 return true;
88 }
89
90
91 static bool IsConstRegion(float value,
92 const Orthanc::ImageAccessor& image,
93 unsigned int x,
94 unsigned int y,
95 unsigned int width,
96 unsigned int height)
97 {
98 Orthanc::ImageAccessor region;
99 image.GetRegion(region, x, y, width, height);
100 return IsConstImage(value, region);
101 }
102
103
104 static bool IsConstImageWithExclusion(float value,
105 const Orthanc::ImageAccessor& image,
106 unsigned int exclusionX,
107 unsigned int exclusionY,
108 unsigned int exclusionWidth,
109 unsigned int exclusionHeight)
110 {
111 for (unsigned int y = 0; y < image.GetHeight(); y++)
112 {
113 for (unsigned int x = 0; x < image.GetWidth(); x++)
114 {
115 if ((x < exclusionX ||
116 y < exclusionY ||
117 x >= exclusionX + exclusionWidth ||
118 y >= exclusionY + exclusionHeight) &&
119 !OrthancStone::LinearAlgebra::IsNear(value, GetPixelValue(image, x, y)))
120 {
121 return false;
122 }
123 }
124 }
125
126 return true;
127 }
128
129
130 static bool AreSameImages(const Orthanc::ImageAccessor& image1,
131 const Orthanc::ImageAccessor& image2)
132 {
133 if (image1.GetWidth() != image2.GetWidth() ||
134 image1.GetHeight() != image2.GetHeight())
135 {
136 return false;
137 }
138
139 for (unsigned int y = 0; y < image1.GetHeight(); y++)
140 {
141 for (unsigned int x = 0; x < image1.GetWidth(); x++)
142 {
143 if (!OrthancStone::LinearAlgebra::IsNear(GetPixelValue(image1, x, y),
144 GetPixelValue(image2, x, y)))
145 {
146 return false;
147 }
148 }
149 }
150
151 return true;
152 }
153
154
155 static void Assign3x3Pattern(Orthanc::ImageAccessor& image)
156 {
157 if (image.GetFormat() == Orthanc::PixelFormat_Grayscale8 &&
158 image.GetWidth() == 3 &&
159 image.GetHeight() == 3)
160 {
161 unsigned int v = 0;
162 for (unsigned int y = 0; y < image.GetHeight(); y++)
163 {
164 uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y));
165 for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
166 {
167 *p = v;
168 v += 25;
169 }
170 }
171 }
172 else
173 {
174 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
175 }
176 }
177
178
179 static Orthanc::ImageAccessor* Render(const OrthancStone::Scene2D& scene,
180 unsigned int width,
181 unsigned int height)
182 {
183 OrthancStone::CairoCompositor compositor(width, height);
184 compositor.Refresh(scene);
185
186 Orthanc::ImageAccessor rendered;
187 compositor.GetCanvas().GetReadOnlyAccessor(rendered);
188
189 return Orthanc::Image::Clone(rendered);
190 }
191
192
193
194 // Render the scene using the identity viewpoint (default)
195 static Orthanc::ImageAccessor* Render(OrthancStone::ISceneLayer* layer,
196 unsigned int width,
197 unsigned int height,
198 bool fitScene)
199 {
200 OrthancStone::Scene2D scene;
201 scene.SetLayer(0, layer);
202
203 if (fitScene)
204 {
205 scene.FitContent(width, height);
206 }
207
208 return Render(scene, width, height);
209 }
210
211
212 enum SlicerType
213 {
214 SlicerType_MPR = 0,
215 SlicerType_Reslicer = 1
216 };
217
218
219 static OrthancStone::TextureBaseSceneLayer* SliceVolume(boost::shared_ptr<OrthancStone::DicomVolumeImage> volume,
220 const OrthancStone::CoordinateSystem3D& volumeCoordinates,
221 const OrthancStone::CoordinateSystem3D& cuttingPlane,
222 SlicerType type)
223 {
224 Orthanc::DicomMap dicom;
225 dicom.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, "study", false);
226 dicom.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, "series", false);
227 dicom.SetValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "sop", false);
228
229 volume->SetDicomParameters(OrthancStone::DicomInstanceParameters(dicom));
230
231 std::unique_ptr<OrthancStone::IVolumeSlicer> slicer;
232
233 switch (type)
234 {
235 case SlicerType_MPR:
236 slicer.reset(new OrthancStone::DicomVolumeImageMPRSlicer(volume));
237 break;
238
239 case SlicerType_Reslicer:
240 slicer.reset(new OrthancStone::DicomVolumeImageReslicer(volume));
241 break;
242
243 default:
244 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
245 }
246
247 std::unique_ptr<OrthancStone::IVolumeSlicer::IExtractedSlice> slice(slicer->ExtractSlice(cuttingPlane));
248 if (slice->IsValid())
249 {
250 OrthancStone::CopyStyleConfigurator configurator;
251 return dynamic_cast<OrthancStone::TextureBaseSceneLayer*>(slice->CreateSceneLayer(&configurator, cuttingPlane));
252 }
253 else
254 {
255 return NULL;
256 }
257 }
258
259
260 static OrthancStone::TextureBaseSceneLayer* Slice3x3x1Pattern(OrthancStone::VolumeProjection projection,
261 const OrthancStone::CoordinateSystem3D& volumeCoordinates,
262 const OrthancStone::CoordinateSystem3D& cuttingPlane,
263 SlicerType type)
264 {
265 OrthancStone::VolumeImageGeometry geometry;
266
267 switch (projection)
268 {
269 case OrthancStone::VolumeProjection_Axial:
270 geometry.SetSizeInVoxels(3, 3, 1);
271 break;
272
273 case OrthancStone::VolumeProjection_Sagittal:
274 geometry.SetSizeInVoxels(1, 3, 3);
275 break;
276
277 case OrthancStone::VolumeProjection_Coronal:
278 geometry.SetSizeInVoxels(3, 1, 3);
279 break;
280
281 default:
282 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
283 }
284
285 geometry.SetAxialGeometry(volumeCoordinates);
286
287 boost::shared_ptr<OrthancStone::DicomVolumeImage> volume(new OrthancStone::DicomVolumeImage);
288 volume->Initialize(geometry, Orthanc::PixelFormat_Grayscale8, false);
289
290 {
291 OrthancStone::ImageBuffer3D::SliceWriter writer(volume->GetPixelData(), projection, 0);
292 Assign3x3Pattern(writer.GetAccessor());
293 }
294
295 OrthancStone::Vector v = volume->GetGeometry().GetVoxelDimensions(OrthancStone::VolumeProjection_Axial);
296 if (!OrthancStone::LinearAlgebra::IsNear(1, v[0]) ||
297 !OrthancStone::LinearAlgebra::IsNear(1, v[1]) ||
298 !OrthancStone::LinearAlgebra::IsNear(1, v[2]))
299 {
300 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
301 }
302
303 return SliceVolume(volume, volumeCoordinates, cuttingPlane, type);
304 }
305
306
307 TEST(VolumeRendering, Pattern)
308 {
309 {
310 // Axial
311 OrthancStone::ImageBuffer3D image(Orthanc::PixelFormat_Grayscale8, 3, 3, 1, true);
312
313 {
314 OrthancStone::ImageBuffer3D::SliceWriter writer(image, OrthancStone::VolumeProjection_Axial, 0);
315 Assign3x3Pattern(writer.GetAccessor());
316 }
317
318 float a, b;
319 ASSERT_TRUE(image.GetRange(a, b));
320 ASSERT_FLOAT_EQ(0, a);
321 ASSERT_FLOAT_EQ(200, b);
322
323 ASSERT_EQ(0, image.GetVoxelGrayscale8(0, 0, 0));
324 ASSERT_EQ(25, image.GetVoxelGrayscale8(1, 0, 0));
325 ASSERT_EQ(50, image.GetVoxelGrayscale8(2, 0, 0));
326 ASSERT_EQ(75, image.GetVoxelGrayscale8(0, 1, 0));
327 ASSERT_EQ(100, image.GetVoxelGrayscale8(1, 1, 0));
328 ASSERT_EQ(125, image.GetVoxelGrayscale8(2, 1, 0));
329 ASSERT_EQ(150, image.GetVoxelGrayscale8(0, 2, 0));
330 ASSERT_EQ(175, image.GetVoxelGrayscale8(1, 2, 0));
331 ASSERT_EQ(200, image.GetVoxelGrayscale8(2, 2, 0));
332
333 float v;
334 OrthancStone::SubvoxelReader<Orthanc::PixelFormat_Grayscale8,
335 OrthancStone::ImageInterpolation_Nearest> reader(image);
336
337 ASSERT_TRUE(reader.GetFloatValue(v, 0.01, 0.01, 0.01)); ASSERT_FLOAT_EQ(0, v);
338 ASSERT_TRUE(reader.GetFloatValue(v, 1.01, 0.01, 0.01)); ASSERT_FLOAT_EQ(25, v);
339 ASSERT_TRUE(reader.GetFloatValue(v, 2.01, 0.01, 0.01)); ASSERT_FLOAT_EQ(50, v);
340 ASSERT_TRUE(reader.GetFloatValue(v, 0.01, 1.01, 0.01)); ASSERT_FLOAT_EQ(75, v);
341 ASSERT_TRUE(reader.GetFloatValue(v, 1.01, 1.01, 0.01)); ASSERT_FLOAT_EQ(100, v);
342 ASSERT_TRUE(reader.GetFloatValue(v, 2.01, 1.01, 0.01)); ASSERT_FLOAT_EQ(125, v);
343 ASSERT_TRUE(reader.GetFloatValue(v, 0.01, 2.01, 0.01)); ASSERT_FLOAT_EQ(150, v);
344 ASSERT_TRUE(reader.GetFloatValue(v, 1.01, 2.01, 0.01)); ASSERT_FLOAT_EQ(175, v);
345 ASSERT_TRUE(reader.GetFloatValue(v, 2.01, 2.01, 0.01)); ASSERT_FLOAT_EQ(200, v);
346
347 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(0, v);
348 ASSERT_TRUE(reader.GetFloatValue(v, 1.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(25, v);
349 ASSERT_TRUE(reader.GetFloatValue(v, 2.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(50, v);
350 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 1.99, 0.99)); ASSERT_FLOAT_EQ(75, v);
351 ASSERT_TRUE(reader.GetFloatValue(v, 1.99, 1.99, 0.99)); ASSERT_FLOAT_EQ(100, v);
352 ASSERT_TRUE(reader.GetFloatValue(v, 2.99, 1.99, 0.99)); ASSERT_FLOAT_EQ(125, v);
353 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 2.99, 0.99)); ASSERT_FLOAT_EQ(150, v);
354 ASSERT_TRUE(reader.GetFloatValue(v, 1.99, 2.99, 0.99)); ASSERT_FLOAT_EQ(175, v);
355 ASSERT_TRUE(reader.GetFloatValue(v, 2.99, 2.99, 0.99)); ASSERT_FLOAT_EQ(200, v);
356 }
357
358 {
359 // Coronal
360 OrthancStone::ImageBuffer3D image(Orthanc::PixelFormat_Grayscale8, 3, 1, 3, true);
361
362 {
363 OrthancStone::ImageBuffer3D::SliceWriter writer(image, OrthancStone::VolumeProjection_Coronal, 0);
364 Assign3x3Pattern(writer.GetAccessor());
365 }
366
367 float a, b;
368 ASSERT_TRUE(image.GetRange(a, b));
369 ASSERT_FLOAT_EQ(0, a);
370 ASSERT_FLOAT_EQ(200, b);
371
372 // "Z" is in reverse order in "Assign3x3Pattern()", because important note in "ImageBuffer3D"
373 ASSERT_EQ(0, image.GetVoxelGrayscale8(0, 0, 2));
374 ASSERT_EQ(25, image.GetVoxelGrayscale8(1, 0, 2));
375 ASSERT_EQ(50, image.GetVoxelGrayscale8(2, 0, 2));
376 ASSERT_EQ(75, image.GetVoxelGrayscale8(0, 0, 1));
377 ASSERT_EQ(100, image.GetVoxelGrayscale8(1, 0, 1));
378 ASSERT_EQ(125, image.GetVoxelGrayscale8(2, 0, 1));
379 ASSERT_EQ(150, image.GetVoxelGrayscale8(0, 0, 0));
380 ASSERT_EQ(175, image.GetVoxelGrayscale8(1, 0, 0));
381 ASSERT_EQ(200, image.GetVoxelGrayscale8(2, 0, 0));
382
383 // Ensure that "SubvoxelReader" is consistent with "image.GetVoxelGrayscale8()"
384 float v;
385 OrthancStone::SubvoxelReader<Orthanc::PixelFormat_Grayscale8,
386 OrthancStone::ImageInterpolation_Nearest> reader(image);
387
388 ASSERT_TRUE(reader.GetFloatValue(v, 0.01, 0.01, 2.01)); ASSERT_FLOAT_EQ(0, v);
389 ASSERT_TRUE(reader.GetFloatValue(v, 1.01, 0.01, 2.01)); ASSERT_FLOAT_EQ(25, v);
390 ASSERT_TRUE(reader.GetFloatValue(v, 2.01, 0.01, 2.01)); ASSERT_FLOAT_EQ(50, v);
391 ASSERT_TRUE(reader.GetFloatValue(v, 0.01, 0.01, 1.01)); ASSERT_FLOAT_EQ(75, v);
392 ASSERT_TRUE(reader.GetFloatValue(v, 1.01, 0.01, 1.01)); ASSERT_FLOAT_EQ(100, v);
393 ASSERT_TRUE(reader.GetFloatValue(v, 2.01, 0.01, 1.01)); ASSERT_FLOAT_EQ(125, v);
394 ASSERT_TRUE(reader.GetFloatValue(v, 0.01, 0.01, 0.01)); ASSERT_FLOAT_EQ(150, v);
395 ASSERT_TRUE(reader.GetFloatValue(v, 1.01, 0.01, 0.01)); ASSERT_FLOAT_EQ(175, v);
396 ASSERT_TRUE(reader.GetFloatValue(v, 2.01, 0.01, 0.01)); ASSERT_FLOAT_EQ(200, v);
397
398 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 2.99)); ASSERT_FLOAT_EQ(0, v);
399 ASSERT_TRUE(reader.GetFloatValue(v, 1.99, 0.99, 2.99)); ASSERT_FLOAT_EQ(25, v);
400 ASSERT_TRUE(reader.GetFloatValue(v, 2.99, 0.99, 2.99)); ASSERT_FLOAT_EQ(50, v);
401 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 1.99)); ASSERT_FLOAT_EQ(75, v);
402 ASSERT_TRUE(reader.GetFloatValue(v, 1.99, 0.99, 1.99)); ASSERT_FLOAT_EQ(100, v);
403 ASSERT_TRUE(reader.GetFloatValue(v, 2.99, 0.99, 1.99)); ASSERT_FLOAT_EQ(125, v);
404 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(150, v);
405 ASSERT_TRUE(reader.GetFloatValue(v, 1.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(175, v);
406 ASSERT_TRUE(reader.GetFloatValue(v, 2.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(200, v);
407 }
408
409 {
410 // Sagittal
411 OrthancStone::ImageBuffer3D image(Orthanc::PixelFormat_Grayscale8, 1, 3, 3, true);
412
413 {
414 OrthancStone::ImageBuffer3D::SliceWriter writer(image, OrthancStone::VolumeProjection_Sagittal, 0);
415 Assign3x3Pattern(writer.GetAccessor());
416 }
417
418 float a, b;
419 ASSERT_TRUE(image.GetRange(a, b));
420 ASSERT_FLOAT_EQ(0, a);
421 ASSERT_FLOAT_EQ(200, b);
422
423 // "Z" is in reverse order in "Assign3x3Pattern()", because important note in "ImageBuffer3D"
424 ASSERT_EQ(0, image.GetVoxelGrayscale8(0, 0, 2));
425 ASSERT_EQ(25, image.GetVoxelGrayscale8(0, 1, 2));
426 ASSERT_EQ(50, image.GetVoxelGrayscale8(0, 2, 2));
427 ASSERT_EQ(75, image.GetVoxelGrayscale8(0, 0, 1));
428 ASSERT_EQ(100, image.GetVoxelGrayscale8(0, 1, 1));
429 ASSERT_EQ(125, image.GetVoxelGrayscale8(0, 2, 1));
430 ASSERT_EQ(150, image.GetVoxelGrayscale8(0, 0, 0));
431 ASSERT_EQ(175, image.GetVoxelGrayscale8(0, 1, 0));
432 ASSERT_EQ(200, image.GetVoxelGrayscale8(0, 2, 0));
433
434 // Ensure that "SubvoxelReader" is consistent with "image.GetVoxelGrayscale8()"
435 float v;
436 OrthancStone::SubvoxelReader<Orthanc::PixelFormat_Grayscale8,
437 OrthancStone::ImageInterpolation_Nearest> reader(image);
438
439 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 0.01, 2.01)); ASSERT_FLOAT_EQ(0, v);
440 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 1.01, 2.01)); ASSERT_FLOAT_EQ(25, v);
441 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 2.01, 2.01)); ASSERT_FLOAT_EQ(50, v);
442 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 0.01, 1.01)); ASSERT_FLOAT_EQ(75, v);
443 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 1.01, 1.01)); ASSERT_FLOAT_EQ(100, v);
444 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 2.01, 1.01)); ASSERT_FLOAT_EQ(125, v);
445 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 0.01, 0.01)); ASSERT_FLOAT_EQ(150, v);
446 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 1.01, 0.01)); ASSERT_FLOAT_EQ(175, v);
447 ASSERT_TRUE(reader.GetFloatValue(v, 0.1, 2.01, 0.01)); ASSERT_FLOAT_EQ(200, v);
448
449 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 2.99)); ASSERT_FLOAT_EQ(0, v);
450 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 1.99, 2.99)); ASSERT_FLOAT_EQ(25, v);
451 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 2.99, 2.99)); ASSERT_FLOAT_EQ(50, v);
452 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 1.99)); ASSERT_FLOAT_EQ(75, v);
453 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 1.99, 1.99)); ASSERT_FLOAT_EQ(100, v);
454 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 2.99, 1.99)); ASSERT_FLOAT_EQ(125, v);
455 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 0.99, 0.99)); ASSERT_FLOAT_EQ(150, v);
456 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 1.99, 0.99)); ASSERT_FLOAT_EQ(175, v);
457 ASSERT_TRUE(reader.GetFloatValue(v, 0.99, 2.99, 0.99)); ASSERT_FLOAT_EQ(200, v);
458 }
459 }
460
461
462 TEST(VolumeRendering, Axial)
463 {
464 OrthancStone::CoordinateSystem3D axial(OrthancStone::LinearAlgebra::CreateVector(-0.5, -0.5, 0),
465 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
466 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
467
468 for (unsigned int mode = 0; mode < 2; mode++)
469 {
470 OrthancStone::CoordinateSystem3D cuttingPlane;
471
472 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
473 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast<SlicerType>(mode)));
474
475 ASSERT_TRUE(layer.get() != NULL);
476 ASSERT_EQ(OrthancStone::ISceneLayer::Type_FloatTexture, layer->GetType());
477
478 OrthancStone::Extent2D box;
479 layer->GetBoundingBox(box);
480 ASSERT_FLOAT_EQ(-1.0f, box.GetX1());
481 ASSERT_FLOAT_EQ(-1.0f, box.GetY1());
482 ASSERT_FLOAT_EQ(2.0f, box.GetX2());
483 ASSERT_FLOAT_EQ(2.0f, box.GetY2());
484
485 {
486 const Orthanc::ImageAccessor& texture = dynamic_cast<OrthancStone::TextureBaseSceneLayer&>(*layer).GetTexture();
487 ASSERT_EQ(3u, texture.GetWidth());
488 ASSERT_EQ(3u, texture.GetHeight());
489 ASSERT_FLOAT_EQ(0, GetPixelValue(texture, 0, 0));
490 ASSERT_FLOAT_EQ(25, GetPixelValue(texture, 1, 0));
491 ASSERT_FLOAT_EQ(50, GetPixelValue(texture, 2, 0));
492 ASSERT_FLOAT_EQ(75, GetPixelValue(texture, 0, 1));
493 ASSERT_FLOAT_EQ(100, GetPixelValue(texture, 1, 1));
494 ASSERT_FLOAT_EQ(125, GetPixelValue(texture, 2, 1));
495 ASSERT_FLOAT_EQ(150, GetPixelValue(texture, 0, 2));
496 ASSERT_FLOAT_EQ(175, GetPixelValue(texture, 1, 2));
497 ASSERT_FLOAT_EQ(200, GetPixelValue(texture, 2, 2));
498 }
499
500 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 5, 5, false));
501 ASSERT_EQ(5u, rendered->GetWidth());
502 ASSERT_EQ(5u, rendered->GetHeight());
503 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
504 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 0));
505 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 2, 0));
506 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 3, 0));
507 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 4, 0));
508 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 1));
509 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 1));
510 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 2, 1));
511 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 3, 1));
512 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 4, 1));
513 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 2));
514 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 2));
515 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 2, 2));
516 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 3, 2));
517 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 4, 2));
518 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 3));
519 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 3));
520 ASSERT_FLOAT_EQ(75, GetPixelValue(*rendered, 2, 3));
521 ASSERT_FLOAT_EQ(100, GetPixelValue(*rendered, 3, 3));
522 ASSERT_FLOAT_EQ(125, GetPixelValue(*rendered, 4, 3));
523 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 4));
524 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 4));
525 ASSERT_FLOAT_EQ(150, GetPixelValue(*rendered, 2, 4));
526 ASSERT_FLOAT_EQ(175, GetPixelValue(*rendered, 3, 4));
527 ASSERT_FLOAT_EQ(200, GetPixelValue(*rendered, 4, 4));
528 }
529 }
530
531
532 TEST(VolumeRendering, TextureCorners)
533 {
534 // The origin of a 2D texture is the coordinate of the BORDER of the
535 // top-left pixel, *not* the center of the top-left pixel (as in
536 // DICOM 3D convention)
537
538 Orthanc::Image pixel(Orthanc::PixelFormat_RGB24, 1, 1, false);
539 Orthanc::ImageProcessing::Set(pixel, 255, 0, 0, 255);
540
541 {
542 std::unique_ptr<OrthancStone::ColorTextureSceneLayer> layer(new OrthancStone::ColorTextureSceneLayer(pixel));
543 layer->SetOrigin(0, 0);
544
545 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 2, 2, false));
546 ASSERT_EQ(2u, rendered->GetWidth());
547 ASSERT_EQ(2u, rendered->GetHeight());
548 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
549 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 0));
550 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 1));
551 ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 1, 1));
552 }
553
554 {
555 std::unique_ptr<OrthancStone::ColorTextureSceneLayer> layer(new OrthancStone::ColorTextureSceneLayer(pixel));
556 layer->SetOrigin(-0.01, 0);
557
558 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 2, 2, false));
559 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
560 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 0));
561 ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 0, 1));
562 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 1));
563 }
564
565 {
566 std::unique_ptr<OrthancStone::ColorTextureSceneLayer> layer(new OrthancStone::ColorTextureSceneLayer(pixel));
567 layer->SetOrigin(-0.01, -0.01);
568
569 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 2, 2, false));
570 ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 0, 0));
571 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 0));
572 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 1));
573 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 1));
574 }
575
576 {
577 std::unique_ptr<OrthancStone::ColorTextureSceneLayer> layer(new OrthancStone::ColorTextureSceneLayer(pixel));
578 layer->SetOrigin(0, -0.01);
579
580 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 2, 2, false));
581 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
582 ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 1, 0));
583 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 1));
584 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 1));
585 }
586 }
587
588
589
590 TEST(VolumeRendering, FitTexture)
591 {
592 Orthanc::Image pixel(Orthanc::PixelFormat_RGB24, 1, 1, false);
593 Orthanc::ImageProcessing::Set(pixel, 255, 0, 0, 255);
594
595 {
596 std::unique_ptr<OrthancStone::ColorTextureSceneLayer> layer(new OrthancStone::ColorTextureSceneLayer(pixel));
597 layer->SetOrigin(-42.0f, 35.0f);
598 layer->SetPixelSpacing(2, 3);
599
600 OrthancStone::Scene2D scene;
601 scene.SetLayer(0, layer.release());
602 scene.FitContent(30, 30);
603
604 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(scene, 30, 30));
605 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 5, 30));
606 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 5, 0, 20, 30));
607 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 25, 0, 5, 30));
608
609 rendered.reset(Render(scene, 40, 30));
610 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 10, 30));
611 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 10, 0, 20, 30));
612 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 30, 0, 5, 30));
613
614 scene.FitContent(40, 30);
615 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 10, 30));
616 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 10, 0, 20, 30));
617 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 30, 0, 5, 30));
618
619 rendered.reset(Render(scene, 30, 36)); // The scene has not been fitted
620 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 30, 3));
621 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 3, 36));
622 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 5, 3, 20, 30));
623 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 25, 0, 5, 36));
624 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 33, 30, 3));
625
626 scene.FitContent(30, 36); // Refit
627 rendered.reset(Render(scene, 30, 36));
628 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 3, 36));
629 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 3, 0, 24, 36));
630 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 27, 0, 3, 36));
631 }
632
633 {
634 std::unique_ptr<OrthancStone::ColorTextureSceneLayer> layer(new OrthancStone::ColorTextureSceneLayer(pixel));
635 layer->SetOrigin(42.0f, -35.0f);
636 layer->SetPixelSpacing(3, 2);
637
638 OrthancStone::Scene2D scene;
639 scene.SetLayer(0, layer.release());
640 scene.FitContent(30, 30);
641
642 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(scene, 30, 30));
643 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 30, 5));
644 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 0, 5, 30, 20));
645 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 25, 30, 5));
646
647 rendered.reset(Render(scene, 30, 40));
648 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 30, 10));
649 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 0, 10, 30, 20));
650 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 30, 30, 5));
651
652 scene.FitContent(30, 40);
653 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 30, 10));
654 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 0, 10, 30, 20));
655 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 30, 30, 5));
656
657 rendered.reset(Render(scene, 36, 30)); // The scene has not been fitted
658 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 3, 30));
659 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 36, 3));
660 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 3, 5, 30, 20));
661 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 25, 36, 5));
662 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 33, 0, 3, 30));
663
664 scene.FitContent(36, 30); // Refit
665 rendered.reset(Render(scene, 36, 30));
666 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 0, 36, 3));
667 ASSERT_TRUE(IsConstRegion(255.0f, *rendered, 0, 3, 36, 24));
668 ASSERT_TRUE(IsConstRegion(0.0f, *rendered, 0, 27, 36, 3));
669 }
670 }
671
672
673
674 TEST(VolumeRendering, MPR)
675 {
676 double x = 2;
677 double y = 1;
678 OrthancStone::CoordinateSystem3D axial(OrthancStone::LinearAlgebra::CreateVector(x, y, 0),
679 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
680 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
681
682 Orthanc::Image pattern(Orthanc::PixelFormat_Grayscale8, 3, 3, false);
683 Assign3x3Pattern(pattern);
684
685 Orthanc::Image patternX(Orthanc::PixelFormat_Grayscale8, 3, 3, false);
686 Assign3x3Pattern(patternX);
687 Orthanc::ImageProcessing::FlipX(patternX);
688
689 Orthanc::Image patternY(Orthanc::PixelFormat_Grayscale8, 3, 3, false);
690 Assign3x3Pattern(patternY);
691 Orthanc::ImageProcessing::FlipY(patternY);
692
693 Orthanc::Image patternXY(Orthanc::PixelFormat_Grayscale8, 3, 3, false);
694 Assign3x3Pattern(patternXY);
695 Orthanc::ImageProcessing::FlipX(patternXY);
696 Orthanc::ImageProcessing::FlipY(patternXY);
697
698 for (unsigned int mode = 0; mode < 2; mode++)
699 {
700 {
701 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0),
702 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
703 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
704
705 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
706 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast<SlicerType>(mode)));
707 ASSERT_TRUE(AreSameImages(layer->GetTexture(), pattern));
708
709 OrthancStone::Extent2D extent;
710 layer->GetBoundingBox(extent);
711 ASSERT_FLOAT_EQ(x - 0.5, extent.GetX1());
712 ASSERT_FLOAT_EQ(y - 0.5, extent.GetY1());
713 ASSERT_FLOAT_EQ(x + 2.5, extent.GetX2());
714 ASSERT_FLOAT_EQ(y + 2.5, extent.GetY2());
715
716 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 15, 15, false));
717 ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 9, 8, 3, 3));
718
719 Orthanc::ImageAccessor p;
720 rendered->GetRegion(p, 9, 8, 3, 3);
721 ASSERT_TRUE(AreSameImages(p, pattern));
722 }
723
724 {
725 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0),
726 OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0),
727 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
728
729 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
730 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast<SlicerType>(mode)));
731 if (mode == 1)
732 {
733 // Reslicer directly flips the pixels of the texture
734 ASSERT_TRUE(AreSameImages(layer->GetTexture(), patternX));
735 }
736 else
737 {
738 // MPR slicer uses "TextureBaseSceneLayer::SetTransform()" to flip
739 ASSERT_TRUE(AreSameImages(layer->GetTexture(), pattern));
740 }
741
742 OrthancStone::Extent2D extent;
743 layer->GetBoundingBox(extent);
744 ASSERT_FLOAT_EQ(-(x + 2.5), extent.GetX1());
745 ASSERT_FLOAT_EQ(y - 0.5, extent.GetY1());
746 ASSERT_FLOAT_EQ(-(x - 0.5), extent.GetX2());
747 ASSERT_FLOAT_EQ(y + 2.5, extent.GetY2());
748
749 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 15, 15, false));
750 ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 3, 8, 3, 3));
751
752 Orthanc::ImageAccessor p;
753 rendered->GetRegion(p, 3, 8, 3, 3);
754 ASSERT_TRUE(AreSameImages(p, patternX));
755 }
756
757 {
758 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0),
759 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
760 OrthancStone::LinearAlgebra::CreateVector(0, -1, 0));
761
762 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
763 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast<SlicerType>(mode)));
764 if (mode == 1)
765 {
766 ASSERT_TRUE(AreSameImages(layer->GetTexture(), patternY));
767 }
768 else
769 {
770 ASSERT_TRUE(AreSameImages(layer->GetTexture(), pattern));
771 }
772
773 OrthancStone::Extent2D extent;
774 layer->GetBoundingBox(extent);
775 ASSERT_FLOAT_EQ(x - 0.5, extent.GetX1());
776 ASSERT_FLOAT_EQ(-(y + 2.5), extent.GetY1());
777 ASSERT_FLOAT_EQ(x + 2.5, extent.GetX2());
778 ASSERT_FLOAT_EQ(-(y - 0.5), extent.GetY2());
779
780 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 15, 15, false));
781 ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 9, 4, 3, 3));
782
783 Orthanc::ImageAccessor p;
784 rendered->GetRegion(p, 9, 4, 3, 3);
785 ASSERT_TRUE(AreSameImages(p, patternY));
786 }
787
788 {
789 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0),
790 OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0),
791 OrthancStone::LinearAlgebra::CreateVector(0, -1, 0));
792
793 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
794 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast<SlicerType>(mode)));
795 if (mode == 1)
796 {
797 ASSERT_TRUE(AreSameImages(layer->GetTexture(), patternXY));
798 }
799 else
800 {
801 ASSERT_TRUE(AreSameImages(layer->GetTexture(), pattern));
802 }
803
804 OrthancStone::Extent2D extent;
805 layer->GetBoundingBox(extent);
806 ASSERT_FLOAT_EQ(-(x + 2.5), extent.GetX1());
807 ASSERT_FLOAT_EQ(-(y + 2.5), extent.GetY1());
808 ASSERT_FLOAT_EQ(-(x - 0.5), extent.GetX2());
809 ASSERT_FLOAT_EQ(-(y - 0.5), extent.GetY2());
810
811 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 15, 15, false));
812 ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 3, 4, 3, 3));
813
814 Orthanc::ImageAccessor p;
815 rendered->GetRegion(p, 3, 4, 3, 3);
816 ASSERT_TRUE(AreSameImages(p, patternXY));
817 }
818
819 // Tests along the sagittal and coronal axis: cf.
820 // "TEST(VolumeRendering, Pattern)" to understand why Z is flipped
821
822 {
823 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0),
824 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
825 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
826
827 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
828 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
829
830 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 1, 3, true));
831 ASSERT_FLOAT_EQ(150, GetPixelValue(*rendered, 0, 0));
832 ASSERT_FLOAT_EQ(175, GetPixelValue(*rendered, 0, 1));
833 ASSERT_FLOAT_EQ(200, GetPixelValue(*rendered, 0, 2));
834 }
835
836 {
837 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 1),
838 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
839 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
840
841 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
842 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
843
844 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 1, 3, true));
845 ASSERT_FLOAT_EQ(75, GetPixelValue(*rendered, 0, 0));
846 ASSERT_FLOAT_EQ(100, GetPixelValue(*rendered, 0, 1));
847 ASSERT_FLOAT_EQ(125, GetPixelValue(*rendered, 0, 2));
848 }
849
850 {
851 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
852 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
853 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
854
855 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
856 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
857
858 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 1, 3, true));
859 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
860 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 0, 1));
861 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 0, 2));
862 }
863
864 {
865 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
866 OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0),
867 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
868
869 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
870 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
871
872 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 1, 3, true));
873 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
874 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 0, 1));
875 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 0, 2));
876 }
877
878 {
879 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
880 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
881 OrthancStone::LinearAlgebra::CreateVector(0, -1, 0));
882
883 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
884 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
885
886 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 1, 3, true));
887 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 0, 0));
888 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 0, 1));
889 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 2));
890 }
891
892 {
893 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
894 OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0),
895 OrthancStone::LinearAlgebra::CreateVector(0, -1, 0));
896
897 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
898 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
899
900 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 1, 3, true));
901 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 0, 0));
902 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 0, 1));
903 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 2));
904 }
905
906 for (double z = -1; z < 4; z += 4) // z in { -1, 3 }, out of volume
907 {
908 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, z),
909 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
910 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
911
912 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
913 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Sagittal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
914
915 ASSERT_TRUE(layer.get() == NULL || // This is for DicomVolumeImageMPRSlicer
916 (layer->GetTexture().GetWidth() == 0 && // This is for DicomVolumeImageReslicer
917 layer->GetTexture().GetHeight() == 0));
918 }
919
920 {
921 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0),
922 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
923 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
924
925 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
926 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
927
928 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 3, 1, true));
929 ASSERT_FLOAT_EQ(150, GetPixelValue(*rendered, 0, 0));
930 ASSERT_FLOAT_EQ(175, GetPixelValue(*rendered, 1, 0));
931 ASSERT_FLOAT_EQ(200, GetPixelValue(*rendered, 2, 0));
932 }
933
934 {
935 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 1),
936 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
937 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
938
939 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
940 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
941
942 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 3, 1, true));
943 ASSERT_FLOAT_EQ(75, GetPixelValue(*rendered, 0, 0));
944 ASSERT_FLOAT_EQ(100, GetPixelValue(*rendered, 1, 0));
945 ASSERT_FLOAT_EQ(125, GetPixelValue(*rendered, 2, 0));
946 }
947
948 {
949 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
950 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
951 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
952
953 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
954 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
955
956 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 3, 1, true));
957 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
958 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 1, 0));
959 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 2, 0));
960 }
961
962 {
963 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
964 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
965 OrthancStone::LinearAlgebra::CreateVector(0, -1, 0));
966
967 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
968 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
969
970 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 3, 1, true));
971 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0));
972 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 1, 0));
973 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 2, 0));
974 }
975
976 {
977 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
978 OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0),
979 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
980
981 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
982 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
983
984 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 3, 1, true));
985 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 0, 0));
986 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 1, 0));
987 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 2, 0));
988 }
989
990 {
991 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, 2),
992 OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0),
993 OrthancStone::LinearAlgebra::CreateVector(0, -1, 0));
994
995 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
996 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
997
998 std::unique_ptr<Orthanc::ImageAccessor> rendered(Render(layer.release(), 3, 1, true));
999 ASSERT_FLOAT_EQ(50, GetPixelValue(*rendered, 0, 0));
1000 ASSERT_FLOAT_EQ(25, GetPixelValue(*rendered, 1, 0));
1001 ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 2, 0));
1002 }
1003
1004 for (double z = -1; z < 4; z += 4) // z in { -1, 3 }, out of volume
1005 {
1006 OrthancStone::CoordinateSystem3D cuttingPlane(OrthancStone::LinearAlgebra::CreateVector(0, 0, z),
1007 OrthancStone::LinearAlgebra::CreateVector(1, 0, 0),
1008 OrthancStone::LinearAlgebra::CreateVector(0, 1, 0));
1009
1010 std::unique_ptr<OrthancStone::TextureBaseSceneLayer> layer(
1011 Slice3x3x1Pattern(OrthancStone::VolumeProjection_Coronal, axial, cuttingPlane, static_cast<SlicerType>(mode)));
1012
1013 ASSERT_TRUE(layer.get() == NULL || // This is for DicomVolumeImageMPRSlicer
1014 (layer->GetTexture().GetWidth() == 0 && // This is for DicomVolumeImageReslicer
1015 layer->GetTexture().GetHeight() == 0));
1016 }
1017 }
1018 }
1019
1020
1021 TEST(VolumeRendering, MacroLayer)
1022 {
1023 OrthancStone::MacroSceneLayer layer;
1024 ASSERT_THROW(layer.AddLayer(NULL), Orthanc::OrthancException);
1025
1026 ASSERT_EQ(0u, layer.AddLayer(new OrthancStone::PolylineSceneLayer));
1027 ASSERT_EQ(1u, layer.AddLayer(new OrthancStone::PolylineSceneLayer));
1028 ASSERT_EQ(2u, layer.AddLayer(new OrthancStone::PolylineSceneLayer));
1029 ASSERT_EQ(3u, layer.GetSize());
1030 ASSERT_TRUE(layer.HasLayer(0));
1031 ASSERT_TRUE(layer.HasLayer(1));
1032 ASSERT_TRUE(layer.HasLayer(2));
1033
1034 layer.DeleteLayer(1);
1035 ASSERT_EQ(3u, layer.GetSize());
1036 ASSERT_TRUE(layer.HasLayer(0));
1037 ASSERT_FALSE(layer.HasLayer(1));
1038 ASSERT_TRUE(layer.HasLayer(2));
1039
1040 ASSERT_THROW(layer.UpdateLayer(1, NULL), Orthanc::OrthancException);
1041 layer.UpdateLayer(1, new OrthancStone::PolylineSceneLayer);
1042 ASSERT_TRUE(layer.HasLayer(1));
1043
1044 ASSERT_EQ(3u, layer.AddLayer(new OrthancStone::PolylineSceneLayer));
1045 ASSERT_EQ(4u, layer.GetSize());
1046
1047 layer.DeleteLayer(1);
1048 layer.DeleteLayer(2);
1049 ASSERT_EQ(1u, layer.AddLayer(new OrthancStone::PolylineSceneLayer));
1050
1051 std::unique_ptr<OrthancStone::MacroSceneLayer> clone(dynamic_cast<OrthancStone::MacroSceneLayer*>(layer.Clone()));
1052
1053 layer.UpdateLayer(2, new OrthancStone::PolylineSceneLayer);
1054 ASSERT_EQ(4u, layer.AddLayer(new OrthancStone::PolylineSceneLayer));
1055 ASSERT_EQ(5u, layer.GetSize());
1056 ASSERT_TRUE(layer.HasLayer(0));
1057 ASSERT_TRUE(layer.HasLayer(1));
1058 ASSERT_TRUE(layer.HasLayer(2));
1059 ASSERT_TRUE(layer.HasLayer(3));
1060 ASSERT_TRUE(layer.HasLayer(4));
1061
1062 ASSERT_EQ(2u, clone->AddLayer(new OrthancStone::PolylineSceneLayer));
1063 ASSERT_EQ(4u, clone->GetSize());
1064 ASSERT_TRUE(clone->HasLayer(0));
1065 ASSERT_TRUE(clone->HasLayer(1));
1066 ASSERT_TRUE(clone->HasLayer(2));
1067 ASSERT_TRUE(clone->HasLayer(3));
1068 ASSERT_THROW(clone->HasLayer(4), Orthanc::OrthancException);
1069 }