changeset 4976:03632ed1eb67 more-tags

merged default -> more-tags
author Alain Mazy <am@osimis.io>
date Wed, 13 Apr 2022 14:58:58 +0200
parents d68b3a2cea17 (current diff) 5e7404f23fa8 (diff)
children dad71e6da406
files NEWS OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
diffstat 6 files changed, 117 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Mar 30 15:45:54 2022 +0200
+++ b/NEWS	Wed Apr 13 14:58:58 2022 +0200
@@ -19,6 +19,7 @@
   - W002_InconsistentDicomTagsInDb
 * C-Find and QIDO-RS can now return the InstanceAvailability tag.  Value is 
   always "ONLINE"
+* Improved decoding of US Images with Implicit VR.
 
 REST API
 --------
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Wed Mar 30 15:45:54 2022 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Wed Apr 13 14:58:58 2022 +0200
@@ -437,12 +437,35 @@
         if (r != "256\\0\\16" ||
             rc != 256 ||
             gc != 256 ||
-            bc != 256 ||
-            pixelLength != target->GetWidth() * target->GetHeight())
+            bc != 256)
         {
           throw OrthancException(ErrorCode_NotImplemented);
         }
 
+        if (pixelLength != target->GetWidth() * target->GetHeight())
+        {
+          DcmElement *elem;
+          Uint16 bitsAllocated = 0;
+
+          if (!dataset.findAndGetUint16(DCM_BitsAllocated, bitsAllocated).good())
+          {
+            throw OrthancException(ErrorCode_NotImplemented);  
+          }
+
+          if (!dataset.findAndGetElement(DCM_PixelData, elem).good())
+          {
+            throw OrthancException(ErrorCode_NotImplemented);  
+          }
+
+          // In implicit VR files, pixelLength is expressed in words (OW) although pixels can actually be 8 bits
+          // -> pixelLength is wrong by a factor of two and the image can still be decoded!
+          // seen in some Philips ClearVue 650 images (using 8 bits LUT)
+          if (!(elem->getVR() == EVR_OW && bitsAllocated == 8 && (2*pixelLength == target->GetWidth() * target->GetHeight())))  
+          {
+            throw OrthancException(ErrorCode_NotImplemented);
+          }
+        }
+
         const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData);
         const unsigned int width = target->GetWidth();
         const unsigned int height = target->GetHeight();
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp	Wed Mar 30 15:45:54 2022 +0200
+++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp	Wed Apr 13 14:58:58 2022 +0200
@@ -414,13 +414,14 @@
             typename SourceType,
             bool UseRound,
             bool Invert>
-  static void ShiftScaleInternal(ImageAccessor& target,
-                                 const ImageAccessor& source,
-                                 float a,
-                                 float b,
-                                 const TargetType LowestValue)
+  static void ShiftScaleIntegerInternal(ImageAccessor& target,
+                                        const ImageAccessor& source,
+                                        float a,
+                                        float b)
   // This function can be applied inplace (source == target)
   {
+    assert(target.GetFormat() != PixelFormat_Float32);
+    
     if (source.GetWidth() != target.GetWidth() ||
         source.GetHeight() != target.GetHeight())
     {
@@ -433,9 +434,9 @@
       throw OrthancException(ErrorCode_IncompatibleImageFormat);
     }
     
-    const TargetType minPixelValue = LowestValue;
+    const TargetType minPixelValue = std::numeric_limits<TargetType>::min();
     const TargetType maxPixelValue = std::numeric_limits<TargetType>::max();
-    const float minFloatValue = static_cast<float>(LowestValue);
+    const float minFloatValue = static_cast<float>(minPixelValue);
     const float maxFloatValue = static_cast<float>(maxPixelValue);
 
     const unsigned int height = target.GetHeight();
@@ -477,6 +478,44 @@
     }
   }
 
+
+  template <typename SourceType>
+  static void ShiftScaleFloatInternal(ImageAccessor& target,
+                                      const ImageAccessor& source,
+                                      float a,
+                                      float b)
+  // This function can be applied inplace (source == target)
+  {
+    assert(target.GetFormat() == PixelFormat_Float32);
+    
+    if (source.GetWidth() != target.GetWidth() ||
+        source.GetHeight() != target.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (&source == &target &&
+        source.GetFormat() != target.GetFormat())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+    
+    const unsigned int height = target.GetHeight();
+    const unsigned int width = target.GetWidth();
+    
+    for (unsigned int y = 0; y < height; y++)
+    {
+      float* p = reinterpret_cast<float*>(target.GetRow(y));
+      const SourceType* q = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++, q++)
+      {
+        *p = a * static_cast<float>(*q) + b;
+      }
+    }
+  }
+  
+
   template <typename PixelType>
   static void ShiftRightInternal(ImageAccessor& image,
                                  unsigned int shift)
@@ -549,10 +588,6 @@
     assert(sizeof(SourceType) == source.GetBytesPerPixel() &&
            sizeof(TargetType) == target.GetBytesPerPixel());
     
-    // WARNING - "::min()" should be replaced by "::lowest()" if
-    // dealing with float or double (which is not the case so far)
-    assert(sizeof(TargetType) <= 2);  // Safeguard to remember about "float/double"
-    const TargetType minTargetValue = std::numeric_limits<TargetType>::min();
     const TargetType maxTargetValue = std::numeric_limits<TargetType>::max();
     const float maxFloatValue = static_cast<float>(maxTargetValue);
     
@@ -564,11 +599,11 @@
 
     if (invert)
     {
-      ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue);
+      ShiftScaleIntegerInternal<TargetType, SourceType, false, true>(target, source, a, b);
     }
     else
     {
-      ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue);
+      ShiftScaleIntegerInternal<TargetType, SourceType, false, false>(target, source, a, b);
     }
   }
 
@@ -1435,45 +1470,44 @@
       case PixelFormat_Grayscale8:
         if (useRound)
         {
-          ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
+          ShiftScaleIntegerInternal<uint8_t, uint8_t, true, false>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
+          ShiftScaleIntegerInternal<uint8_t, uint8_t, false, false>(image, image, a, b);
         }
         return;
 
       case PixelFormat_Grayscale16:
         if (useRound)
         {
-          ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
+          ShiftScaleIntegerInternal<uint16_t, uint16_t, true, false>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
+          ShiftScaleIntegerInternal<uint16_t, uint16_t, false, false>(image, image, a, b);
         }
         return;
 
       case PixelFormat_SignedGrayscale16:
         if (useRound)
         {
-          ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
+          ShiftScaleIntegerInternal<int16_t, int16_t, true, false>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
+          ShiftScaleIntegerInternal<int16_t, int16_t, false, false>(image, image, a, b);
         }
         return;
 
       case PixelFormat_Float32:
-        // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double.
         if (useRound)
         {
-          ShiftScaleInternal<float, float, true, false>(image, image, a, b, -std::numeric_limits<float>::max());
+          ShiftScaleFloatInternal<float>(image, image, a, b);
         }
         else
         {
-          ShiftScaleInternal<float, float, false, false>(image, image, a, b, -std::numeric_limits<float>::max());
+          ShiftScaleFloatInternal<float>(image, image, a, b);
         }
         return;
 
@@ -1509,13 +1543,11 @@
           case PixelFormat_Float32:
             if (useRound)
             {
-              ShiftScaleInternal<uint8_t, float, true, false>(
-                target, source, a, b, std::numeric_limits<uint8_t>::min());
+              ShiftScaleIntegerInternal<uint8_t, float, true, false>(target, source, a, b);
             }
             else
             {
-              ShiftScaleInternal<uint8_t, float, false, false>(
-                target, source, a, b, std::numeric_limits<uint8_t>::min());
+              ShiftScaleIntegerInternal<uint8_t, float, false, false>(target, source, a, b);
             }
             return;
 
--- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Wed Mar 30 15:45:54 2022 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Wed Apr 13 14:58:58 2022 +0200
@@ -1058,6 +1058,29 @@
 }
 
 
+TEST(ImageProcessing, ShiftFloatBuggy)
+{
+  // This test failed in Orthanc 1.10.1
+  
+  Image image(PixelFormat_Float32, 3, 1, false);
+  ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, -1.0f, 0, 0);
+  ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 0.0f, 1, 0);
+  ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 1.0f, 2, 0);
+
+  std::unique_ptr<Image> cloned(Image::Clone(image));
+
+  ImageProcessing::ShiftScale2(image, 0, 0.000539, true);
+  ASSERT_FLOAT_EQ(-0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(image, 0, 0));
+  ASSERT_FLOAT_EQ(0.0f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(image, 1, 0));
+  ASSERT_FLOAT_EQ(0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(image, 2, 0));
+
+  ImageProcessing::ShiftScale2(*cloned, 0, 0.000539, false);
+  ASSERT_FLOAT_EQ(-0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(*cloned, 0, 0));
+  ASSERT_FLOAT_EQ(0.0f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(*cloned, 1, 0));
+  ASSERT_FLOAT_EQ(0.000539f, ImageTraits<PixelFormat_Float32>::GetFloatPixel(*cloned, 2, 0));
+}
+
+
 TEST(ImageProcessing, ShiftScale2)
 {
   std::vector<float> va;
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Mar 30 15:45:54 2022 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Apr 13 14:58:58 2022 +0200
@@ -3711,7 +3711,13 @@
 
     try
     {
-      *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data, size) ? 1 : 0);
+      if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+      }
+      
+      *isReadOnly = (that.StoreFile(WebDavConvertPath(pathSize, pathItems), data,
+                                    static_cast<size_t>(size)) ? 1 : 0);
       return OrthancPluginErrorCode_Success;
     }
     catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
--- a/OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py	Wed Mar 30 15:45:54 2022 +0200
+++ b/OrthancServer/Resources/Samples/ImportDicomFiles/OrthancImport.py	Wed Apr 13 14:58:58 2022 +0200
@@ -63,7 +63,7 @@
 WARNING: This script will remove all the content of your
 Orthanc instance running on %s!
 
-Are you sure ["yes" to go on]?""" % args.server)
+Are you sure ["yes" to go on]?""" % args.url)
 
     if sys.stdin.readline().strip() != 'yes':
         print('Aborting...')
@@ -116,7 +116,8 @@
     info = r.json()
     COUNT_DICOM += 1
 
-    if not info['ParentStudy'] in IMPORTED_STUDIES:
+    if (isinstance(info, dict) and
+        not info['ParentStudy'] in IMPORTED_STUDIES):
         IMPORTED_STUDIES.add(info['ParentStudy'])
         
         r2 = requests.get('%s/instances/%s/tags?short' % (args.url, info['ID']),