Mercurial > hg > orthanc
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); |