comparison OrthancFramework/Sources/Images/ImageProcessing.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/Images/ImageProcessing.cpp@2a170a8f1faf
children 55727d85f419
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
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 General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../PrecompiledHeaders.h"
35 #include "ImageProcessing.h"
36
37 #include "Image.h"
38 #include "ImageTraits.h"
39 #include "PixelTraits.h"
40 #include "../OrthancException.h"
41
42 #ifdef __EMSCRIPTEN__
43 /*
44 Avoid this error:
45 -----------------
46 .../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits<long long>::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion]
47 .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:333:28: note: in instantiation of function template specialization 'boost::math::llround<float>' requested here
48 .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:1006:9: note: in instantiation of function template specialization 'Orthanc::MultiplyConstantInternal<unsigned char, true>' requested here
49 */
50 #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion"
51 #endif
52
53 #include <boost/math/special_functions/round.hpp>
54
55 #include <cassert>
56 #include <string.h>
57 #include <limits>
58 #include <stdint.h>
59 #include <algorithm>
60
61 namespace Orthanc
62 {
63 double ImageProcessing::ImagePoint::GetDistanceTo(const ImagePoint& other) const
64 {
65 double dx = (double)(other.GetX() - GetX());
66 double dy = (double)(other.GetY() - GetY());
67 return sqrt(dx * dx + dy * dy);
68 }
69
70 double ImageProcessing::ImagePoint::GetDistanceToLine(double a, double b, double c) const // where ax + by + c = 0 is the equation of the line
71 {
72 return std::abs(a * static_cast<double>(GetX()) + b * static_cast<double>(GetY()) + c) / pow(a * a + b * b, 0.5);
73 }
74
75 template <typename TargetType, typename SourceType>
76 static void ConvertInternal(ImageAccessor& target,
77 const ImageAccessor& source)
78 {
79 // WARNING - "::min()" should be replaced by "::lowest()" if
80 // dealing with float or double (which is not the case so far)
81 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double"
82 const TargetType minValue = std::numeric_limits<TargetType>::min();
83 const TargetType maxValue = std::numeric_limits<TargetType>::max();
84
85 const unsigned int width = source.GetWidth();
86 const unsigned int height = source.GetHeight();
87
88 for (unsigned int y = 0; y < height; y++)
89 {
90 TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
91 const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
92
93 for (unsigned int x = 0; x < width; x++, t++, s++)
94 {
95 if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue))
96 {
97 *t = minValue;
98 }
99 else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue))
100 {
101 *t = maxValue;
102 }
103 else
104 {
105 *t = static_cast<TargetType>(*s);
106 }
107 }
108 }
109 }
110
111
112 template <typename SourceType>
113 static void ConvertGrayscaleToFloat(ImageAccessor& target,
114 const ImageAccessor& source)
115 {
116 assert(sizeof(float) == 4);
117
118 const unsigned int width = source.GetWidth();
119 const unsigned int height = source.GetHeight();
120
121 for (unsigned int y = 0; y < height; y++)
122 {
123 float* t = reinterpret_cast<float*>(target.GetRow(y));
124 const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
125
126 for (unsigned int x = 0; x < width; x++, t++, s++)
127 {
128 *t = static_cast<float>(*s);
129 }
130 }
131 }
132
133
134 template <PixelFormat TargetFormat>
135 static void ConvertFloatToGrayscale(ImageAccessor& target,
136 const ImageAccessor& source)
137 {
138 typedef typename PixelTraits<TargetFormat>::PixelType TargetType;
139
140 assert(sizeof(float) == 4);
141
142 const unsigned int width = source.GetWidth();
143 const unsigned int height = source.GetHeight();
144
145 for (unsigned int y = 0; y < height; y++)
146 {
147 TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y));
148 const float* p = reinterpret_cast<const float*>(source.GetConstRow(y));
149
150 for (unsigned int x = 0; x < width; x++, p++, q++)
151 {
152 PixelTraits<TargetFormat>::FloatToPixel(*q, *p);
153 }
154 }
155 }
156
157
158 template <typename TargetType>
159 static void ConvertColorToGrayscale(ImageAccessor& target,
160 const ImageAccessor& source)
161 {
162 assert(source.GetFormat() == PixelFormat_RGB24);
163
164 // WARNING - "::min()" should be replaced by "::lowest()" if
165 // dealing with float or double (which is not the case so far)
166 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double"
167 const TargetType minValue = std::numeric_limits<TargetType>::min();
168 const TargetType maxValue = std::numeric_limits<TargetType>::max();
169
170 const unsigned int width = source.GetWidth();
171 const unsigned int height = source.GetHeight();
172
173 for (unsigned int y = 0; y < height; y++)
174 {
175 TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
176 const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
177
178 for (unsigned int x = 0; x < width; x++, t++, s += 3)
179 {
180 // Y = 0.2126 R + 0.7152 G + 0.0722 B
181 int32_t v = (2126 * static_cast<int32_t>(s[0]) +
182 7152 * static_cast<int32_t>(s[1]) +
183 0722 * static_cast<int32_t>(s[2])) / 10000;
184
185 if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
186 {
187 *t = minValue;
188 }
189 else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
190 {
191 *t = maxValue;
192 }
193 else
194 {
195 *t = static_cast<TargetType>(v);
196 }
197 }
198 }
199 }
200
201
202 static void MemsetZeroInternal(ImageAccessor& image)
203 {
204 const unsigned int height = image.GetHeight();
205 const size_t lineSize = image.GetBytesPerPixel() * image.GetWidth();
206 const size_t pitch = image.GetPitch();
207
208 uint8_t *p = reinterpret_cast<uint8_t*>(image.GetBuffer());
209
210 for (unsigned int y = 0; y < height; y++)
211 {
212 memset(p, 0, lineSize);
213 p += pitch;
214 }
215 }
216
217
218 template <typename PixelType>
219 static void SetInternal(ImageAccessor& image,
220 int64_t constant)
221 {
222 if (constant == 0 &&
223 (image.GetFormat() == PixelFormat_Grayscale8 ||
224 image.GetFormat() == PixelFormat_Grayscale16 ||
225 image.GetFormat() == PixelFormat_Grayscale32 ||
226 image.GetFormat() == PixelFormat_Grayscale64 ||
227 image.GetFormat() == PixelFormat_SignedGrayscale16))
228 {
229 MemsetZeroInternal(image);
230 }
231 else
232 {
233 const unsigned int width = image.GetWidth();
234 const unsigned int height = image.GetHeight();
235
236 for (unsigned int y = 0; y < height; y++)
237 {
238 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
239
240 for (unsigned int x = 0; x < width; x++, p++)
241 {
242 *p = static_cast<PixelType>(constant);
243 }
244 }
245 }
246 }
247
248
249 template <typename PixelType>
250 static void GetMinMaxValueInternal(PixelType& minValue,
251 PixelType& maxValue,
252 const ImageAccessor& source,
253 const PixelType LowestValue = std::numeric_limits<PixelType>::min())
254 {
255 // Deal with the special case of empty image
256 if (source.GetWidth() == 0 ||
257 source.GetHeight() == 0)
258 {
259 minValue = 0;
260 maxValue = 0;
261 return;
262 }
263
264 minValue = std::numeric_limits<PixelType>::max();
265 maxValue = LowestValue;
266
267 const unsigned int height = source.GetHeight();
268 const unsigned int width = source.GetWidth();
269
270 for (unsigned int y = 0; y < height; y++)
271 {
272 const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
273
274 for (unsigned int x = 0; x < width; x++, p++)
275 {
276 if (*p < minValue)
277 {
278 minValue = *p;
279 }
280
281 if (*p > maxValue)
282 {
283 maxValue = *p;
284 }
285 }
286 }
287 }
288
289
290
291 template <typename PixelType>
292 static void AddConstantInternal(ImageAccessor& image,
293 int64_t constant)
294 {
295 if (constant == 0)
296 {
297 return;
298 }
299
300 // WARNING - "::min()" should be replaced by "::lowest()" if
301 // dealing with float or double (which is not the case so far)
302 assert(sizeof(PixelType) <= 2); // Safeguard to remember about "float/double"
303 const int64_t minValue = std::numeric_limits<PixelType>::min();
304 const int64_t maxValue = std::numeric_limits<PixelType>::max();
305
306 const unsigned int width = image.GetWidth();
307 const unsigned int height = image.GetHeight();
308
309 for (unsigned int y = 0; y < height; y++)
310 {
311 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
312
313 for (unsigned int x = 0; x < width; x++, p++)
314 {
315 int64_t v = static_cast<int64_t>(*p) + constant;
316
317 if (v > maxValue)
318 {
319 *p = std::numeric_limits<PixelType>::max();
320 }
321 else if (v < minValue)
322 {
323 *p = std::numeric_limits<PixelType>::min();
324 }
325 else
326 {
327 *p = static_cast<PixelType>(v);
328 }
329 }
330 }
331 }
332
333
334
335 template <typename PixelType,
336 bool UseRound>
337 static void MultiplyConstantInternal(ImageAccessor& image,
338 float factor)
339 {
340 if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
341 {
342 return;
343 }
344
345 // WARNING - "::min()" should be replaced by "::lowest()" if
346 // dealing with float or double (which is not the case so far)
347 assert(sizeof(PixelType) <= 2); // Safeguard to remember about "float/double"
348 const int64_t minValue = std::numeric_limits<PixelType>::min();
349 const int64_t maxValue = std::numeric_limits<PixelType>::max();
350
351 const unsigned int width = image.GetWidth();
352 const unsigned int height = image.GetHeight();
353
354 for (unsigned int y = 0; y < height; y++)
355 {
356 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
357
358 for (unsigned int x = 0; x < width; x++, p++)
359 {
360 int64_t v;
361 if (UseRound)
362 {
363 // The "round" operation is very costly
364 v = boost::math::llround(static_cast<float>(*p) * factor);
365 }
366 else
367 {
368 v = static_cast<int64_t>(static_cast<float>(*p) * factor);
369 }
370
371 if (v > maxValue)
372 {
373 *p = std::numeric_limits<PixelType>::max();
374 }
375 else if (v < minValue)
376 {
377 *p = std::numeric_limits<PixelType>::min();
378 }
379 else
380 {
381 *p = static_cast<PixelType>(v);
382 }
383 }
384 }
385 }
386
387
388 // Computes "a * x + b" at each pixel => Note that this is not the
389 // same convention as in "ShiftScale()"
390 template <typename TargetType,
391 typename SourceType,
392 bool UseRound,
393 bool Invert>
394 static void ShiftScaleInternal(ImageAccessor& target,
395 const ImageAccessor& source,
396 float a,
397 float b,
398 const TargetType LowestValue)
399 // This function can be applied inplace (source == target)
400 {
401 if (source.GetWidth() != target.GetWidth() ||
402 source.GetHeight() != target.GetHeight())
403 {
404 throw OrthancException(ErrorCode_IncompatibleImageSize);
405 }
406
407 if (&source == &target &&
408 source.GetFormat() != target.GetFormat())
409 {
410 throw OrthancException(ErrorCode_IncompatibleImageFormat);
411 }
412
413 const TargetType minPixelValue = LowestValue;
414 const TargetType maxPixelValue = std::numeric_limits<TargetType>::max();
415 const float minFloatValue = static_cast<float>(LowestValue);
416 const float maxFloatValue = static_cast<float>(maxPixelValue);
417
418 const unsigned int height = target.GetHeight();
419 const unsigned int width = target.GetWidth();
420
421 for (unsigned int y = 0; y < height; y++)
422 {
423 TargetType* p = reinterpret_cast<TargetType*>(target.GetRow(y));
424 const SourceType* q = reinterpret_cast<const SourceType*>(source.GetRow(y));
425
426 for (unsigned int x = 0; x < width; x++, p++, q++)
427 {
428 float v = a * static_cast<float>(*q) + b;
429
430 if (v >= maxFloatValue)
431 {
432 *p = maxPixelValue;
433 }
434 else if (v <= minFloatValue)
435 {
436 *p = minPixelValue;
437 }
438 else if (UseRound)
439 {
440 // The "round" operation is very costly
441 *p = static_cast<TargetType>(boost::math::iround(v));
442 }
443 else
444 {
445 *p = static_cast<TargetType>(std::floor(v));
446 }
447
448 if (Invert)
449 {
450 *p = maxPixelValue - *p;
451 }
452 }
453 }
454 }
455
456 template <typename PixelType>
457 static void ShiftRightInternal(ImageAccessor& image,
458 unsigned int shift)
459 {
460 const unsigned int height = image.GetHeight();
461 const unsigned int width = image.GetWidth();
462
463 for (unsigned int y = 0; y < height; y++)
464 {
465 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
466
467 for (unsigned int x = 0; x < width; x++, p++)
468 {
469 *p = *p >> shift;
470 }
471 }
472 }
473
474 template <typename PixelType>
475 static void ShiftLeftInternal(ImageAccessor& image,
476 unsigned int shift)
477 {
478 const unsigned int height = image.GetHeight();
479 const unsigned int width = image.GetWidth();
480
481 for (unsigned int y = 0; y < height; y++)
482 {
483 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
484
485 for (unsigned int x = 0; x < width; x++, p++)
486 {
487 *p = *p << shift;
488 }
489 }
490 }
491
492 void ImageProcessing::Copy(ImageAccessor& target,
493 const ImageAccessor& source)
494 {
495 if (target.GetWidth() != source.GetWidth() ||
496 target.GetHeight() != source.GetHeight())
497 {
498 throw OrthancException(ErrorCode_IncompatibleImageSize);
499 }
500
501 if (target.GetFormat() != source.GetFormat())
502 {
503 throw OrthancException(ErrorCode_IncompatibleImageFormat);
504 }
505
506 unsigned int lineSize = source.GetBytesPerPixel() * source.GetWidth();
507
508 assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize);
509
510 for (unsigned int y = 0; y < source.GetHeight(); y++)
511 {
512 memcpy(target.GetRow(y), source.GetConstRow(y), lineSize);
513 }
514 }
515
516 template <typename TargetType, typename SourceType>
517 static void ApplyWindowingInternal(ImageAccessor& target,
518 const ImageAccessor& source,
519 float windowCenter,
520 float windowWidth,
521 float rescaleSlope,
522 float rescaleIntercept,
523 bool invert)
524 {
525 assert(sizeof(SourceType) == source.GetBytesPerPixel() &&
526 sizeof(TargetType) == target.GetBytesPerPixel());
527
528 // WARNING - "::min()" should be replaced by "::lowest()" if
529 // dealing with float or double (which is not the case so far)
530 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double"
531 const TargetType minTargetValue = std::numeric_limits<TargetType>::min();
532 const TargetType maxTargetValue = std::numeric_limits<TargetType>::max();
533 const float maxFloatValue = static_cast<float>(maxTargetValue);
534
535 const float windowIntercept = windowCenter - windowWidth / 2.0f;
536 const float windowSlope = (maxFloatValue + 1.0f) / windowWidth;
537
538 const float a = rescaleSlope * windowSlope;
539 const float b = (rescaleIntercept - windowIntercept) * windowSlope;
540
541 if (invert)
542 {
543 ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue);
544 }
545 else
546 {
547 ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue);
548 }
549 }
550
551 void ImageProcessing::ApplyWindowing_Deprecated(ImageAccessor& target,
552 const ImageAccessor& source,
553 float windowCenter,
554 float windowWidth,
555 float rescaleSlope,
556 float rescaleIntercept,
557 bool invert)
558 {
559 if (target.GetWidth() != source.GetWidth() ||
560 target.GetHeight() != source.GetHeight())
561 {
562 throw OrthancException(ErrorCode_IncompatibleImageSize);
563 }
564
565 switch (source.GetFormat())
566 {
567 case Orthanc::PixelFormat_Float32:
568 {
569 switch (target.GetFormat())
570 {
571 case Orthanc::PixelFormat_Grayscale8:
572 ApplyWindowingInternal<uint8_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
573 break;
574 case Orthanc::PixelFormat_Grayscale16:
575 ApplyWindowingInternal<uint16_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
576 break;
577 default:
578 throw OrthancException(ErrorCode_NotImplemented);
579 }
580 };break;
581 case Orthanc::PixelFormat_Grayscale8:
582 {
583 switch (target.GetFormat())
584 {
585 case Orthanc::PixelFormat_Grayscale8:
586 ApplyWindowingInternal<uint8_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
587 break;
588 case Orthanc::PixelFormat_Grayscale16:
589 ApplyWindowingInternal<uint16_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
590 break;
591 default:
592 throw OrthancException(ErrorCode_NotImplemented);
593 }
594 };break;
595 case Orthanc::PixelFormat_Grayscale16:
596 {
597 switch (target.GetFormat())
598 {
599 case Orthanc::PixelFormat_Grayscale8:
600 ApplyWindowingInternal<uint8_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
601 break;
602 case Orthanc::PixelFormat_Grayscale16:
603 ApplyWindowingInternal<uint16_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
604 break;
605 default:
606 throw OrthancException(ErrorCode_NotImplemented);
607 }
608 };break;
609 default:
610 throw OrthancException(ErrorCode_NotImplemented);
611 }
612 }
613
614
615 void ImageProcessing::Convert(ImageAccessor& target,
616 const ImageAccessor& source)
617 {
618 if (target.GetWidth() != source.GetWidth() ||
619 target.GetHeight() != source.GetHeight())
620 {
621 throw OrthancException(ErrorCode_IncompatibleImageSize);
622 }
623
624 const unsigned int width = source.GetWidth();
625 const unsigned int height = source.GetHeight();
626
627 if (source.GetFormat() == target.GetFormat())
628 {
629 Copy(target, source);
630 return;
631 }
632
633 if (target.GetFormat() == PixelFormat_Grayscale16 &&
634 source.GetFormat() == PixelFormat_Grayscale8)
635 {
636 ConvertInternal<uint16_t, uint8_t>(target, source);
637 return;
638 }
639
640 if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
641 source.GetFormat() == PixelFormat_Grayscale8)
642 {
643 ConvertInternal<int16_t, uint8_t>(target, source);
644 return;
645 }
646
647 if (target.GetFormat() == PixelFormat_Grayscale8 &&
648 source.GetFormat() == PixelFormat_Grayscale16)
649 {
650 ConvertInternal<uint8_t, uint16_t>(target, source);
651 return;
652 }
653
654 if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
655 source.GetFormat() == PixelFormat_Grayscale16)
656 {
657 ConvertInternal<int16_t, uint16_t>(target, source);
658 return;
659 }
660
661 if (target.GetFormat() == PixelFormat_Grayscale8 &&
662 source.GetFormat() == PixelFormat_SignedGrayscale16)
663 {
664 ConvertInternal<uint8_t, int16_t>(target, source);
665 return;
666 }
667
668 if (target.GetFormat() == PixelFormat_Grayscale16 &&
669 source.GetFormat() == PixelFormat_SignedGrayscale16)
670 {
671 ConvertInternal<uint16_t, int16_t>(target, source);
672 return;
673 }
674
675 if (target.GetFormat() == PixelFormat_Grayscale8 &&
676 source.GetFormat() == PixelFormat_RGB24)
677 {
678 ConvertColorToGrayscale<uint8_t>(target, source);
679 return;
680 }
681
682 if (target.GetFormat() == PixelFormat_Grayscale16 &&
683 source.GetFormat() == PixelFormat_RGB24)
684 {
685 ConvertColorToGrayscale<uint16_t>(target, source);
686 return;
687 }
688
689 if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
690 source.GetFormat() == PixelFormat_RGB24)
691 {
692 ConvertColorToGrayscale<int16_t>(target, source);
693 return;
694 }
695
696 if (target.GetFormat() == PixelFormat_Float32 &&
697 source.GetFormat() == PixelFormat_Grayscale8)
698 {
699 ConvertGrayscaleToFloat<uint8_t>(target, source);
700 return;
701 }
702
703 if (target.GetFormat() == PixelFormat_Float32 &&
704 source.GetFormat() == PixelFormat_Grayscale16)
705 {
706 ConvertGrayscaleToFloat<uint16_t>(target, source);
707 return;
708 }
709
710 if (target.GetFormat() == PixelFormat_Float32 &&
711 source.GetFormat() == PixelFormat_Grayscale32)
712 {
713 ConvertGrayscaleToFloat<uint32_t>(target, source);
714 return;
715 }
716
717 if (target.GetFormat() == PixelFormat_Float32 &&
718 source.GetFormat() == PixelFormat_SignedGrayscale16)
719 {
720 ConvertGrayscaleToFloat<int16_t>(target, source);
721 return;
722 }
723
724
725 if (target.GetFormat() == PixelFormat_Grayscale8 &&
726 source.GetFormat() == PixelFormat_RGBA32)
727 {
728 for (unsigned int y = 0; y < height; y++)
729 {
730 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
731 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
732 for (unsigned int x = 0; x < width; x++, q++)
733 {
734 *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) +
735 7152 * static_cast<uint32_t>(p[1]) +
736 0722 * static_cast<uint32_t>(p[2])) / 10000);
737 p += 4;
738 }
739 }
740
741 return;
742 }
743
744 if (target.GetFormat() == PixelFormat_Grayscale8 &&
745 source.GetFormat() == PixelFormat_BGRA32)
746 {
747 for (unsigned int y = 0; y < height; y++)
748 {
749 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
750 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
751 for (unsigned int x = 0; x < width; x++, q++)
752 {
753 *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[2]) +
754 7152 * static_cast<uint32_t>(p[1]) +
755 0722 * static_cast<uint32_t>(p[0])) / 10000);
756 p += 4;
757 }
758 }
759
760 return;
761 }
762
763 if (target.GetFormat() == PixelFormat_RGB24 &&
764 source.GetFormat() == PixelFormat_RGBA32)
765 {
766 for (unsigned int y = 0; y < height; y++)
767 {
768 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
769 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
770 for (unsigned int x = 0; x < width; x++)
771 {
772 q[0] = p[0];
773 q[1] = p[1];
774 q[2] = p[2];
775 p += 4;
776 q += 3;
777 }
778 }
779
780 return;
781 }
782
783 if (target.GetFormat() == PixelFormat_RGB24 &&
784 source.GetFormat() == PixelFormat_BGRA32)
785 {
786 for (unsigned int y = 0; y < height; y++)
787 {
788 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
789 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
790 for (unsigned int x = 0; x < width; x++)
791 {
792 q[0] = p[2];
793 q[1] = p[1];
794 q[2] = p[0];
795 p += 4;
796 q += 3;
797 }
798 }
799
800 return;
801 }
802
803 if (target.GetFormat() == PixelFormat_RGBA32 &&
804 source.GetFormat() == PixelFormat_RGB24)
805 {
806 for (unsigned int y = 0; y < height; y++)
807 {
808 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
809 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
810 for (unsigned int x = 0; x < width; x++)
811 {
812 q[0] = p[0];
813 q[1] = p[1];
814 q[2] = p[2];
815 q[3] = 255; // Set the alpha channel to full opacity
816 p += 3;
817 q += 4;
818 }
819 }
820
821 return;
822 }
823
824 if (target.GetFormat() == PixelFormat_RGB24 &&
825 source.GetFormat() == PixelFormat_Grayscale8)
826 {
827 for (unsigned int y = 0; y < height; y++)
828 {
829 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
830 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
831 for (unsigned int x = 0; x < width; x++)
832 {
833 q[0] = *p;
834 q[1] = *p;
835 q[2] = *p;
836 p += 1;
837 q += 3;
838 }
839 }
840
841 return;
842 }
843
844 if ((target.GetFormat() == PixelFormat_RGBA32 ||
845 target.GetFormat() == PixelFormat_BGRA32) &&
846 source.GetFormat() == PixelFormat_Grayscale8)
847 {
848 for (unsigned int y = 0; y < height; y++)
849 {
850 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
851 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
852 for (unsigned int x = 0; x < width; x++)
853 {
854 q[0] = *p;
855 q[1] = *p;
856 q[2] = *p;
857 q[3] = 255;
858 p += 1;
859 q += 4;
860 }
861 }
862
863 return;
864 }
865
866 if (target.GetFormat() == PixelFormat_BGRA32 &&
867 source.GetFormat() == PixelFormat_Grayscale16)
868 {
869 for (unsigned int y = 0; y < height; y++)
870 {
871 const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y));
872 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
873 for (unsigned int x = 0; x < width; x++)
874 {
875 uint8_t value = (*p < 256 ? *p : 255);
876 q[0] = value;
877 q[1] = value;
878 q[2] = value;
879 q[3] = 255;
880 p += 1;
881 q += 4;
882 }
883 }
884
885 return;
886 }
887
888 if (target.GetFormat() == PixelFormat_BGRA32 &&
889 source.GetFormat() == PixelFormat_SignedGrayscale16)
890 {
891 for (unsigned int y = 0; y < height; y++)
892 {
893 const int16_t* p = reinterpret_cast<const int16_t*>(source.GetConstRow(y));
894 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
895 for (unsigned int x = 0; x < width; x++)
896 {
897 uint8_t value;
898 if (*p < 0)
899 {
900 value = 0;
901 }
902 else if (*p > 255)
903 {
904 value = 255;
905 }
906 else
907 {
908 value = static_cast<uint8_t>(*p);
909 }
910
911 q[0] = value;
912 q[1] = value;
913 q[2] = value;
914 q[3] = 255;
915 p += 1;
916 q += 4;
917 }
918 }
919
920 return;
921 }
922
923 if (target.GetFormat() == PixelFormat_BGRA32 &&
924 source.GetFormat() == PixelFormat_RGB24)
925 {
926 for (unsigned int y = 0; y < height; y++)
927 {
928 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
929 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
930 for (unsigned int x = 0; x < width; x++)
931 {
932 q[0] = p[2];
933 q[1] = p[1];
934 q[2] = p[0];
935 q[3] = 255;
936 p += 3;
937 q += 4;
938 }
939 }
940
941 return;
942 }
943
944 if ((target.GetFormat() == PixelFormat_BGRA32 &&
945 source.GetFormat() == PixelFormat_RGBA32)
946 || (target.GetFormat() == PixelFormat_RGBA32 &&
947 source.GetFormat() == PixelFormat_BGRA32))
948 {
949 for (unsigned int y = 0; y < height; y++)
950 {
951 const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
952 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
953 for (unsigned int x = 0; x < width; x++)
954 {
955 q[0] = p[2];
956 q[1] = p[1];
957 q[2] = p[0];
958 q[3] = p[3];
959 p += 4;
960 q += 4;
961 }
962 }
963
964 return;
965 }
966
967 if (target.GetFormat() == PixelFormat_RGB24 &&
968 source.GetFormat() == PixelFormat_RGB48)
969 {
970 for (unsigned int y = 0; y < height; y++)
971 {
972 const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y));
973 uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
974 for (unsigned int x = 0; x < width; x++)
975 {
976 q[0] = p[0] >> 8;
977 q[1] = p[1] >> 8;
978 q[2] = p[2] >> 8;
979 p += 3;
980 q += 3;
981 }
982 }
983
984 return;
985 }
986
987 if (target.GetFormat() == PixelFormat_Grayscale16 &&
988 source.GetFormat() == PixelFormat_Float32)
989 {
990 ConvertFloatToGrayscale<PixelFormat_Grayscale16>(target, source);
991 return;
992 }
993
994 if (target.GetFormat() == PixelFormat_Grayscale8 &&
995 source.GetFormat() == PixelFormat_Float32)
996 {
997 ConvertFloatToGrayscale<PixelFormat_Grayscale8>(target, source);
998 return;
999 }
1000
1001 throw OrthancException(ErrorCode_NotImplemented);
1002 }
1003
1004
1005
1006 void ImageProcessing::Set(ImageAccessor& image,
1007 int64_t value)
1008 {
1009 switch (image.GetFormat())
1010 {
1011 case PixelFormat_Grayscale8:
1012 SetInternal<uint8_t>(image, value);
1013 return;
1014
1015 case PixelFormat_Grayscale16:
1016 SetInternal<uint16_t>(image, value);
1017 return;
1018
1019 case PixelFormat_Grayscale32:
1020 SetInternal<uint32_t>(image, value);
1021 return;
1022
1023 case PixelFormat_Grayscale64:
1024 SetInternal<uint64_t>(image, value);
1025 return;
1026
1027 case PixelFormat_SignedGrayscale16:
1028 SetInternal<int16_t>(image, value);
1029 return;
1030
1031 case PixelFormat_Float32:
1032 assert(sizeof(float) == 4);
1033 SetInternal<float>(image, value);
1034 return;
1035
1036 case PixelFormat_RGBA32:
1037 case PixelFormat_BGRA32:
1038 case PixelFormat_RGB24:
1039 {
1040 uint8_t v = static_cast<uint8_t>(value);
1041 Set(image, v, v, v, v); // Use the color version
1042 return;
1043 }
1044
1045 default:
1046 throw OrthancException(ErrorCode_NotImplemented);
1047 }
1048 }
1049
1050
1051 void ImageProcessing::Set(ImageAccessor& image,
1052 uint8_t red,
1053 uint8_t green,
1054 uint8_t blue,
1055 uint8_t alpha)
1056 {
1057 uint8_t p[4];
1058 unsigned int size;
1059
1060 switch (image.GetFormat())
1061 {
1062 case PixelFormat_RGBA32:
1063 p[0] = red;
1064 p[1] = green;
1065 p[2] = blue;
1066 p[3] = alpha;
1067 size = 4;
1068 break;
1069
1070 case PixelFormat_BGRA32:
1071 p[0] = blue;
1072 p[1] = green;
1073 p[2] = red;
1074 p[3] = alpha;
1075 size = 4;
1076 break;
1077
1078 case PixelFormat_RGB24:
1079 p[0] = red;
1080 p[1] = green;
1081 p[2] = blue;
1082 size = 3;
1083 break;
1084
1085 default:
1086 throw OrthancException(ErrorCode_NotImplemented);
1087 }
1088
1089 const unsigned int width = image.GetWidth();
1090 const unsigned int height = image.GetHeight();
1091
1092 for (unsigned int y = 0; y < height; y++)
1093 {
1094 uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
1095
1096 for (unsigned int x = 0; x < width; x++)
1097 {
1098 for (unsigned int i = 0; i < size; i++)
1099 {
1100 q[i] = p[i];
1101 }
1102
1103 q += size;
1104 }
1105 }
1106 }
1107
1108 void ImageProcessing::Set(ImageAccessor& image,
1109 uint8_t red,
1110 uint8_t green,
1111 uint8_t blue,
1112 ImageAccessor& alpha)
1113 {
1114 uint8_t p[4];
1115
1116 if (alpha.GetWidth() != image.GetWidth() || alpha.GetHeight() != image.GetHeight())
1117 {
1118 throw OrthancException(ErrorCode_IncompatibleImageSize);
1119 }
1120
1121 if (alpha.GetFormat() != PixelFormat_Grayscale8)
1122 {
1123 throw OrthancException(ErrorCode_NotImplemented);
1124 }
1125
1126 switch (image.GetFormat())
1127 {
1128 case PixelFormat_RGBA32:
1129 p[0] = red;
1130 p[1] = green;
1131 p[2] = blue;
1132 break;
1133
1134 case PixelFormat_BGRA32:
1135 p[0] = blue;
1136 p[1] = green;
1137 p[2] = red;
1138 break;
1139
1140 default:
1141 throw OrthancException(ErrorCode_NotImplemented);
1142 }
1143
1144 const unsigned int width = image.GetWidth();
1145 const unsigned int height = image.GetHeight();
1146
1147 for (unsigned int y = 0; y < height; y++)
1148 {
1149 uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
1150 uint8_t* a = reinterpret_cast<uint8_t*>(alpha.GetRow(y));
1151
1152 for (unsigned int x = 0; x < width; x++)
1153 {
1154 for (unsigned int i = 0; i < 3; i++)
1155 {
1156 q[i] = p[i];
1157 }
1158 q[3] = *a;
1159 q += 4;
1160 ++a;
1161 }
1162 }
1163 }
1164
1165
1166 void ImageProcessing::ShiftRight(ImageAccessor& image,
1167 unsigned int shift)
1168 {
1169 if (image.GetWidth() == 0 ||
1170 image.GetHeight() == 0 ||
1171 shift == 0)
1172 {
1173 // Nothing to do
1174 return;
1175 }
1176
1177 switch (image.GetFormat())
1178 {
1179 case PixelFormat_Grayscale8:
1180 {
1181 ShiftRightInternal<uint8_t>(image, shift);
1182 break;
1183 }
1184
1185 case PixelFormat_Grayscale16:
1186 {
1187 ShiftRightInternal<uint16_t>(image, shift);
1188 break;
1189 }
1190 default:
1191 throw OrthancException(ErrorCode_NotImplemented);
1192 }
1193 }
1194
1195 void ImageProcessing::ShiftLeft(ImageAccessor& image,
1196 unsigned int shift)
1197 {
1198 if (image.GetWidth() == 0 ||
1199 image.GetHeight() == 0 ||
1200 shift == 0)
1201 {
1202 // Nothing to do
1203 return;
1204 }
1205
1206 switch (image.GetFormat())
1207 {
1208 case PixelFormat_Grayscale8:
1209 {
1210 ShiftLeftInternal<uint8_t>(image, shift);
1211 break;
1212 }
1213
1214 case PixelFormat_Grayscale16:
1215 {
1216 ShiftLeftInternal<uint16_t>(image, shift);
1217 break;
1218 }
1219 default:
1220 throw OrthancException(ErrorCode_NotImplemented);
1221 }
1222 }
1223
1224 void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue,
1225 int64_t& maxValue,
1226 const ImageAccessor& image)
1227 {
1228 switch (image.GetFormat())
1229 {
1230 case PixelFormat_Grayscale8:
1231 {
1232 uint8_t a, b;
1233 GetMinMaxValueInternal<uint8_t>(a, b, image);
1234 minValue = a;
1235 maxValue = b;
1236 break;
1237 }
1238
1239 case PixelFormat_Grayscale16:
1240 {
1241 uint16_t a, b;
1242 GetMinMaxValueInternal<uint16_t>(a, b, image);
1243 minValue = a;
1244 maxValue = b;
1245 break;
1246 }
1247
1248 case PixelFormat_Grayscale32:
1249 {
1250 uint32_t a, b;
1251 GetMinMaxValueInternal<uint32_t>(a, b, image);
1252 minValue = a;
1253 maxValue = b;
1254 break;
1255 }
1256
1257 case PixelFormat_SignedGrayscale16:
1258 {
1259 int16_t a, b;
1260 GetMinMaxValueInternal<int16_t>(a, b, image);
1261 minValue = a;
1262 maxValue = b;
1263 break;
1264 }
1265
1266 default:
1267 throw OrthancException(ErrorCode_NotImplemented);
1268 }
1269 }
1270
1271
1272 void ImageProcessing::GetMinMaxFloatValue(float& minValue,
1273 float& maxValue,
1274 const ImageAccessor& image)
1275 {
1276 switch (image.GetFormat())
1277 {
1278 case PixelFormat_Float32:
1279 {
1280 assert(sizeof(float) == 4);
1281 float a, b;
1282
1283 /**
1284 * WARNING - On floating-point types, the minimal value is
1285 * "-FLT_MAX" (as implemented by "::lowest()"), not "FLT_MIN"
1286 * (as implemented by "::min()")
1287 * https://en.cppreference.com/w/cpp/types/numeric_limits
1288 **/
1289 GetMinMaxValueInternal<float>(a, b, image, -std::numeric_limits<float>::max());
1290 minValue = a;
1291 maxValue = b;
1292 break;
1293 }
1294
1295 default:
1296 throw OrthancException(ErrorCode_NotImplemented);
1297 }
1298 }
1299
1300
1301
1302 void ImageProcessing::AddConstant(ImageAccessor& image,
1303 int64_t value)
1304 {
1305 switch (image.GetFormat())
1306 {
1307 case PixelFormat_Grayscale8:
1308 AddConstantInternal<uint8_t>(image, value);
1309 return;
1310
1311 case PixelFormat_Grayscale16:
1312 AddConstantInternal<uint16_t>(image, value);
1313 return;
1314
1315 case PixelFormat_SignedGrayscale16:
1316 AddConstantInternal<int16_t>(image, value);
1317 return;
1318
1319 default:
1320 throw OrthancException(ErrorCode_NotImplemented);
1321 }
1322 }
1323
1324
1325 void ImageProcessing::MultiplyConstant(ImageAccessor& image,
1326 float factor,
1327 bool useRound)
1328 {
1329 switch (image.GetFormat())
1330 {
1331 case PixelFormat_Grayscale8:
1332 if (useRound)
1333 {
1334 MultiplyConstantInternal<uint8_t, true>(image, factor);
1335 }
1336 else
1337 {
1338 MultiplyConstantInternal<uint8_t, false>(image, factor);
1339 }
1340 return;
1341
1342 case PixelFormat_Grayscale16:
1343 if (useRound)
1344 {
1345 MultiplyConstantInternal<uint16_t, true>(image, factor);
1346 }
1347 else
1348 {
1349 MultiplyConstantInternal<uint16_t, false>(image, factor);
1350 }
1351 return;
1352
1353 case PixelFormat_SignedGrayscale16:
1354 if (useRound)
1355 {
1356 MultiplyConstantInternal<int16_t, true>(image, factor);
1357 }
1358 else
1359 {
1360 MultiplyConstantInternal<int16_t, false>(image, factor);
1361 }
1362 return;
1363
1364 default:
1365 throw OrthancException(ErrorCode_NotImplemented);
1366 }
1367 }
1368
1369
1370 void ImageProcessing::ShiftScale(ImageAccessor& image,
1371 float offset,
1372 float scaling,
1373 bool useRound)
1374 {
1375 // Rewrite "(x + offset) * scaling" as "a * x + b"
1376
1377 const float a = scaling;
1378 const float b = offset * scaling;
1379
1380 switch (image.GetFormat())
1381 {
1382 case PixelFormat_Grayscale8:
1383 if (useRound)
1384 {
1385 ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
1386 }
1387 else
1388 {
1389 ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
1390 }
1391 return;
1392
1393 case PixelFormat_Grayscale16:
1394 if (useRound)
1395 {
1396 ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
1397 }
1398 else
1399 {
1400 ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
1401 }
1402 return;
1403
1404 case PixelFormat_SignedGrayscale16:
1405 if (useRound)
1406 {
1407 ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
1408 }
1409 else
1410 {
1411 ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
1412 }
1413 return;
1414
1415 case PixelFormat_Float32:
1416 // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double.
1417 if (useRound)
1418 {
1419 ShiftScaleInternal<float, float, true, false>(image, image, a, b, -std::numeric_limits<float>::max());
1420 }
1421 else
1422 {
1423 ShiftScaleInternal<float, float, false, false>(image, image, a, b, -std::numeric_limits<float>::max());
1424 }
1425 return;
1426
1427 default:
1428 throw OrthancException(ErrorCode_NotImplemented);
1429 }
1430 }
1431
1432
1433 void ImageProcessing::ShiftScale(ImageAccessor& target,
1434 const ImageAccessor& source,
1435 float offset,
1436 float scaling,
1437 bool useRound)
1438 {
1439 // Rewrite "(x + offset) * scaling" as "a * x + b"
1440
1441 const float a = scaling;
1442 const float b = offset * scaling;
1443
1444 switch (target.GetFormat())
1445 {
1446 case PixelFormat_Grayscale8:
1447
1448 switch (source.GetFormat())
1449 {
1450 case PixelFormat_Float32:
1451 if (useRound)
1452 {
1453 ShiftScaleInternal<uint8_t, float, true, false>(
1454 target, source, a, b, std::numeric_limits<uint8_t>::min());
1455 }
1456 else
1457 {
1458 ShiftScaleInternal<uint8_t, float, false, false>(
1459 target, source, a, b, std::numeric_limits<uint8_t>::min());
1460 }
1461 return;
1462
1463 default:
1464 throw OrthancException(ErrorCode_NotImplemented);
1465 }
1466
1467 default:
1468 throw OrthancException(ErrorCode_NotImplemented);
1469 }
1470 }
1471
1472
1473 void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue)
1474 {
1475 const unsigned int width = image.GetWidth();
1476 const unsigned int height = image.GetHeight();
1477
1478 switch (image.GetFormat())
1479 {
1480 case PixelFormat_Grayscale16:
1481 {
1482 uint16_t maxValueUint16 = (uint16_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint16_t>::max())));
1483
1484 for (unsigned int y = 0; y < height; y++)
1485 {
1486 uint16_t* p = reinterpret_cast<uint16_t*>(image.GetRow(y));
1487
1488 for (unsigned int x = 0; x < width; x++, p++)
1489 {
1490 *p = maxValueUint16 - (*p);
1491 }
1492 }
1493
1494 return;
1495 }
1496 case PixelFormat_Grayscale8:
1497 {
1498 uint8_t maxValueUint8 = (uint8_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint8_t>::max())));
1499
1500 for (unsigned int y = 0; y < height; y++)
1501 {
1502 uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y));
1503
1504 for (unsigned int x = 0; x < width; x++, p++)
1505 {
1506 *p = maxValueUint8 - (*p);
1507 }
1508 }
1509
1510 return;
1511 }
1512
1513 default:
1514 throw OrthancException(ErrorCode_NotImplemented);
1515 }
1516
1517 }
1518
1519 void ImageProcessing::Invert(ImageAccessor& image)
1520 {
1521 switch (image.GetFormat())
1522 {
1523 case PixelFormat_Grayscale8:
1524 return Invert(image, 255);
1525 default:
1526 throw OrthancException(ErrorCode_NotImplemented); // you should use the Invert(image, maxValue) overload
1527 }
1528 }
1529
1530
1531
1532 namespace
1533 {
1534 template <Orthanc::PixelFormat Format>
1535 class BresenhamPixelWriter
1536 {
1537 private:
1538 typedef typename PixelTraits<Format>::PixelType PixelType;
1539
1540 Orthanc::ImageAccessor& image_;
1541 PixelType value_;
1542
1543 void PlotLineLow(int x0,
1544 int y0,
1545 int x1,
1546 int y1)
1547 {
1548 int dx = x1 - x0;
1549 int dy = y1 - y0;
1550 int yi = 1;
1551
1552 if (dy < 0)
1553 {
1554 yi = -1;
1555 dy = -dy;
1556 }
1557
1558 int d = 2 * dy - dx;
1559 int y = y0;
1560
1561 for (int x = x0; x <= x1; x++)
1562 {
1563 Write(x, y);
1564
1565 if (d > 0)
1566 {
1567 y = y + yi;
1568 d = d - 2 * dx;
1569 }
1570
1571 d = d + 2*dy;
1572 }
1573 }
1574
1575 void PlotLineHigh(int x0,
1576 int y0,
1577 int x1,
1578 int y1)
1579 {
1580 int dx = x1 - x0;
1581 int dy = y1 - y0;
1582 int xi = 1;
1583
1584 if (dx < 0)
1585 {
1586 xi = -1;
1587 dx = -dx;
1588 }
1589
1590 int d = 2 * dx - dy;
1591 int x = x0;
1592
1593 for (int y = y0; y <= y1; y++)
1594 {
1595 Write(x, y);
1596
1597 if (d > 0)
1598 {
1599 x = x + xi;
1600 d = d - 2 * dy;
1601 }
1602
1603 d = d + 2 * dx;
1604 }
1605 }
1606
1607 public:
1608 BresenhamPixelWriter(Orthanc::ImageAccessor& image,
1609 int64_t value) :
1610 image_(image),
1611 value_(PixelTraits<Format>::IntegerToPixel(value))
1612 {
1613 }
1614
1615 BresenhamPixelWriter(Orthanc::ImageAccessor& image,
1616 const PixelType& value) :
1617 image_(image),
1618 value_(value)
1619 {
1620 }
1621
1622 void Write(int x,
1623 int y)
1624 {
1625 if (x >= 0 &&
1626 y >= 0 &&
1627 static_cast<unsigned int>(x) < image_.GetWidth() &&
1628 static_cast<unsigned int>(y) < image_.GetHeight())
1629 {
1630 PixelType* p = reinterpret_cast<PixelType*>(image_.GetRow(y));
1631 p[x] = value_;
1632 }
1633 }
1634
1635 void DrawSegment(int x0,
1636 int y0,
1637 int x1,
1638 int y1)
1639 {
1640 // This is an implementation of Bresenham's line algorithm
1641 // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases
1642
1643 if (abs(y1 - y0) < abs(x1 - x0))
1644 {
1645 if (x0 > x1)
1646 {
1647 PlotLineLow(x1, y1, x0, y0);
1648 }
1649 else
1650 {
1651 PlotLineLow(x0, y0, x1, y1);
1652 }
1653 }
1654 else
1655 {
1656 if (y0 > y1)
1657 {
1658 PlotLineHigh(x1, y1, x0, y0);
1659 }
1660 else
1661 {
1662 PlotLineHigh(x0, y0, x1, y1);
1663 }
1664 }
1665 }
1666 };
1667 }
1668
1669
1670 void ImageProcessing::DrawLineSegment(ImageAccessor& image,
1671 int x0,
1672 int y0,
1673 int x1,
1674 int y1,
1675 int64_t value)
1676 {
1677 switch (image.GetFormat())
1678 {
1679 case Orthanc::PixelFormat_Grayscale8:
1680 {
1681 BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale8> writer(image, value);
1682 writer.DrawSegment(x0, y0, x1, y1);
1683 break;
1684 }
1685
1686 case Orthanc::PixelFormat_Grayscale16:
1687 {
1688 BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale16> writer(image, value);
1689 writer.DrawSegment(x0, y0, x1, y1);
1690 break;
1691 }
1692
1693 case Orthanc::PixelFormat_SignedGrayscale16:
1694 {
1695 BresenhamPixelWriter<Orthanc::PixelFormat_SignedGrayscale16> writer(image, value);
1696 writer.DrawSegment(x0, y0, x1, y1);
1697 break;
1698 }
1699
1700 default:
1701 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1702 }
1703 }
1704
1705
1706 void ImageProcessing::DrawLineSegment(ImageAccessor& image,
1707 int x0,
1708 int y0,
1709 int x1,
1710 int y1,
1711 uint8_t red,
1712 uint8_t green,
1713 uint8_t blue,
1714 uint8_t alpha)
1715 {
1716 switch (image.GetFormat())
1717 {
1718 case Orthanc::PixelFormat_BGRA32:
1719 {
1720 PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel;
1721 pixel.red_ = red;
1722 pixel.green_ = green;
1723 pixel.blue_ = blue;
1724 pixel.alpha_ = alpha;
1725
1726 BresenhamPixelWriter<Orthanc::PixelFormat_BGRA32> writer(image, pixel);
1727 writer.DrawSegment(x0, y0, x1, y1);
1728 break;
1729 }
1730
1731 case Orthanc::PixelFormat_RGBA32:
1732 {
1733 PixelTraits<Orthanc::PixelFormat_RGBA32>::PixelType pixel;
1734 pixel.red_ = red;
1735 pixel.green_ = green;
1736 pixel.blue_ = blue;
1737 pixel.alpha_ = alpha;
1738
1739 BresenhamPixelWriter<Orthanc::PixelFormat_RGBA32> writer(image, pixel);
1740 writer.DrawSegment(x0, y0, x1, y1);
1741 break;
1742 }
1743
1744 case Orthanc::PixelFormat_RGB24:
1745 {
1746 PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel;
1747 pixel.red_ = red;
1748 pixel.green_ = green;
1749 pixel.blue_ = blue;
1750
1751 BresenhamPixelWriter<Orthanc::PixelFormat_RGB24> writer(image, pixel);
1752 writer.DrawSegment(x0, y0, x1, y1);
1753 break;
1754 }
1755
1756 default:
1757 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1758 }
1759 }
1760
1761 void ComputePolygonExtent(int32_t& left, int32_t& right, int32_t& top, int32_t& bottom, const std::vector<ImageProcessing::ImagePoint>& points)
1762 {
1763 left = std::numeric_limits<int32_t>::max();
1764 right = std::numeric_limits<int32_t>::min();
1765 top = std::numeric_limits<int32_t>::max();
1766 bottom = std::numeric_limits<int32_t>::min();
1767
1768 for (size_t i = 0; i < points.size(); i++)
1769 {
1770 const ImageProcessing::ImagePoint& p = points[i];
1771 left = std::min(p.GetX(), left);
1772 right = std::max(p.GetX(), right);
1773 bottom = std::max(p.GetY(), bottom);
1774 top = std::min(p.GetY(), top);
1775 }
1776 }
1777
1778 template <PixelFormat TargetFormat>
1779 void FillPolygon_(ImageAccessor& image,
1780 const std::vector<ImageProcessing::ImagePoint>& points,
1781 int64_t value_)
1782 {
1783 typedef typename PixelTraits<TargetFormat>::PixelType TargetType;
1784
1785 TargetType value = PixelTraits<TargetFormat>::IntegerToPixel(value_);
1786 int imageWidth = static_cast<int>(image.GetWidth());
1787 int imageHeight = static_cast<int>(image.GetHeight());
1788 int32_t left;
1789 int32_t right;
1790 int32_t top;
1791 int32_t bottom;
1792
1793 // TODO: test clipping in UT (in Trello board)
1794 ComputePolygonExtent(left, right, top, bottom, points);
1795
1796 // clip the computed extent with the target image
1797 // L and R
1798 left = std::max(0, left);
1799 left = std::min(imageWidth, left);
1800 right = std::max(0, right);
1801 right = std::min(imageWidth, right);
1802 if (left > right)
1803 std::swap(left, right);
1804
1805 // T and B
1806 top = std::max(0, top);
1807 top = std::min(imageHeight, top);
1808 bottom = std::max(0, bottom);
1809 bottom = std::min(imageHeight, bottom);
1810 if (top > bottom)
1811 std::swap(top, bottom);
1812
1813 // from http://alienryderflex.com/polygon_fill/
1814
1815 // convert all "corner" points to double only once
1816 std::vector<double> cpx;
1817 std::vector<double> cpy;
1818 size_t cpSize = points.size();
1819 for (size_t i = 0; i < points.size(); i++)
1820 {
1821 if (points[i].GetX() < 0 || points[i].GetX() >= imageWidth
1822 || points[i].GetY() < 0 || points[i].GetY() >= imageHeight)
1823 {
1824 throw Orthanc::OrthancException(ErrorCode_ParameterOutOfRange);
1825 }
1826 cpx.push_back((double)points[i].GetX());
1827 cpy.push_back((double)points[i].GetY());
1828 }
1829
1830 // Draw the lines segments
1831 for (size_t i = 0; i < (points.size() -1); i++)
1832 {
1833 ImageProcessing::DrawLineSegment(image, points[i].GetX(), points[i].GetY(), points[i+1].GetX(), points[i+1].GetY(), value_);
1834 }
1835 ImageProcessing::DrawLineSegment(image, points[points.size() -1].GetX(), points[points.size() -1].GetY(), points[0].GetX(), points[0].GetY(), value_);
1836
1837 std::vector<int32_t> nodeX;
1838 nodeX.resize(cpSize);
1839 int nodes, pixelX, pixelY, i, j, swap ;
1840
1841 // Loop through the rows of the image.
1842 for (pixelY = top; pixelY < bottom; pixelY++)
1843 {
1844 double y = (double)pixelY;
1845 // Build a list of nodes.
1846 nodes = 0;
1847 j = static_cast<int>(cpSize) - 1;
1848
1849 for (i = 0; i < static_cast<int>(cpSize); i++)
1850 {
1851 if ((cpy[i] < y && cpy[j] >= y) || (cpy[j] < y && cpy[i] >= y))
1852 {
1853 nodeX[nodes++] = (int32_t)(cpx[i] + (y - cpy[i])/(cpy[j] - cpy[i]) * (cpx[j] - cpx[i]));
1854 }
1855 j=i;
1856 }
1857
1858 // Sort the nodes, via a simple “Bubble” sort.
1859 i=0;
1860 while (i < nodes-1)
1861 {
1862 if (nodeX[i] > nodeX[i+1])
1863 {
1864 swap = nodeX[i];
1865 nodeX[i] = nodeX[i+1];
1866 nodeX[i+1] = swap;
1867 if (i > 0)
1868 {
1869 i--;
1870 }
1871 }
1872 else
1873 {
1874 i++;
1875 }
1876 }
1877
1878 TargetType* row = reinterpret_cast<TargetType*>(image.GetRow(pixelY));
1879 // Fill the pixels between node pairs.
1880 for (i = 0; i < nodes; i += 2)
1881 {
1882 if (nodeX[i] >= right)
1883 break;
1884
1885 if (nodeX[i + 1] >= left)
1886 {
1887 if (nodeX[i] < left)
1888 {
1889 nodeX[i] = left;
1890 }
1891
1892 if (nodeX[i + 1] > right)
1893 {
1894 nodeX[i + 1] = right;
1895 }
1896
1897 for (pixelX = nodeX[i]; pixelX <= nodeX[i + 1]; pixelX++)
1898 {
1899 *(row + pixelX) = value;
1900 }
1901 }
1902 }
1903 }
1904 }
1905
1906 void ImageProcessing::FillPolygon(ImageAccessor& image,
1907 const std::vector<ImagePoint>& points,
1908 int64_t value)
1909 {
1910 switch (image.GetFormat())
1911 {
1912 case Orthanc::PixelFormat_Grayscale8:
1913 {
1914 FillPolygon_<Orthanc::PixelFormat_Grayscale8>(image, points, value);
1915 break;
1916 }
1917 case Orthanc::PixelFormat_Grayscale16:
1918 {
1919 FillPolygon_<Orthanc::PixelFormat_Grayscale16>(image, points, value);
1920 break;
1921 }
1922 case Orthanc::PixelFormat_SignedGrayscale16:
1923 {
1924 FillPolygon_<Orthanc::PixelFormat_SignedGrayscale16>(image, points, value);
1925 break;
1926 }
1927 default:
1928 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1929 }
1930 }
1931
1932
1933 template <PixelFormat Format>
1934 static void ResizeInternal(ImageAccessor& target,
1935 const ImageAccessor& source)
1936 {
1937 assert(target.GetFormat() == source.GetFormat() &&
1938 target.GetFormat() == Format);
1939
1940 const unsigned int sourceWidth = source.GetWidth();
1941 const unsigned int sourceHeight = source.GetHeight();
1942 const unsigned int targetWidth = target.GetWidth();
1943 const unsigned int targetHeight = target.GetHeight();
1944
1945 if (targetWidth == 0 || targetHeight == 0)
1946 {
1947 return;
1948 }
1949
1950 if (sourceWidth == 0 || sourceHeight == 0)
1951 {
1952 // Avoids division by zero below
1953 ImageProcessing::Set(target, 0);
1954 return;
1955 }
1956
1957 const float scaleX = static_cast<float>(sourceWidth) / static_cast<float>(targetWidth);
1958 const float scaleY = static_cast<float>(sourceHeight) / static_cast<float>(targetHeight);
1959
1960
1961 /**
1962 * Create two lookup tables to quickly know the (x,y) position
1963 * in the source image, given the (x,y) position in the target
1964 * image.
1965 **/
1966
1967 std::vector<unsigned int> lookupX(targetWidth);
1968
1969 for (unsigned int x = 0; x < targetWidth; x++)
1970 {
1971 int sourceX = static_cast<int>(std::floor((static_cast<float>(x) + 0.5f) * scaleX));
1972 if (sourceX < 0)
1973 {
1974 sourceX = 0; // Should never happen
1975 }
1976 else if (sourceX >= static_cast<int>(sourceWidth))
1977 {
1978 sourceX = sourceWidth - 1;
1979 }
1980
1981 lookupX[x] = static_cast<unsigned int>(sourceX);
1982 }
1983
1984 std::vector<unsigned int> lookupY(targetHeight);
1985
1986 for (unsigned int y = 0; y < targetHeight; y++)
1987 {
1988 int sourceY = static_cast<int>(std::floor((static_cast<float>(y) + 0.5f) * scaleY));
1989 if (sourceY < 0)
1990 {
1991 sourceY = 0; // Should never happen
1992 }
1993 else if (sourceY >= static_cast<int>(sourceHeight))
1994 {
1995 sourceY = sourceHeight - 1;
1996 }
1997
1998 lookupY[y] = static_cast<unsigned int>(sourceY);
1999 }
2000
2001
2002 /**
2003 * Actual resizing
2004 **/
2005
2006 for (unsigned int targetY = 0; targetY < targetHeight; targetY++)
2007 {
2008 unsigned int sourceY = lookupY[targetY];
2009
2010 for (unsigned int targetX = 0; targetX < targetWidth; targetX++)
2011 {
2012 unsigned int sourceX = lookupX[targetX];
2013
2014 typename ImageTraits<Format>::PixelType pixel;
2015 ImageTraits<Format>::GetPixel(pixel, source, sourceX, sourceY);
2016 ImageTraits<Format>::SetPixel(target, pixel, targetX, targetY);
2017 }
2018 }
2019 }
2020
2021
2022
2023 void ImageProcessing::Resize(ImageAccessor& target,
2024 const ImageAccessor& source)
2025 {
2026 if (source.GetFormat() != source.GetFormat())
2027 {
2028 throw OrthancException(ErrorCode_IncompatibleImageFormat);
2029 }
2030
2031 if (source.GetWidth() == target.GetWidth() &&
2032 source.GetHeight() == target.GetHeight())
2033 {
2034 Copy(target, source);
2035 return;
2036 }
2037
2038 switch (source.GetFormat())
2039 {
2040 case PixelFormat_Grayscale8:
2041 ResizeInternal<PixelFormat_Grayscale8>(target, source);
2042 break;
2043
2044 case PixelFormat_Float32:
2045 ResizeInternal<PixelFormat_Float32>(target, source);
2046 break;
2047
2048 case PixelFormat_RGB24:
2049 ResizeInternal<PixelFormat_RGB24>(target, source);
2050 break;
2051
2052 default:
2053 throw OrthancException(ErrorCode_NotImplemented);
2054 }
2055 }
2056
2057
2058 ImageAccessor* ImageProcessing::Halve(const ImageAccessor& source,
2059 bool forceMinimalPitch)
2060 {
2061 std::unique_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth() / 2,
2062 source.GetHeight() / 2, forceMinimalPitch));
2063 Resize(*target, source);
2064 return target.release();
2065 }
2066
2067
2068 template <PixelFormat Format>
2069 static void FlipXInternal(ImageAccessor& image)
2070 {
2071 const unsigned int height = image.GetHeight();
2072 const unsigned int width = image.GetWidth();
2073
2074 for (unsigned int y = 0; y < height; y++)
2075 {
2076 for (unsigned int x1 = 0; x1 < width / 2; x1++)
2077 {
2078 unsigned int x2 = width - 1 - x1;
2079
2080 typename ImageTraits<Format>::PixelType a, b;
2081 ImageTraits<Format>::GetPixel(a, image, x1, y);
2082 ImageTraits<Format>::GetPixel(b, image, x2, y);
2083 ImageTraits<Format>::SetPixel(image, a, x2, y);
2084 ImageTraits<Format>::SetPixel(image, b, x1, y);
2085 }
2086 }
2087 }
2088
2089
2090 void ImageProcessing::FlipX(ImageAccessor& image)
2091 {
2092 switch (image.GetFormat())
2093 {
2094 case PixelFormat_Grayscale8:
2095 FlipXInternal<PixelFormat_Grayscale8>(image);
2096 break;
2097
2098 case PixelFormat_RGB24:
2099 FlipXInternal<PixelFormat_RGB24>(image);
2100 break;
2101
2102 default:
2103 throw OrthancException(ErrorCode_NotImplemented);
2104 }
2105 }
2106
2107
2108 template <PixelFormat Format>
2109 static void FlipYInternal(ImageAccessor& image)
2110 {
2111 const unsigned int height = image.GetHeight();
2112 const unsigned int width = image.GetWidth();
2113
2114 for (unsigned int y1 = 0; y1 < height / 2; y1++)
2115 {
2116 unsigned int y2 = height - 1 - y1;
2117
2118 for (unsigned int x = 0; x < width; x++)
2119 {
2120 typename ImageTraits<Format>::PixelType a, b;
2121 ImageTraits<Format>::GetPixel(a, image, x, y1);
2122 ImageTraits<Format>::GetPixel(b, image, x, y2);
2123 ImageTraits<Format>::SetPixel(image, a, x, y2);
2124 ImageTraits<Format>::SetPixel(image, b, x, y1);
2125 }
2126 }
2127 }
2128
2129
2130 void ImageProcessing::FlipY(ImageAccessor& image)
2131 {
2132 switch (image.GetFormat())
2133 {
2134 case PixelFormat_Grayscale8:
2135 FlipYInternal<PixelFormat_Grayscale8>(image);
2136 break;
2137
2138 case PixelFormat_RGB24:
2139 FlipYInternal<PixelFormat_RGB24>(image);
2140 break;
2141
2142 default:
2143 throw OrthancException(ErrorCode_NotImplemented);
2144 }
2145 }
2146
2147
2148 // This is a slow implementation of horizontal convolution on one
2149 // individual channel, that checks for out-of-image values
2150 template <typename RawPixel, unsigned int ChannelsCount>
2151 static float GetHorizontalConvolutionFloatSecure(const Orthanc::ImageAccessor& source,
2152 const std::vector<float>& horizontal,
2153 size_t horizontalAnchor,
2154 unsigned int x,
2155 unsigned int y,
2156 float leftBorder,
2157 float rightBorder,
2158 unsigned int channel)
2159 {
2160 const RawPixel* row = reinterpret_cast<const RawPixel*>(source.GetConstRow(y)) + channel;
2161
2162 float p = 0;
2163
2164 for (unsigned int k = 0; k < horizontal.size(); k++)
2165 {
2166 float value;
2167
2168 if (x + k < horizontalAnchor) // Negation of "x - horizontalAnchor + k >= 0"
2169 {
2170 value = leftBorder;
2171 }
2172 else if (x + k >= source.GetWidth() + horizontalAnchor) // Negation of "x - horizontalAnchor + k < width"
2173 {
2174 value = rightBorder;
2175 }
2176 else
2177 {
2178 // The value lies within the image
2179 value = row[(x - horizontalAnchor + k) * ChannelsCount];
2180 }
2181
2182 p += value * horizontal[k];
2183 }
2184
2185 return p;
2186 }
2187
2188
2189
2190 // This is an implementation of separable convolution that uses
2191 // floating-point arithmetics, and an intermediate Float32
2192 // image. The out-of-image values are taken as the border
2193 // value. Further optimization is possible.
2194 template <typename RawPixel, unsigned int ChannelsCount>
2195 static void SeparableConvolutionFloat(ImageAccessor& image /* inplace */,
2196 const std::vector<float>& horizontal,
2197 size_t horizontalAnchor,
2198 const std::vector<float>& vertical,
2199 size_t verticalAnchor,
2200 float normalization)
2201 {
2202 // WARNING - "::min()" should be replaced by "::lowest()" if
2203 // dealing with float or double (which is not the case so far)
2204 assert(sizeof(RawPixel) <= 2); // Safeguard to remember about "float/double"
2205
2206 const unsigned int width = image.GetWidth();
2207 const unsigned int height = image.GetHeight();
2208
2209
2210 /**
2211 * Horizontal convolution
2212 **/
2213
2214 Image tmp(PixelFormat_Float32, ChannelsCount * width, height, false);
2215
2216 for (unsigned int y = 0; y < height; y++)
2217 {
2218 const RawPixel* row = reinterpret_cast<const RawPixel*>(image.GetConstRow(y));
2219
2220 float leftBorder[ChannelsCount], rightBorder[ChannelsCount];
2221
2222 for (unsigned int c = 0; c < ChannelsCount; c++)
2223 {
2224 leftBorder[c] = row[c];
2225 rightBorder[c] = row[ChannelsCount * (width - 1) + c];
2226 }
2227
2228 float* p = static_cast<float*>(tmp.GetRow(y));
2229
2230 if (width < horizontal.size())
2231 {
2232 // It is not possible to have the full kernel within the image, use the direct implementation
2233 for (unsigned int x = 0; x < width; x++)
2234 {
2235 for (unsigned int c = 0; c < ChannelsCount; c++, p++)
2236 {
2237 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount>
2238 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c);
2239 }
2240 }
2241 }
2242 else
2243 {
2244 // Deal with the left border
2245 for (unsigned int x = 0; x < horizontalAnchor; x++)
2246 {
2247 for (unsigned int c = 0; c < ChannelsCount; c++, p++)
2248 {
2249 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount>
2250 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c);
2251 }
2252 }
2253
2254 // Deal with the central portion of the image (all pixel values
2255 // scanned by the kernel lie inside the image)
2256
2257 for (unsigned int x = 0; x < width - horizontal.size() + 1; x++)
2258 {
2259 for (unsigned int c = 0; c < ChannelsCount; c++, p++)
2260 {
2261 *p = 0;
2262 for (unsigned int k = 0; k < horizontal.size(); k++)
2263 {
2264 *p += static_cast<float>(row[(x + k) * ChannelsCount + c]) * horizontal[k];
2265 }
2266 }
2267 }
2268
2269 // Deal with the right border
2270 for (unsigned int x = static_cast<unsigned int>(
2271 horizontalAnchor + width - horizontal.size() + 1); x < width; x++)
2272 {
2273 for (unsigned int c = 0; c < ChannelsCount; c++, p++)
2274 {
2275 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount>
2276 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c);
2277 }
2278 }
2279 }
2280 }
2281
2282
2283 /**
2284 * Vertical convolution
2285 **/
2286
2287 std::vector<const float*> rows(vertical.size());
2288
2289 for (unsigned int y = 0; y < height; y++)
2290 {
2291 for (unsigned int k = 0; k < vertical.size(); k++)
2292 {
2293 if (y + k < verticalAnchor)
2294 {
2295 rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(0)); // Use top border
2296 }
2297 else if (y + k >= height + verticalAnchor)
2298 {
2299 rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(height - 1)); // Use bottom border
2300 }
2301 else
2302 {
2303 rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(static_cast<unsigned int>(y + k - verticalAnchor)));
2304 }
2305 }
2306
2307 RawPixel* p = reinterpret_cast<RawPixel*>(image.GetRow(y));
2308
2309 for (unsigned int x = 0; x < width; x++)
2310 {
2311 for (unsigned int c = 0; c < ChannelsCount; c++, p++)
2312 {
2313 float accumulator = 0;
2314
2315 for (unsigned int k = 0; k < vertical.size(); k++)
2316 {
2317 accumulator += rows[k][ChannelsCount * x + c] * vertical[k];
2318 }
2319
2320 accumulator *= normalization;
2321
2322 if (accumulator <= static_cast<float>(std::numeric_limits<RawPixel>::min()))
2323 {
2324 *p = std::numeric_limits<RawPixel>::min();
2325 }
2326 else if (accumulator >= static_cast<float>(std::numeric_limits<RawPixel>::max()))
2327 {
2328 *p = std::numeric_limits<RawPixel>::max();
2329 }
2330 else
2331 {
2332 *p = static_cast<RawPixel>(accumulator);
2333 }
2334 }
2335 }
2336 }
2337 }
2338
2339
2340 void ImageProcessing::SeparableConvolution(ImageAccessor& image /* inplace */,
2341 const std::vector<float>& horizontal,
2342 size_t horizontalAnchor,
2343 const std::vector<float>& vertical,
2344 size_t verticalAnchor)
2345 {
2346 if (horizontal.size() == 0 ||
2347 vertical.size() == 0 ||
2348 horizontalAnchor >= horizontal.size() ||
2349 verticalAnchor >= vertical.size())
2350 {
2351 throw OrthancException(ErrorCode_ParameterOutOfRange);
2352 }
2353
2354 if (image.GetWidth() == 0 ||
2355 image.GetHeight() == 0)
2356 {
2357 return;
2358 }
2359
2360 /**
2361 * Compute normalization
2362 **/
2363
2364 float sumHorizontal = 0;
2365 for (size_t i = 0; i < horizontal.size(); i++)
2366 {
2367 sumHorizontal += horizontal[i];
2368 }
2369
2370 float sumVertical = 0;
2371 for (size_t i = 0; i < vertical.size(); i++)
2372 {
2373 sumVertical += vertical[i];
2374 }
2375
2376 if (fabsf(sumHorizontal) <= std::numeric_limits<float>::epsilon() ||
2377 fabsf(sumVertical) <= std::numeric_limits<float>::epsilon())
2378 {
2379 throw OrthancException(ErrorCode_ParameterOutOfRange, "Singular convolution kernel");
2380 }
2381
2382 const float normalization = 1.0f / (sumHorizontal * sumVertical);
2383
2384 switch (image.GetFormat())
2385 {
2386 case PixelFormat_Grayscale8:
2387 SeparableConvolutionFloat<uint8_t, 1u>
2388 (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
2389 break;
2390
2391 case PixelFormat_RGB24:
2392 SeparableConvolutionFloat<uint8_t, 3u>
2393 (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization);
2394 break;
2395
2396 default:
2397 throw OrthancException(ErrorCode_NotImplemented);
2398 }
2399 }
2400
2401
2402 void ImageProcessing::SmoothGaussian5x5(ImageAccessor& image)
2403 {
2404 std::vector<float> kernel(5);
2405 kernel[0] = 1;
2406 kernel[1] = 4;
2407 kernel[2] = 6;
2408 kernel[3] = 4;
2409 kernel[4] = 1;
2410
2411 SeparableConvolution(image, kernel, 2, kernel, 2);
2412 }
2413
2414
2415 void ImageProcessing::FitSize(ImageAccessor& target,
2416 const ImageAccessor& source)
2417 {
2418 if (target.GetWidth() == 0 ||
2419 target.GetHeight() == 0)
2420 {
2421 return;
2422 }
2423
2424 if (source.GetWidth() == target.GetWidth() &&
2425 source.GetHeight() == target.GetHeight())
2426 {
2427 Copy(target, source);
2428 return;
2429 }
2430
2431 Set(target, 0);
2432
2433 // Preserve the aspect ratio
2434 float cw = static_cast<float>(source.GetWidth());
2435 float ch = static_cast<float>(source.GetHeight());
2436 float r = std::min(
2437 static_cast<float>(target.GetWidth()) / cw,
2438 static_cast<float>(target.GetHeight()) / ch);
2439
2440 unsigned int sw = std::min(static_cast<unsigned int>(boost::math::iround(cw * r)), target.GetWidth());
2441 unsigned int sh = std::min(static_cast<unsigned int>(boost::math::iround(ch * r)), target.GetHeight());
2442 Image resized(target.GetFormat(), sw, sh, false);
2443
2444 //ImageProcessing::SmoothGaussian5x5(source);
2445 ImageProcessing::Resize(resized, source);
2446
2447 assert(target.GetWidth() >= resized.GetWidth() &&
2448 target.GetHeight() >= resized.GetHeight());
2449 unsigned int offsetX = (target.GetWidth() - resized.GetWidth()) / 2;
2450 unsigned int offsetY = (target.GetHeight() - resized.GetHeight()) / 2;
2451
2452 ImageAccessor region;
2453 target.GetRegion(region, offsetX, offsetY, resized.GetWidth(), resized.GetHeight());
2454 ImageProcessing::Copy(region, resized);
2455 }
2456
2457
2458 ImageAccessor* ImageProcessing::FitSize(const ImageAccessor& source,
2459 unsigned int width,
2460 unsigned int height)
2461 {
2462 std::unique_ptr<ImageAccessor> target(new Image(source.GetFormat(), width, height, false));
2463 FitSize(*target, source);
2464 return target.release();
2465 }
2466 }