Mercurial > hg > orthanc-stone
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 } |