comparison Core/Images/ImageProcessing.cpp @ 3682:5f64c866108a

merging implementations of ImageProcessing::ShiftScale() and ApplyWindowing()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 24 Feb 2020 16:26:59 +0100
parents 94f4a18a79cc
children 12253ddefe5a
comparison
equal deleted inserted replaced
3680:453c0ece560a 3682:5f64c866108a
39 #include "PixelTraits.h" 39 #include "PixelTraits.h"
40 #include "../OrthancException.h" 40 #include "../OrthancException.h"
41 41
42 #ifdef __EMSCRIPTEN__ 42 #ifdef __EMSCRIPTEN__
43 /* 43 /*
44 Avoid this error: 44 Avoid this error:
45 ----------------- 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] 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 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 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 */ 49 */
50 #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" 50 #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion"
51 #endif 51 #endif
52 52
53 #include <boost/math/special_functions/round.hpp> 53 #include <boost/math/special_functions/round.hpp>
383 } 383 }
384 } 384 }
385 } 385 }
386 386
387 387
388 template <typename PixelType, 388 // Computes "a * x + b" at each pixel => Note that this is not the
389 bool UseRound> 389 // same convention as in "ShiftScale()"
390 static void ShiftScaleInternal(ImageAccessor& image, 390 template <typename TargetType,
391 float offset, 391 typename SourceType,
392 float scaling, 392 bool UseRound,
393 const PixelType LowestValue = std::numeric_limits<PixelType>::min()) 393 bool Invert>
394 { 394 static void ShiftScaleInternal(ImageAccessor& target,
395 const PixelType minPixelValue = LowestValue; 395 const ImageAccessor& source,
396 const PixelType maxPixelValue = std::numeric_limits<PixelType>::max(); 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();
397 const float minFloatValue = static_cast<float>(LowestValue); 415 const float minFloatValue = static_cast<float>(LowestValue);
398 const float maxFloatValue = static_cast<float>(maxPixelValue); 416 const float maxFloatValue = static_cast<float>(maxPixelValue);
399 417
400 const unsigned int height = image.GetHeight(); 418 const unsigned int height = target.GetHeight();
401 const unsigned int width = image.GetWidth(); 419 const unsigned int width = target.GetWidth();
402 420
403 for (unsigned int y = 0; y < height; y++) 421 for (unsigned int y = 0; y < height; y++)
404 { 422 {
405 PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y)); 423 TargetType* p = reinterpret_cast<TargetType*>(target.GetRow(y));
406 424 const SourceType* q = reinterpret_cast<const SourceType*>(source.GetRow(y));
407 for (unsigned int x = 0; x < width; x++, p++) 425
408 { 426 for (unsigned int x = 0; x < width; x++, p++, q++)
409 float v = (static_cast<float>(*p) + offset) * scaling; 427 {
428 float v = a * static_cast<float>(*q) + b;
410 429
411 if (v >= maxFloatValue) 430 if (v >= maxFloatValue)
412 { 431 {
413 *p = maxPixelValue; 432 *p = maxPixelValue;
414 } 433 }
417 *p = minPixelValue; 436 *p = minPixelValue;
418 } 437 }
419 else if (UseRound) 438 else if (UseRound)
420 { 439 {
421 // The "round" operation is very costly 440 // The "round" operation is very costly
422 *p = static_cast<PixelType>(boost::math::iround(v)); 441 *p = static_cast<TargetType>(boost::math::iround(v));
423 } 442 }
424 else 443 else
425 { 444 {
426 *p = static_cast<PixelType>(v); 445 *p = static_cast<TargetType>(std::floor(v));
446 }
447
448 if (Invert)
449 {
450 *p = maxPixelValue - *p;
427 } 451 }
428 } 452 }
429 } 453 }
430 } 454 }
431 455
496 float windowWidth, 520 float windowWidth,
497 float rescaleSlope, 521 float rescaleSlope,
498 float rescaleIntercept, 522 float rescaleIntercept,
499 bool invert) 523 bool invert)
500 { 524 {
525 assert(sizeof(SourceType) == source.GetBytesPerPixel() &&
526 sizeof(TargetType) == target.GetBytesPerPixel());
527
501 // WARNING - "::min()" should be replaced by "::lowest()" if 528 // WARNING - "::min()" should be replaced by "::lowest()" if
502 // dealing with float or double (which is not the case so far) 529 // dealing with float or double (which is not the case so far)
503 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" 530 assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double"
504 const TargetType minTargetValue = std::numeric_limits<TargetType>::min(); 531 const TargetType minTargetValue = std::numeric_limits<TargetType>::min();
505 const TargetType maxTargetValue = std::numeric_limits<TargetType>::max(); 532 const TargetType maxTargetValue = std::numeric_limits<TargetType>::max();
506 const float minFloatValue = static_cast<float>(minTargetValue);
507 const float maxFloatValue = static_cast<float>(maxTargetValue); 533 const float maxFloatValue = static_cast<float>(maxTargetValue);
508 534
509 const float windowIntercept = windowCenter - windowWidth / 2.0f; 535 const float windowIntercept = windowCenter - windowWidth / 2.0f;
510 const float windowSlope = (maxFloatValue + 1.0f) / windowWidth; 536 const float windowSlope = (maxFloatValue + 1.0f) / windowWidth;
511 537
512 const unsigned int width = source.GetWidth(); 538 const float a = rescaleSlope * windowSlope;
513 const unsigned int height = source.GetHeight(); 539 const float b = (rescaleIntercept - windowIntercept) * windowSlope;
514 540
515 for (unsigned int y = 0; y < height; y++) 541 if (invert)
516 { 542 {
517 TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y)); 543 ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue);
518 const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); 544 }
519 545 else
520 for (unsigned int x = 0; x < width; x++, t++, s++) 546 {
521 { 547 ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue);
522 float rescaledValue = *s * rescaleSlope + rescaleIntercept;
523 float v = (rescaledValue - windowIntercept) * windowSlope;
524 if (v <= minFloatValue)
525 {
526 v = minFloatValue;
527 }
528 else if (v >= maxFloatValue)
529 {
530 v = maxFloatValue;
531 }
532
533 TargetType vv = static_cast<TargetType>(v);
534
535 if (invert)
536 {
537 vv = maxTargetValue - vv;
538 }
539
540 *t = static_cast<TargetType>(vv);
541 }
542 } 548 }
543 } 549 }
544 550
545 void ImageProcessing::ApplyWindowing(ImageAccessor& target, 551 void ImageProcessing::ApplyWindowing(ImageAccessor& target,
546 const ImageAccessor& source, 552 const ImageAccessor& source,
560 { 566 {
561 case Orthanc::PixelFormat_Float32: 567 case Orthanc::PixelFormat_Float32:
562 { 568 {
563 switch (target.GetFormat()) 569 switch (target.GetFormat())
564 { 570 {
565 case Orthanc::PixelFormat_Grayscale8: 571 case Orthanc::PixelFormat_Grayscale8:
566 ApplyWindowingInternal<uint8_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); 572 ApplyWindowingInternal<uint8_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
567 break; 573 break;
568 case Orthanc::PixelFormat_Grayscale16: 574 case Orthanc::PixelFormat_Grayscale16:
569 ApplyWindowingInternal<uint16_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); 575 ApplyWindowingInternal<uint16_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
570 break; 576 break;
571 default: 577 default:
572 throw OrthancException(ErrorCode_NotImplemented); 578 throw OrthancException(ErrorCode_NotImplemented);
573 } 579 }
574 };break; 580 };break;
575 case Orthanc::PixelFormat_Grayscale8: 581 case Orthanc::PixelFormat_Grayscale8:
576 { 582 {
577 switch (target.GetFormat()) 583 switch (target.GetFormat())
578 { 584 {
579 case Orthanc::PixelFormat_Grayscale8: 585 case Orthanc::PixelFormat_Grayscale8:
580 ApplyWindowingInternal<uint8_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); 586 ApplyWindowingInternal<uint8_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
581 break; 587 break;
582 case Orthanc::PixelFormat_Grayscale16: 588 case Orthanc::PixelFormat_Grayscale16:
583 ApplyWindowingInternal<uint16_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); 589 ApplyWindowingInternal<uint16_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
584 break; 590 break;
585 default: 591 default:
586 throw OrthancException(ErrorCode_NotImplemented); 592 throw OrthancException(ErrorCode_NotImplemented);
587 } 593 }
588 };break; 594 };break;
589 case Orthanc::PixelFormat_Grayscale16: 595 case Orthanc::PixelFormat_Grayscale16:
590 { 596 {
591 switch (target.GetFormat()) 597 switch (target.GetFormat())
592 { 598 {
593 case Orthanc::PixelFormat_Grayscale8: 599 case Orthanc::PixelFormat_Grayscale8:
594 ApplyWindowingInternal<uint8_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); 600 ApplyWindowingInternal<uint8_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
595 break; 601 break;
596 case Orthanc::PixelFormat_Grayscale16: 602 case Orthanc::PixelFormat_Grayscale16:
597 ApplyWindowingInternal<uint16_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); 603 ApplyWindowingInternal<uint16_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
598 break; 604 break;
599 default: 605 default:
600 throw OrthancException(ErrorCode_NotImplemented); 606 throw OrthancException(ErrorCode_NotImplemented);
601 } 607 }
602 };break; 608 };break;
603 default: 609 default:
604 throw OrthancException(ErrorCode_NotImplemented); 610 throw OrthancException(ErrorCode_NotImplemented);
605 } 611 }
1179 case PixelFormat_Grayscale16: 1185 case PixelFormat_Grayscale16:
1180 { 1186 {
1181 ShiftRightInternal<uint16_t>(image, shift); 1187 ShiftRightInternal<uint16_t>(image, shift);
1182 break; 1188 break;
1183 } 1189 }
1184 default: 1190 default:
1185 throw OrthancException(ErrorCode_NotImplemented); 1191 throw OrthancException(ErrorCode_NotImplemented);
1186 } 1192 }
1187 } 1193 }
1188 1194
1189 void ImageProcessing::ShiftLeft(ImageAccessor& image, 1195 void ImageProcessing::ShiftLeft(ImageAccessor& image,
1208 case PixelFormat_Grayscale16: 1214 case PixelFormat_Grayscale16:
1209 { 1215 {
1210 ShiftLeftInternal<uint16_t>(image, shift); 1216 ShiftLeftInternal<uint16_t>(image, shift);
1211 break; 1217 break;
1212 } 1218 }
1213 default: 1219 default:
1214 throw OrthancException(ErrorCode_NotImplemented); 1220 throw OrthancException(ErrorCode_NotImplemented);
1215 } 1221 }
1216 } 1222 }
1217 1223
1218 void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue, 1224 void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue,
1364 void ImageProcessing::ShiftScale(ImageAccessor& image, 1370 void ImageProcessing::ShiftScale(ImageAccessor& image,
1365 float offset, 1371 float offset,
1366 float scaling, 1372 float scaling,
1367 bool useRound) 1373 bool useRound)
1368 { 1374 {
1375 // Rewrite "(x + offset) * scaling" as "a * x + b"
1376
1377 const float a = scaling;
1378 const float b = offset * scaling;
1379
1369 switch (image.GetFormat()) 1380 switch (image.GetFormat())
1370 { 1381 {
1371 case PixelFormat_Grayscale8: 1382 case PixelFormat_Grayscale8:
1372 if (useRound) 1383 if (useRound)
1373 { 1384 {
1374 ShiftScaleInternal<uint8_t, true>(image, offset, scaling); 1385 ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
1375 } 1386 }
1376 else 1387 else
1377 { 1388 {
1378 ShiftScaleInternal<uint8_t, false>(image, offset, scaling); 1389 ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
1379 } 1390 }
1380 return; 1391 return;
1381 1392
1382 case PixelFormat_Grayscale16: 1393 case PixelFormat_Grayscale16:
1383 if (useRound) 1394 if (useRound)
1384 { 1395 {
1385 ShiftScaleInternal<uint16_t, true>(image, offset, scaling); 1396 ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
1386 } 1397 }
1387 else 1398 else
1388 { 1399 {
1389 ShiftScaleInternal<uint16_t, false>(image, offset, scaling); 1400 ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
1390 } 1401 }
1391 return; 1402 return;
1392 1403
1393 case PixelFormat_SignedGrayscale16: 1404 case PixelFormat_SignedGrayscale16:
1394 if (useRound) 1405 if (useRound)
1395 { 1406 {
1396 ShiftScaleInternal<int16_t, true>(image, offset, scaling); 1407 ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
1397 } 1408 }
1398 else 1409 else
1399 { 1410 {
1400 ShiftScaleInternal<int16_t, false>(image, offset, scaling); 1411 ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
1401 } 1412 }
1402 return; 1413 return;
1403 1414
1404 case PixelFormat_Float32: 1415 case PixelFormat_Float32:
1416 // "::min()" must be replaced by "::lowest()" if dealing with float or double
1405 if (useRound) 1417 if (useRound)
1406 { 1418 {
1407 ShiftScaleInternal<float, true>(image, offset, scaling, -std::numeric_limits<float>::max()); 1419 ShiftScaleInternal<float, float, true, false>(image, image, a, b, std::numeric_limits<float>::lowest());
1408 } 1420 }
1409 else 1421 else
1410 { 1422 {
1411 ShiftScaleInternal<float, false>(image, offset, scaling, -std::numeric_limits<float>::max()); 1423 ShiftScaleInternal<float, float, false, false>(image, image, a, b, std::numeric_limits<float>::lowest());
1412 } 1424 }
1413 return; 1425 return;
1414 1426
1415 default: 1427 default:
1416 throw OrthancException(ErrorCode_NotImplemented); 1428 throw OrthancException(ErrorCode_NotImplemented);
2214 } 2226 }
2215 } 2227 }
2216 2228
2217 // Deal with the right border 2229 // Deal with the right border
2218 for (unsigned int x = static_cast<unsigned int>( 2230 for (unsigned int x = static_cast<unsigned int>(
2219 horizontalAnchor + width - horizontal.size() + 1); x < width; x++) 2231 horizontalAnchor + width - horizontal.size() + 1); x < width; x++)
2220 { 2232 {
2221 for (unsigned int c = 0; c < ChannelsCount; c++, p++) 2233 for (unsigned int c = 0; c < ChannelsCount; c++, p++)
2222 { 2234 {
2223 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount> 2235 *p = GetHorizontalConvolutionFloatSecure<RawPixel, ChannelsCount>
2224 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); 2236 (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c);