changeset 800:ecedd89055db

generation of DICOM images from PNG files
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 06 May 2014 16:33:40 +0200
parents 777b6b694da6
children 262feb14f92f
files CMakeLists.txt Core/DicomFormat/DicomIntegerPixelAccessor.cpp Core/DicomFormat/DicomTag.h Core/Enumerations.cpp Core/Enumerations.h Core/ImageFormats/ImageAccessor.cpp Core/ImageFormats/ImageAccessor.h Core/ImageFormats/PngReader.cpp Core/ImageFormats/PngWriter.cpp Core/Toolbox.cpp Core/Toolbox.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h UnitTestsSources/FromDcmtk.cpp
diffstat 15 files changed, 339 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue May 06 12:55:41 2014 +0200
+++ b/CMakeLists.txt	Tue May 06 16:33:40 2014 +0200
@@ -173,6 +173,7 @@
   Core/MultiThreading/SharedMessageQueue.cpp
   Core/MultiThreading/ThreadedCommandProcessor.cpp
   Core/ImageFormats/ImageAccessor.cpp
+  Core/ImageFormats/ImageBuffer.cpp
   Core/ImageFormats/PngReader.cpp
   Core/ImageFormats/PngWriter.cpp
   Core/SQLite/Connection.cpp
@@ -333,6 +334,7 @@
     ${ORTHANC_ROOT}/Core/MultiThreading/ThreadedCommandProcessor.cpp
     ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp
     ${ORTHANC_ROOT}/Core/ImageFormats/ImageAccessor.cpp
+    ${ORTHANC_ROOT}/Core/ImageFormats/ImageBuffer.cpp
     ${ORTHANC_ROOT}/Core/ImageFormats/PngReader.cpp
     ${ORTHANC_ROOT}/OrthancCppClient/OrthancConnection.cpp
     ${ORTHANC_ROOT}/OrthancCppClient/Series.cpp
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Tue May 06 16:33:40 2014 +0200
@@ -44,15 +44,6 @@
 
 namespace Orthanc
 {
-  static const DicomTag COLUMNS(0x0028, 0x0011);
-  static const DicomTag ROWS(0x0028, 0x0010);
-  static const DicomTag SAMPLES_PER_PIXEL(0x0028, 0x0002);
-  static const DicomTag BITS_ALLOCATED(0x0028, 0x0100);
-  static const DicomTag BITS_STORED(0x0028, 0x0101);
-  static const DicomTag HIGH_BIT(0x0028, 0x0102);
-  static const DicomTag PIXEL_REPRESENTATION(0x0028, 0x0103);
-  static const DicomTag PLANAR_CONFIGURATION(0x0028, 0x0006);
-
   DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values,
                                                        const void* pixelData,
                                                        size_t size) :
@@ -67,19 +58,19 @@
 
     try
     {
-      width_ = boost::lexical_cast<unsigned int>(values.GetValue(COLUMNS).AsString());
-      height_ = boost::lexical_cast<unsigned int>(values.GetValue(ROWS).AsString());
-      samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(SAMPLES_PER_PIXEL).AsString());
-      bitsAllocated = boost::lexical_cast<unsigned int>(values.GetValue(BITS_ALLOCATED).AsString());
-      bitsStored = boost::lexical_cast<unsigned int>(values.GetValue(BITS_STORED).AsString());
-      highBit = boost::lexical_cast<unsigned int>(values.GetValue(HIGH_BIT).AsString());
-      pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(PIXEL_REPRESENTATION).AsString());
+      width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).AsString());
+      height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).AsString());
+      samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).AsString());
+      bitsAllocated = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).AsString());
+      bitsStored = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).AsString());
+      highBit = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).AsString());
+      pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).AsString());
 
       if (samplesPerPixel_ > 1)
       {
         // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1
         // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.3/
-        planarConfiguration_ = boost::lexical_cast<unsigned int>(values.GetValue(PLANAR_CONFIGURATION).AsString());
+        planarConfiguration_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).AsString());
       }
     }
     catch (boost::bad_lexical_cast)
--- a/Core/DicomFormat/DicomTag.h	Tue May 06 12:55:41 2014 +0200
+++ b/Core/DicomFormat/DicomTag.h	Tue May 06 16:33:40 2014 +0200
@@ -115,4 +115,15 @@
   static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
   static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052);
   static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061);
+
+  // Tags for images
+  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+  static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
+  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
+  static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102);
+  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006);
+  static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
 }
--- a/Core/Enumerations.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/Core/Enumerations.cpp	Tue May 06 16:33:40 2014 +0200
@@ -247,6 +247,19 @@
   }
 
 
+  const char* EnumerationToString(ImageFormat format)
+  {
+    switch (format)
+    {
+      case ImageFormat_Png:
+        return "Png";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   ResourceType StringToResourceType(const char* type)
   {
     std::string s(type);
@@ -269,9 +282,44 @@
     {
       return ResourceType_Instance;
     }
-    else
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ImageFormat StringToImageFormat(const char* format)
+  {
+    std::string s(format);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PNG")
     {
-      throw OrthancException(ErrorCode_ParameterOutOfRange);
+      return ImageFormat_Png;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  unsigned int GetBytesPerPixel(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        return 1;
+
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        return 2;
+
+      case PixelFormat_RGB24:
+        return 3;
+
+      case PixelFormat_RGBA32:
+        return 4;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
 }
--- a/Core/Enumerations.h	Tue May 06 12:55:41 2014 +0200
+++ b/Core/Enumerations.h	Tue May 06 16:33:40 2014 +0200
@@ -85,6 +85,13 @@
     PixelFormat_RGB24,
 
     /**
+     * {summary}{Color image in RGBA32 format.}
+     * {description}{This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    PixelFormat_RGBA32,
+
+    /**
      * {summary}{Graylevel 8bpp image.}
      * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
      **/
@@ -213,6 +220,12 @@
   };
 
 
+  enum ImageFormat
+  {
+    ImageFormat_Png = 1
+  };
+
+
   /**
    * WARNING: Do not change the explicit values in the enumerations
    * below this point. This would result in incompatible databases
@@ -250,5 +263,11 @@
 
   const char* EnumerationToString(ResourceType type);
 
+  const char* EnumerationToString(ImageFormat format);
+
   ResourceType StringToResourceType(const char* type);
+
+  ImageFormat StringToImageFormat(const char* format);
+
+  unsigned int GetBytesPerPixel(PixelFormat format);
 }
--- a/Core/ImageFormats/ImageAccessor.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/Core/ImageFormats/ImageAccessor.cpp	Tue May 06 16:33:40 2014 +0200
@@ -38,7 +38,7 @@
 
 namespace Orthanc
 {
-  void* ImageAccessor::GetBuffer()
+  void* ImageAccessor::GetBuffer() const
   {
     if (readOnly_)
     {
@@ -62,7 +62,7 @@
   }
 
 
-  void* ImageAccessor::GetRow(unsigned int y) 
+  void* ImageAccessor::GetRow(unsigned int y) const
   {
     if (readOnly_)
     {
--- a/Core/ImageFormats/ImageAccessor.h	Tue May 06 12:55:41 2014 +0200
+++ b/Core/ImageFormats/ImageAccessor.h	Tue May 06 16:33:40 2014 +0200
@@ -82,11 +82,11 @@
       return buffer_;
     }
 
-    void* GetBuffer();
+    void* GetBuffer() const;
 
     const void* GetConstRow(unsigned int y) const;
 
-    void* GetRow(unsigned int y);
+    void* GetRow(unsigned int y) const;
 
     void AssignEmpty(PixelFormat format);
 
--- a/Core/ImageFormats/PngReader.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/Core/ImageFormats/PngReader.cpp	Tue May 06 16:33:40 2014 +0200
@@ -171,6 +171,11 @@
       format = PixelFormat_RGB24;
       pitch = 3 * width;
     }
+    else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8)
+    {
+      format = PixelFormat_RGBA32;
+      pitch = 4 * width;
+    }
     else
     {
       throw OrthancException(ErrorCode_NotImplemented);
--- a/Core/ImageFormats/PngWriter.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/Core/ImageFormats/PngWriter.cpp	Tue May 06 16:33:40 2014 +0200
@@ -140,6 +140,11 @@
       pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
       break;
 
+    case PixelFormat_RGBA32:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA;
+      break;
+
     case PixelFormat_Grayscale8:
       pimpl_->bitDepth_ = 8;
       pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
--- a/Core/Toolbox.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/Core/Toolbox.cpp	Tue May 06 16:33:40 2014 +0200
@@ -803,5 +803,26 @@
 
     result.push_back(currentItem);
   }
+
+
+  void Toolbox::DecodeDataUriScheme(std::string& mime,
+                                    std::string& content,
+                                    const std::string& source)
+  {
+    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
+                         boost::regex::icase /* case insensitive search */);
+
+    boost::cmatch what;
+    if (regex_match(source.c_str(), what, pattern))
+    {
+      mime = what[1];
+      content = what[2];
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
 }
 
--- a/Core/Toolbox.h	Tue May 06 12:55:41 2014 +0200
+++ b/Core/Toolbox.h	Tue May 06 16:33:40 2014 +0200
@@ -122,5 +122,9 @@
     void TokenizeString(std::vector<std::string>& result,
                         const std::string& source,
                         char separator);
+
+    void DecodeDataUriScheme(std::string& mime,
+                             std::string& content,
+                             const std::string& source);
   }
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue May 06 16:33:40 2014 +0200
@@ -96,10 +96,7 @@
       std::string value = replacements[name].asString();
 
       DicomTag tag = FromDcmtkBridge::ParseTag(name);
-      if (tag != DICOM_TAG_PIXEL_DATA)
-      {
-        target.Replace(tag, value);
-      }
+      target.Replace(tag, value);
 
       VLOG(1) << "Replace: " << name << " " << tag << " == " << value << std::endl;
     }
@@ -406,6 +403,7 @@
   static void Create(RestApi::PostCall& call)
   {
     // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}'
+    // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}'
 
     Json::Value request;
     if (call.ParseJsonRequest(request) && request.isObject())
@@ -414,6 +412,13 @@
       ParseReplacements(modification, request);
 
       ParsedDicomFile dicom;
+
+      if (modification.IsReplaced(DICOM_TAG_PIXEL_DATA))
+      {
+        dicom.EmbedImage(modification.GetReplacement(DICOM_TAG_PIXEL_DATA));
+        modification.Keep(DICOM_TAG_PIXEL_DATA);
+      }
+
       modification.Apply(dicom);
 
       std::string id;
--- a/OrthancServer/ParsedDicomFile.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Tue May 06 16:33:40 2014 +0200
@@ -87,6 +87,7 @@
 #include "../Core/DicomFormat/DicomString.h"
 #include "../Core/DicomFormat/DicomNullValue.h"
 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
+#include "../Core/ImageFormats/PngReader.h"
 
 #include <list>
 #include <limits>
@@ -1087,4 +1088,132 @@
   {
     return new ParsedDicomFile(*this);
   }
+
+
+  void ParsedDicomFile::EmbedImage(const std::string& dataUriScheme)
+  {
+    std::string mime, content;
+    Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme);
+
+    std::string decoded = Toolbox::DecodeBase64(content);
+
+    if (mime == "image/png")
+    {
+      PngReader reader;
+      reader.ReadFromMemory(decoded);
+      EmbedImage(reader);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor)
+  {
+    if (accessor.GetFormat() != PixelFormat_Grayscale8 &&
+        accessor.GetFormat() != PixelFormat_Grayscale16 &&
+        accessor.GetFormat() != PixelFormat_RGB24 &&
+        accessor.GetFormat() != PixelFormat_RGBA32)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    if (accessor.GetFormat() == PixelFormat_RGBA32)
+    {
+      LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
+    }
+
+    // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
+
+    Remove(DICOM_TAG_PIXEL_DATA);
+    Replace(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
+    Replace(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
+    Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
+    Replace(DICOM_TAG_NUMBER_OF_FRAMES, "1");
+    Replace(DICOM_TAG_PIXEL_REPRESENTATION, "0");  // Unsigned pixels
+    Replace(DICOM_TAG_PLANAR_CONFIGURATION, "0");  // Color channels are interleaved
+    Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
+    Replace(DICOM_TAG_BITS_ALLOCATED, "8");
+    Replace(DICOM_TAG_BITS_STORED, "8");
+    Replace(DICOM_TAG_HIGH_BIT, "7");
+
+    unsigned int bytesPerPixel = 1;
+
+    switch (accessor.GetFormat())
+    {
+      case PixelFormat_RGB24:
+      case PixelFormat_RGBA32:
+        Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
+        Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
+        bytesPerPixel = 3;
+        break;
+
+      case PixelFormat_Grayscale8:
+        break;
+
+      case PixelFormat_Grayscale16:
+        Replace(DICOM_TAG_BITS_ALLOCATED, "16");
+        Replace(DICOM_TAG_BITS_STORED, "16");
+        Replace(DICOM_TAG_HIGH_BIT, "15");
+        bytesPerPixel = 2;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), 
+               DICOM_TAG_PIXEL_DATA.GetElement());
+
+    std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key));
+
+    unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
+    Uint8* target = NULL;
+    pixels->createUint8Array(accessor.GetHeight() * pitch, target);
+
+    for (unsigned int y = 0; y < accessor.GetHeight(); y++)
+    {
+      switch (accessor.GetFormat())
+      {
+        case PixelFormat_RGB24:
+        case PixelFormat_Grayscale8:
+        case PixelFormat_Grayscale16:
+        case PixelFormat_SignedGrayscale16:
+        {
+          if (Toolbox::DetectEndianness() != Endianness_Little)
+          {
+            throw OrthancException(ErrorCode_NotImplemented);
+          }
+
+          memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch);
+          target += pitch;
+          break;
+        }
+
+        case PixelFormat_RGBA32:
+        {
+          // The alpha channel is not supported by the DICOM standard
+          const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
+          for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4)
+          {
+            target[0] = source[0];
+            target[1] = source[1];
+            target[2] = source[2];
+          }
+
+          break;
+        }
+          
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
+      }
+    }
+
+    if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }    
+  }
 }
--- a/OrthancServer/ParsedDicomFile.h	Tue May 06 12:55:41 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Tue May 06 16:33:40 2014 +0200
@@ -35,6 +35,7 @@
 #include "../Core/DicomFormat/DicomInstanceHasher.h"
 #include "../Core/RestApi/RestApiOutput.h"
 #include "ServerEnumerations.h"
+#include "../Core/ImageFormats/ImageAccessor.h"
 
 namespace Orthanc
 {
@@ -87,6 +88,10 @@
     void SaveToMemoryBuffer(std::string& buffer);
 
     void SaveToFile(const std::string& path);
+
+    void EmbedImage(const ImageAccessor& accessor);
+
+    void EmbedImage(const std::string& dataUriScheme);
   };
 
 }
--- a/UnitTestsSources/FromDcmtk.cpp	Tue May 06 12:55:41 2014 +0200
+++ b/UnitTestsSources/FromDcmtk.cpp	Tue May 06 16:33:40 2014 +0200
@@ -4,6 +4,9 @@
 #include "../OrthancServer/OrthancInitialization.h"
 #include "../OrthancServer/DicomModification.h"
 #include "../Core/OrthancException.h"
+#include "../Core/ImageFormats/ImageBuffer.h"
+#include "../Core/ImageFormats/PngReader.h"
+#include "../Core/ImageFormats/PngWriter.h"
 
 using namespace Orthanc;
 
@@ -47,3 +50,67 @@
     f->SaveToFile(b);
   }
 }
+
+
+#include <dcmdata/dcuid.h>
+
+TEST(DicomModification, Png)
+{
+  // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image)
+  std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+
+  std::string m, c;
+  Toolbox::DecodeDataUriScheme(m, c, s);
+
+  ASSERT_EQ("image/png", m);
+  ASSERT_EQ(116, c.size());
+
+  std::string cc = Toolbox::DecodeBase64(c);
+
+  Toolbox::WriteFile(cc, "/tmp/tata.png");
+
+  PngReader reader;
+  reader.ReadFromMemory(cc);
+
+  ASSERT_EQ(5, reader.GetHeight());
+  ASSERT_EQ(5, reader.GetWidth());
+  ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
+
+  ParsedDicomFile o;
+  o.EmbedImage(s);
+  o.SaveToFile("png1.dcm");
+
+  // Red dot, without alpha channel
+  s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII=";
+  o.EmbedImage(s);
+  o.SaveToFile("png2.dcm");
+
+  // Check box in Graylevel8
+  s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII=";
+  o.EmbedImage(s);
+  //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
+  o.SaveToFile("png3.dcm");
+
+
+  {
+    // Gradient in Graylevel16
+
+    ImageBuffer img;
+    img.SetWidth(256);
+    img.SetHeight(256);
+    img.SetFormat(PixelFormat_Grayscale16);
+
+    int v = 0;
+    for (unsigned int y = 0; y < img.GetHeight(); y++)
+    {
+      uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y));
+      for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++)
+      {
+        *p = v;
+      }
+    }
+
+    o.EmbedImage(img.GetAccessor());
+    o.SaveToFile("png4.dcm");
+  }
+}