Mercurial > hg > orthanc-stone
comparison OrthancStone/Sources/Volumes/VolumeReslicer.cpp @ 1512:244ad1e4e76a
reorganization of folders
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 07 Jul 2020 16:21:02 +0200 |
parents | Framework/Volumes/VolumeReslicer.cpp@30deba7bc8e2 |
children | 4fb8fdf03314 |
comparison
equal
deleted
inserted
replaced
1511:9dfeee74c1e6 | 1512:244ad1e4e76a |
---|---|
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-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "VolumeReslicer.h" | |
23 | |
24 #include "../Toolbox/GeometryToolbox.h" | |
25 #include "../Toolbox/SubvoxelReader.h" | |
26 | |
27 #include <Images/ImageTraits.h> | |
28 #include <Logging.h> | |
29 #include <OrthancException.h> | |
30 | |
31 #include <boost/math/special_functions/round.hpp> | |
32 | |
33 namespace OrthancStone | |
34 { | |
35 // Anonymous namespace to avoid clashes between compilation modules | |
36 namespace | |
37 { | |
38 enum TransferFunction | |
39 { | |
40 TransferFunction_Copy, | |
41 TransferFunction_Float, | |
42 TransferFunction_Linear | |
43 }; | |
44 | |
45 | |
46 template <Orthanc::PixelFormat InputFormat, | |
47 Orthanc::PixelFormat OutputFormat, | |
48 ImageInterpolation Interpolation, | |
49 TransferFunction Function> | |
50 class PixelShader; | |
51 | |
52 | |
53 template <Orthanc::PixelFormat Format> | |
54 class PixelShader<Format, | |
55 Format, | |
56 ImageInterpolation_Nearest, | |
57 TransferFunction_Copy> | |
58 { | |
59 private: | |
60 typedef SubvoxelReader<Format, ImageInterpolation_Nearest> VoxelReader; | |
61 typedef Orthanc::PixelTraits<Format> PixelWriter; | |
62 | |
63 VoxelReader reader_; | |
64 | |
65 public: | |
66 PixelShader(const ImageBuffer3D& image, | |
67 float /* scaling */, | |
68 float /* offset */) : | |
69 reader_(image) | |
70 { | |
71 } | |
72 | |
73 ORTHANC_FORCE_INLINE | |
74 void Apply(typename PixelWriter::PixelType* pixel, | |
75 float volumeX, | |
76 float volumeY, | |
77 float volumeZ) | |
78 { | |
79 typename VoxelReader::PixelType value; | |
80 | |
81 if (!reader_.GetValue(value, volumeX, volumeY, volumeZ)) | |
82 { | |
83 VoxelReader::Traits::SetMinValue(value); | |
84 } | |
85 | |
86 *pixel = value; | |
87 } | |
88 }; | |
89 | |
90 | |
91 template <Orthanc::PixelFormat InputFormat, | |
92 Orthanc::PixelFormat OutputFormat> | |
93 class PixelShader<InputFormat, | |
94 OutputFormat, | |
95 ImageInterpolation_Nearest, | |
96 TransferFunction_Copy> | |
97 { | |
98 private: | |
99 typedef SubvoxelReader<InputFormat, ImageInterpolation_Nearest> VoxelReader; | |
100 typedef Orthanc::PixelTraits<OutputFormat> PixelWriter; | |
101 | |
102 VoxelReader reader_; | |
103 | |
104 public: | |
105 PixelShader(const ImageBuffer3D& image, | |
106 float /* scaling */, | |
107 float /* offset */) : | |
108 reader_(image) | |
109 { | |
110 } | |
111 | |
112 ORTHANC_FORCE_INLINE | |
113 void Apply(typename PixelWriter::PixelType* pixel, | |
114 float volumeX, | |
115 float volumeY, | |
116 float volumeZ) | |
117 { | |
118 typename VoxelReader::PixelType value; | |
119 | |
120 if (!reader_.GetValue(value, volumeX, volumeY, volumeZ)) | |
121 { | |
122 VoxelReader::Traits::SetMinValue(value); | |
123 } | |
124 | |
125 PixelWriter::FloatToPixel(*pixel, VoxelReader::Traits::PixelToFloat(value)); | |
126 } | |
127 }; | |
128 | |
129 | |
130 template <Orthanc::PixelFormat InputFormat, | |
131 Orthanc::PixelFormat OutputFormat, | |
132 ImageInterpolation Interpolation> | |
133 class PixelShader<InputFormat, | |
134 OutputFormat, | |
135 Interpolation, | |
136 TransferFunction_Float> | |
137 { | |
138 private: | |
139 typedef SubvoxelReader<InputFormat, Interpolation> VoxelReader; | |
140 typedef Orthanc::PixelTraits<OutputFormat> PixelWriter; | |
141 | |
142 VoxelReader reader_; | |
143 float outOfVolume_; | |
144 | |
145 public: | |
146 PixelShader(const ImageBuffer3D& image, | |
147 float /* scaling */, | |
148 float /* offset */) : | |
149 reader_(image), | |
150 outOfVolume_(static_cast<float>(std::numeric_limits<typename VoxelReader::PixelType>::min())) | |
151 { | |
152 } | |
153 | |
154 ORTHANC_FORCE_INLINE | |
155 void Apply(typename PixelWriter::PixelType* pixel, | |
156 float volumeX, | |
157 float volumeY, | |
158 float volumeZ) | |
159 { | |
160 float value; | |
161 | |
162 if (!reader_.GetFloatValue(value, volumeX, volumeY, volumeZ)) | |
163 { | |
164 value = outOfVolume_; | |
165 } | |
166 | |
167 PixelWriter::FloatToPixel(*pixel, value); | |
168 } | |
169 }; | |
170 | |
171 | |
172 template <Orthanc::PixelFormat InputFormat, | |
173 Orthanc::PixelFormat OutputFormat, | |
174 ImageInterpolation Interpolation> | |
175 class PixelShader<InputFormat, | |
176 OutputFormat, | |
177 Interpolation, | |
178 TransferFunction_Linear> | |
179 { | |
180 private: | |
181 typedef SubvoxelReader<InputFormat, Interpolation> VoxelReader; | |
182 typedef Orthanc::PixelTraits<OutputFormat> PixelWriter; | |
183 | |
184 VoxelReader reader_; | |
185 float scaling_; | |
186 float offset_; | |
187 float outOfVolume_; | |
188 | |
189 public: | |
190 PixelShader(const ImageBuffer3D& image, | |
191 float scaling, | |
192 float offset) : | |
193 reader_(image), | |
194 scaling_(scaling), | |
195 offset_(offset), | |
196 outOfVolume_(static_cast<float>(std::numeric_limits<typename VoxelReader::PixelType>::min())) | |
197 { | |
198 } | |
199 | |
200 ORTHANC_FORCE_INLINE | |
201 void Apply(typename PixelWriter::PixelType* pixel, | |
202 float volumeX, | |
203 float volumeY, | |
204 float volumeZ) | |
205 { | |
206 float value; | |
207 | |
208 if (reader_.GetFloatValue(value, volumeX, volumeY, volumeZ)) | |
209 { | |
210 value = scaling_ * value + offset_; | |
211 } | |
212 else | |
213 { | |
214 value = outOfVolume_; | |
215 } | |
216 | |
217 PixelWriter::FloatToPixel(*pixel, value); | |
218 } | |
219 }; | |
220 | |
221 | |
222 | |
223 class FastRowIterator : public boost::noncopyable | |
224 { | |
225 private: | |
226 float position_[3]; | |
227 float offset_[3]; | |
228 | |
229 public: | |
230 FastRowIterator(const Orthanc::ImageAccessor& slice, | |
231 const Extent2D& extent, | |
232 const CoordinateSystem3D& plane, | |
233 const OrientedVolumeBoundingBox& box, | |
234 unsigned int y) | |
235 { | |
236 const double width = static_cast<double>(slice.GetWidth()); | |
237 const double height = static_cast<double>(slice.GetHeight()); | |
238 assert(y < height); | |
239 | |
240 Vector q1 = plane.MapSliceToWorldCoordinates | |
241 (extent.GetX1() + extent.GetWidth() * static_cast<double>(0) / static_cast<double>(width + 1), | |
242 extent.GetY1() + extent.GetHeight() * static_cast<double>(y) / static_cast<double>(height + 1)); | |
243 | |
244 Vector q2 = plane.MapSliceToWorldCoordinates | |
245 (extent.GetX1() + extent.GetWidth() * static_cast<double>(width - 1) / static_cast<double>(width + 1), | |
246 extent.GetY1() + extent.GetHeight() * static_cast<double>(y) / static_cast<double>(height + 1)); | |
247 | |
248 Vector r1, r2; | |
249 box.ToInternalCoordinates(r1, q1); | |
250 box.ToInternalCoordinates(r2, q2); | |
251 | |
252 position_[0] = static_cast<float>(r1[0]); | |
253 position_[1] = static_cast<float>(r1[1]); | |
254 position_[2] = static_cast<float>(r1[2]); | |
255 | |
256 Vector tmp = (r2 - r1) / static_cast<double>(width - 1); | |
257 offset_[0] = static_cast<float>(tmp[0]); | |
258 offset_[1] = static_cast<float>(tmp[1]); | |
259 offset_[2] = static_cast<float>(tmp[2]); | |
260 } | |
261 | |
262 ORTHANC_FORCE_INLINE | |
263 void Next() | |
264 { | |
265 position_[0] += offset_[0]; | |
266 position_[1] += offset_[1]; | |
267 position_[2] += offset_[2]; | |
268 } | |
269 | |
270 ORTHANC_FORCE_INLINE | |
271 void GetVolumeCoordinates(float& x, | |
272 float& y, | |
273 float& z) const | |
274 { | |
275 x = position_[0]; | |
276 y = position_[1]; | |
277 z = position_[2]; | |
278 } | |
279 }; | |
280 | |
281 | |
282 class SlowRowIterator : public boost::noncopyable | |
283 { | |
284 private: | |
285 const Orthanc::ImageAccessor& slice_; | |
286 const Extent2D& extent_; | |
287 const CoordinateSystem3D& plane_; | |
288 const OrientedVolumeBoundingBox& box_; | |
289 unsigned int x_; | |
290 unsigned int y_; | |
291 | |
292 public: | |
293 SlowRowIterator(const Orthanc::ImageAccessor& slice, | |
294 const Extent2D& extent, | |
295 const CoordinateSystem3D& plane, | |
296 const OrientedVolumeBoundingBox& box, | |
297 unsigned int y) : | |
298 slice_(slice), | |
299 extent_(extent), | |
300 plane_(plane), | |
301 box_(box), | |
302 x_(0), | |
303 y_(y) | |
304 { | |
305 assert(y_ < slice_.GetHeight()); | |
306 } | |
307 | |
308 void Next() | |
309 { | |
310 x_++; | |
311 } | |
312 | |
313 void GetVolumeCoordinates(float& x, | |
314 float& y, | |
315 float& z) const | |
316 { | |
317 assert(x_ < slice_.GetWidth()); | |
318 | |
319 const double width = static_cast<double>(slice_.GetWidth()); | |
320 const double height = static_cast<double>(slice_.GetHeight()); | |
321 | |
322 Vector q = plane_.MapSliceToWorldCoordinates | |
323 (extent_.GetX1() + extent_.GetWidth() * static_cast<double>(x_) / (width + 1.0), | |
324 extent_.GetY1() + extent_.GetHeight() * static_cast<double>(y_) / (height + 1.0)); | |
325 | |
326 Vector r; | |
327 box_.ToInternalCoordinates(r, q); | |
328 | |
329 x = static_cast<float>(r[0]); | |
330 y = static_cast<float>(r[1]); | |
331 z = static_cast<float>(r[2]); | |
332 } | |
333 }; | |
334 | |
335 | |
336 template <typename RowIterator, | |
337 Orthanc::PixelFormat InputFormat, | |
338 Orthanc::PixelFormat OutputFormat, | |
339 ImageInterpolation Interpolation, | |
340 TransferFunction Function> | |
341 static void ProcessImage(Orthanc::ImageAccessor& slice, | |
342 const Extent2D& extent, | |
343 const ImageBuffer3D& source, | |
344 const CoordinateSystem3D& plane, | |
345 const OrientedVolumeBoundingBox& box, | |
346 float scaling, | |
347 float offset) | |
348 { | |
349 typedef PixelShader<InputFormat, OutputFormat, Interpolation, Function> Shader; | |
350 | |
351 const unsigned int outputWidth = slice.GetWidth(); | |
352 const unsigned int outputHeight = slice.GetHeight(); | |
353 | |
354 const float sourceWidth = static_cast<float>(source.GetWidth()); | |
355 const float sourceHeight = static_cast<float>(source.GetHeight()); | |
356 const float sourceDepth = static_cast<float>(source.GetDepth()); | |
357 | |
358 Shader shader(source, scaling, offset); | |
359 | |
360 for (unsigned int y = 0; y < outputHeight; y++) | |
361 { | |
362 typedef typename Orthanc::ImageTraits<OutputFormat>::PixelType PixelType; | |
363 PixelType* p = reinterpret_cast<PixelType*>(slice.GetRow(y)); | |
364 | |
365 RowIterator it(slice, extent, plane, box, y); | |
366 | |
367 for (unsigned int x = 0; x < outputWidth; x++, p++) | |
368 { | |
369 float volumeX, volumeY, volumeZ; | |
370 it.GetVolumeCoordinates(volumeX, volumeY, volumeZ); | |
371 | |
372 shader.Apply(p, | |
373 volumeX * sourceWidth, | |
374 volumeY * sourceHeight, | |
375 volumeZ * sourceDepth); | |
376 it.Next(); | |
377 } | |
378 } | |
379 } | |
380 | |
381 | |
382 template <typename RowIterator, | |
383 Orthanc::PixelFormat InputFormat, | |
384 Orthanc::PixelFormat OutputFormat> | |
385 static void ProcessImage(Orthanc::ImageAccessor& slice, | |
386 const Extent2D& extent, | |
387 const ImageBuffer3D& source, | |
388 const CoordinateSystem3D& plane, | |
389 const OrientedVolumeBoundingBox& box, | |
390 ImageInterpolation interpolation, | |
391 bool hasLinearFunction, | |
392 float scaling, | |
393 float offset) | |
394 { | |
395 if (hasLinearFunction) | |
396 { | |
397 switch (interpolation) | |
398 { | |
399 case ImageInterpolation_Nearest: | |
400 ProcessImage<RowIterator, InputFormat, OutputFormat, | |
401 ImageInterpolation_Nearest, TransferFunction_Linear> | |
402 (slice, extent, source, plane, box, scaling, offset); | |
403 break; | |
404 | |
405 case ImageInterpolation_Bilinear: | |
406 ProcessImage<RowIterator, InputFormat, OutputFormat, | |
407 ImageInterpolation_Bilinear, TransferFunction_Linear> | |
408 (slice, extent, source, plane, box, scaling, offset); | |
409 break; | |
410 | |
411 case ImageInterpolation_Trilinear: | |
412 ProcessImage<RowIterator, InputFormat, OutputFormat, | |
413 ImageInterpolation_Trilinear, TransferFunction_Linear> | |
414 (slice, extent, source, plane, box, scaling, offset); | |
415 break; | |
416 | |
417 default: | |
418 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
419 } | |
420 } | |
421 else | |
422 { | |
423 switch (interpolation) | |
424 { | |
425 case ImageInterpolation_Nearest: | |
426 ProcessImage<RowIterator, InputFormat, OutputFormat, | |
427 ImageInterpolation_Nearest, TransferFunction_Copy> | |
428 (slice, extent, source, plane, box, 0, 0); | |
429 break; | |
430 | |
431 case ImageInterpolation_Bilinear: | |
432 ProcessImage<RowIterator, InputFormat, OutputFormat, | |
433 ImageInterpolation_Bilinear, TransferFunction_Float> | |
434 (slice, extent, source, plane, box, 0, 0); | |
435 break; | |
436 | |
437 case ImageInterpolation_Trilinear: | |
438 ProcessImage<RowIterator, InputFormat, OutputFormat, | |
439 ImageInterpolation_Trilinear, TransferFunction_Float> | |
440 (slice, extent, source, plane, box, 0, 0); | |
441 break; | |
442 | |
443 default: | |
444 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
445 } | |
446 } | |
447 } | |
448 | |
449 | |
450 template <typename RowIterator> | |
451 static void ProcessImage(Orthanc::ImageAccessor& slice, | |
452 const Extent2D& extent, | |
453 const ImageBuffer3D& source, | |
454 const CoordinateSystem3D& plane, | |
455 const OrientedVolumeBoundingBox& box, | |
456 ImageInterpolation interpolation, | |
457 bool hasLinearFunction, | |
458 float scaling, | |
459 float offset) | |
460 { | |
461 if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 && | |
462 slice.GetFormat() == Orthanc::PixelFormat_Grayscale8) | |
463 { | |
464 ProcessImage<RowIterator, | |
465 Orthanc::PixelFormat_Grayscale16, | |
466 Orthanc::PixelFormat_Grayscale8> | |
467 (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset); | |
468 } | |
469 else if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 && | |
470 slice.GetFormat() == Orthanc::PixelFormat_Grayscale16) | |
471 { | |
472 ProcessImage<RowIterator, | |
473 Orthanc::PixelFormat_Grayscale16, | |
474 Orthanc::PixelFormat_Grayscale16> | |
475 (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset); | |
476 } | |
477 else if (source.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16 && | |
478 slice.GetFormat() == Orthanc::PixelFormat_BGRA32) | |
479 { | |
480 ProcessImage<RowIterator, | |
481 Orthanc::PixelFormat_SignedGrayscale16, | |
482 Orthanc::PixelFormat_BGRA32> | |
483 (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset); | |
484 } | |
485 else if (source.GetFormat() == Orthanc::PixelFormat_Grayscale16 && | |
486 slice.GetFormat() == Orthanc::PixelFormat_BGRA32) | |
487 { | |
488 ProcessImage<RowIterator, | |
489 Orthanc::PixelFormat_Grayscale16, | |
490 Orthanc::PixelFormat_BGRA32> | |
491 (slice, extent, source, plane, box, interpolation, hasLinearFunction, scaling, offset); | |
492 } | |
493 else | |
494 { | |
495 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
496 } | |
497 } | |
498 } | |
499 | |
500 | |
501 | |
502 void VolumeReslicer::CheckIterators(const ImageBuffer3D& source, | |
503 const CoordinateSystem3D& plane, | |
504 const OrientedVolumeBoundingBox& box) const | |
505 { | |
506 for (unsigned int y = 0; y < slice_->GetHeight(); y++) | |
507 { | |
508 FastRowIterator fast(*slice_, extent_, plane, box, y); | |
509 SlowRowIterator slow(*slice_, extent_, plane, box, y); | |
510 | |
511 for (unsigned int x = 0; x < slice_->GetWidth(); x++) | |
512 { | |
513 float px, py, pz; | |
514 fast.GetVolumeCoordinates(px, py, pz); | |
515 | |
516 float qx, qy, qz; | |
517 slow.GetVolumeCoordinates(qx, qy, qz); | |
518 | |
519 Vector d; | |
520 LinearAlgebra::AssignVector(d, px - qx, py - qy, pz - qz); | |
521 double norm = boost::numeric::ublas::norm_2(d); | |
522 if (norm > 0.0001) | |
523 { | |
524 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
525 } | |
526 | |
527 fast.Next(); | |
528 slow.Next(); | |
529 } | |
530 } | |
531 } | |
532 | |
533 | |
534 void VolumeReslicer::Reset() | |
535 { | |
536 success_ = false; | |
537 extent_.Reset(); | |
538 slice_.reset(NULL); | |
539 } | |
540 | |
541 | |
542 float VolumeReslicer::GetMinOutputValue() const | |
543 { | |
544 switch (outputFormat_) | |
545 { | |
546 case Orthanc::PixelFormat_Grayscale8: | |
547 case Orthanc::PixelFormat_Grayscale16: | |
548 case Orthanc::PixelFormat_BGRA32: | |
549 return 0.0f; | |
550 break; | |
551 | |
552 default: | |
553 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
554 } | |
555 } | |
556 | |
557 | |
558 float VolumeReslicer::GetMaxOutputValue() const | |
559 { | |
560 switch (outputFormat_) | |
561 { | |
562 case Orthanc::PixelFormat_Grayscale8: | |
563 case Orthanc::PixelFormat_BGRA32: | |
564 return static_cast<float>(std::numeric_limits<uint8_t>::max()); | |
565 break; | |
566 | |
567 case Orthanc::PixelFormat_Grayscale16: | |
568 return static_cast<float>(std::numeric_limits<uint16_t>::max()); | |
569 break; | |
570 | |
571 default: | |
572 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
573 } | |
574 } | |
575 | |
576 | |
577 VolumeReslicer::VolumeReslicer() : | |
578 outputFormat_(Orthanc::PixelFormat_Grayscale8), | |
579 interpolation_(ImageInterpolation_Nearest), | |
580 fastMode_(true), | |
581 success_(false) | |
582 { | |
583 ResetLinearFunction(); | |
584 } | |
585 | |
586 | |
587 void VolumeReslicer::GetLinearFunction(float& scaling, | |
588 float& offset) const | |
589 { | |
590 if (hasLinearFunction_) | |
591 { | |
592 scaling = scaling_; | |
593 offset = offset_; | |
594 } | |
595 else | |
596 { | |
597 scaling = 1.0f; | |
598 offset = 0.0f; | |
599 } | |
600 } | |
601 | |
602 | |
603 void VolumeReslicer::ResetLinearFunction() | |
604 { | |
605 Reset(); | |
606 hasLinearFunction_ = false; | |
607 scaling_ = 1.0f; | |
608 offset_ = 0.0f; | |
609 } | |
610 | |
611 | |
612 void VolumeReslicer::SetLinearFunction(float scaling, | |
613 float offset) | |
614 { | |
615 Reset(); | |
616 hasLinearFunction_ = true; | |
617 scaling_ = scaling; | |
618 offset_ = offset; | |
619 } | |
620 | |
621 | |
622 void VolumeReslicer::SetWindow(float low, | |
623 float high) | |
624 { | |
625 //printf("Range in pixel values: %f->%f\n", low, high); | |
626 float scaling = (GetMaxOutputValue() - GetMinOutputValue()) / (high - low); | |
627 float offset = GetMinOutputValue() - scaling * low; | |
628 | |
629 SetLinearFunction(scaling, offset); | |
630 | |
631 /*float x = scaling_ * low + offset_; | |
632 float y = scaling_ * high + offset_; | |
633 printf("%f %f (should be %f->%f)\n", x, y, GetMinOutputValue(), GetMaxOutputValue());*/ | |
634 } | |
635 | |
636 | |
637 void VolumeReslicer::FitRange(const ImageBuffer3D& image) | |
638 { | |
639 float minInputValue, maxInputValue; | |
640 | |
641 if (!image.GetRange(minInputValue, maxInputValue) || | |
642 maxInputValue < 1) | |
643 { | |
644 ResetLinearFunction(); | |
645 } | |
646 else | |
647 { | |
648 SetWindow(minInputValue, maxInputValue); | |
649 } | |
650 } | |
651 | |
652 | |
653 void VolumeReslicer::SetWindowing(ImageWindowing windowing, | |
654 const ImageBuffer3D& image, | |
655 float rescaleSlope, | |
656 float rescaleIntercept) | |
657 { | |
658 if (windowing == ImageWindowing_Custom) | |
659 { | |
660 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
661 } | |
662 | |
663 float center, width; | |
664 ComputeWindowing(center, width, windowing, 0, 0); | |
665 | |
666 float a = (center - width / 2.0f - rescaleIntercept) / rescaleSlope; | |
667 float b = (center + width / 2.0f - rescaleIntercept) / rescaleSlope; | |
668 SetWindow(a, b); | |
669 } | |
670 | |
671 | |
672 void VolumeReslicer::SetOutputFormat(Orthanc::PixelFormat format) | |
673 { | |
674 if (format != Orthanc::PixelFormat_Grayscale8 && | |
675 format != Orthanc::PixelFormat_Grayscale16 && | |
676 format != Orthanc::PixelFormat_BGRA32) | |
677 { | |
678 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
679 } | |
680 | |
681 if (hasLinearFunction_) | |
682 { | |
683 LOG(WARNING) << "Calls to VolumeReslicer::SetOutputFormat() should be done before VolumeReslicer::FitRange()"; | |
684 } | |
685 | |
686 outputFormat_ = format; | |
687 Reset(); | |
688 } | |
689 | |
690 | |
691 void VolumeReslicer::SetInterpolation(ImageInterpolation interpolation) | |
692 { | |
693 if (interpolation != ImageInterpolation_Nearest && | |
694 interpolation != ImageInterpolation_Bilinear && | |
695 interpolation != ImageInterpolation_Trilinear) | |
696 { | |
697 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
698 } | |
699 | |
700 interpolation_ = interpolation; | |
701 Reset(); | |
702 } | |
703 | |
704 | |
705 const Extent2D& VolumeReslicer::GetOutputExtent() const | |
706 { | |
707 if (success_) | |
708 { | |
709 return extent_; | |
710 } | |
711 else | |
712 { | |
713 LOG(ERROR) << "VolumeReslicer::GetOutputExtent(): (!success_)"; | |
714 | |
715 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
716 } | |
717 } | |
718 | |
719 | |
720 const Orthanc::ImageAccessor& VolumeReslicer::GetOutputSlice() const | |
721 { | |
722 if (success_) | |
723 { | |
724 assert(slice_.get() != NULL); | |
725 return *slice_; | |
726 } | |
727 else | |
728 { | |
729 LOG(ERROR) << "VolumeReslicer::GetOutputSlice(): (!success_)"; | |
730 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
731 } | |
732 } | |
733 | |
734 | |
735 Orthanc::ImageAccessor* VolumeReslicer::ReleaseOutputSlice() | |
736 { | |
737 if (success_) | |
738 { | |
739 assert(slice_.get() != NULL); | |
740 success_ = false; | |
741 return slice_.release(); | |
742 } | |
743 else | |
744 { | |
745 LOG(ERROR) << "VolumeReslicer::ReleaseOutputSlice(): (!success_)"; | |
746 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
747 } | |
748 } | |
749 | |
750 | |
751 void VolumeReslicer::Apply(const ImageBuffer3D& source, | |
752 const VolumeImageGeometry& geometry, | |
753 const CoordinateSystem3D& plane) | |
754 { | |
755 // Choose the default voxel size as the finest voxel dimension | |
756 // of the source volumetric image | |
757 const OrthancStone::Vector dim = | |
758 geometry.GetVoxelDimensions(OrthancStone::VolumeProjection_Axial); | |
759 double voxelSize = dim[0]; | |
760 | |
761 if (dim[1] < voxelSize) | |
762 { | |
763 voxelSize = dim[1]; | |
764 } | |
765 | |
766 if (dim[2] < voxelSize) | |
767 { | |
768 voxelSize = dim[2]; | |
769 } | |
770 | |
771 if (voxelSize <= 0) | |
772 { | |
773 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
774 } | |
775 | |
776 Apply(source, geometry, plane, voxelSize); | |
777 } | |
778 | |
779 | |
780 void VolumeReslicer::Apply(const ImageBuffer3D& source, | |
781 const VolumeImageGeometry& geometry, | |
782 const CoordinateSystem3D& plane, | |
783 double voxelSize) | |
784 { | |
785 Reset(); | |
786 pixelSpacing_ = voxelSize; | |
787 | |
788 // Firstly, compute the intersection of the source volumetric | |
789 // image with the reslicing plane. This leads to a polygon with 3 | |
790 // to 6 vertices. We compute the extent of the intersection | |
791 // polygon, with respect to the coordinate system of the reslicing | |
792 // plane. | |
793 OrientedVolumeBoundingBox box(geometry); | |
794 | |
795 if (!box.ComputeExtent(extent_, plane)) | |
796 { | |
797 // The plane does not intersect with the bounding box of the volume | |
798 slice_.reset(new Orthanc::Image(outputFormat_, 0, 0, false)); | |
799 success_ = true; | |
800 return; | |
801 } | |
802 | |
803 // Secondly, the extent together with the voxel size gives the | |
804 // size of the output image | |
805 unsigned int width = boost::math::iround(extent_.GetWidth() / voxelSize); | |
806 unsigned int height = boost::math::iround(extent_.GetHeight() / voxelSize); | |
807 | |
808 slice_.reset(new Orthanc::Image(outputFormat_, width, height, false)); | |
809 | |
810 //CheckIterators(source, plane, box); | |
811 | |
812 if (fastMode_) | |
813 { | |
814 ProcessImage<FastRowIterator>(*slice_, extent_, source, plane, box, | |
815 interpolation_, hasLinearFunction_, scaling_, offset_); | |
816 } | |
817 else | |
818 { | |
819 ProcessImage<SlowRowIterator>(*slice_, extent_, source, plane, box, | |
820 interpolation_, hasLinearFunction_, scaling_, offset_); | |
821 } | |
822 | |
823 success_ = true; | |
824 } | |
825 | |
826 | |
827 double VolumeReslicer::GetPixelSpacing() const | |
828 { | |
829 if (success_) | |
830 { | |
831 return pixelSpacing_; | |
832 } | |
833 else | |
834 { | |
835 LOG(ERROR) << "VolumeReslicer::GetPixelSpacing(): (!success_)"; | |
836 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
837 } | |
838 } | |
839 } |