changeset 407:842a3c7cfdc0

renames
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 12 Nov 2018 11:44:20 +0100
parents 5d359b115b29
children 6834c236b36d
files Applications/Samples/SingleFrameEditorApplication.h
diffstat 1 files changed, 728 insertions(+), 717 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/SingleFrameEditorApplication.h	Sun Nov 11 18:17:50 2018 +0100
+++ b/Applications/Samples/SingleFrameEditorApplication.h	Mon Nov 12 11:44:20 2018 +0100
@@ -39,6 +39,9 @@
 #include <Plugins/Samples/Common/DicomDatasetReader.h>
 #include <Plugins/Samples/Common/FullOrthancDataset.h>
 
+
+// Export using PAM is faster than using PNG, but requires Orthanc
+// core >= 1.4.3
 #define EXPORT_USING_PAM  1
 
 
@@ -48,13 +51,13 @@
 
 namespace OrthancStone
 {
-  class BitmapStack :
+  class RadiologyScene :
     public IObserver,
     public IObservable
   {
   public:
-    typedef OriginMessage<MessageType_Widget_GeometryChanged, BitmapStack> GeometryChangedMessage;
-    typedef OriginMessage<MessageType_Widget_ContentChanged, BitmapStack> ContentChangedMessage;
+    typedef OriginMessage<MessageType_Widget_GeometryChanged, RadiologyScene> GeometryChangedMessage;
+    typedef OriginMessage<MessageType_Widget_ContentChanged, RadiologyScene> ContentChangedMessage;
 
 
     enum Corner
@@ -67,8 +70,10 @@
 
 
 
-    class Bitmap : public boost::noncopyable
+    class Layer : public boost::noncopyable
     {
+      friend class RadiologyScene;
+      
     private:
       size_t        index_;
       bool          hasSize_;
@@ -187,8 +192,77 @@
       }
 
 
+      void SetIndex(size_t index)
+      {
+        index_ = index;
+      }
+
+      
+      bool Contains(double x,
+                    double y) const
+      {
+        ApplyTransform(x, y, transformInverse_);
+        
+        unsigned int cropX, cropY, cropWidth, cropHeight;
+        GetCrop(cropX, cropY, cropWidth, cropHeight);
+
+        return (x >= cropX && x <= cropX + cropWidth &&
+                y >= cropY && y <= cropY + cropHeight);
+      }
+
+
+      void DrawBorders(CairoContext& context,
+                       double zoom)
+      {
+        unsigned int cx, cy, width, height;
+        GetCrop(cx, cy, width, height);
+
+        double dx = static_cast<double>(cx);
+        double dy = static_cast<double>(cy);
+        double dwidth = static_cast<double>(width);
+        double dheight = static_cast<double>(height);
+
+        cairo_t* cr = context.GetObject();
+        cairo_set_line_width(cr, 2.0 / zoom);
+        
+        double x, y;
+        x = dx;
+        y = dy;
+        ApplyTransform(x, y, transform_);
+        cairo_move_to(cr, x, y);
+
+        x = dx + dwidth;
+        y = dy;
+        ApplyTransform(x, y, transform_);
+        cairo_line_to(cr, x, y);
+
+        x = dx + dwidth;
+        y = dy + dheight;
+        ApplyTransform(x, y, transform_);
+        cairo_line_to(cr, x, y);
+
+        x = dx;
+        y = dy + dheight;
+        ApplyTransform(x, y, transform_);
+        cairo_line_to(cr, x, y);
+
+        x = dx;
+        y = dy;
+        ApplyTransform(x, y, transform_);
+        cairo_line_to(cr, x, y);
+
+        cairo_stroke(cr);
+      }
+
+
+      static double Square(double x)
+      {
+        return x * x;
+      }
+
+
     public:
-      Bitmap() :
+      Layer() :
         index_(0),
         hasSize_(false),
         width_(0),
@@ -204,15 +278,10 @@
         UpdateTransform();
       }
 
-      virtual ~Bitmap()
+      virtual ~Layer()
       {
       }
 
-      void SetIndex(size_t index)
-      {
-        index_ = index;
-      }
-
       size_t GetIndex() const
       {
         return index_;
@@ -310,18 +379,6 @@
       }       
 
 
-      void CheckSize(unsigned int width,
-                     unsigned int height)
-      {
-        if (hasSize_ &&
-            (width != width_ ||
-             height != height_))
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
-        }
-      }
-      
-
       Extent2D GetExtent() const
       {
         Extent2D extent;
@@ -343,19 +400,6 @@
       }
 
 
-      bool Contains(double x,
-                    double y) const
-      {
-        ApplyTransform(x, y, transformInverse_);
-        
-        unsigned int cropX, cropY, cropWidth, cropHeight;
-        GetCrop(cropX, cropY, cropWidth, cropHeight);
-
-        return (x >= cropX && x <= cropX + cropWidth &&
-                y >= cropY && y <= cropY + cropHeight);
-      }
-
-
       bool GetPixel(unsigned int& imageX,
                     unsigned int& imageY,
                     double sceneX,
@@ -450,56 +494,6 @@
       }
 
 
-      void DrawBorders(CairoContext& context,
-                       double zoom)
-      {
-        unsigned int cx, cy, width, height;
-        GetCrop(cx, cy, width, height);
-
-        double dx = static_cast<double>(cx);
-        double dy = static_cast<double>(cy);
-        double dwidth = static_cast<double>(width);
-        double dheight = static_cast<double>(height);
-
-        cairo_t* cr = context.GetObject();
-        cairo_set_line_width(cr, 2.0 / zoom);
-        
-        double x, y;
-        x = dx;
-        y = dy;
-        ApplyTransform(x, y, transform_);
-        cairo_move_to(cr, x, y);
-
-        x = dx + dwidth;
-        y = dy;
-        ApplyTransform(x, y, transform_);
-        cairo_line_to(cr, x, y);
-
-        x = dx + dwidth;
-        y = dy + dheight;
-        ApplyTransform(x, y, transform_);
-        cairo_line_to(cr, x, y);
-
-        x = dx;
-        y = dy + dheight;
-        ApplyTransform(x, y, transform_);
-        cairo_line_to(cr, x, y);
-
-        x = dx;
-        y = dy;
-        ApplyTransform(x, y, transform_);
-        cairo_line_to(cr, x, y);
-
-        cairo_stroke(cr);
-      }
-
-
-      static double Square(double x)
-      {
-        return x * x;
-      }
-
-
       void GetCorner(double& x /* out */,
                      double& y /* out */,
                      Corner corner) const
@@ -556,10 +550,7 @@
       }
 
       virtual bool GetDefaultWindowing(float& center,
-                                       float& width) const
-      {
-        return false;
-      }
+                                       float& width) const = 0;
 
       virtual void Render(Orthanc::ImageAccessor& buffer,
                           const Matrix& viewTransform,
@@ -570,72 +561,72 @@
     }; 
 
 
-    class BitmapAccessor : public boost::noncopyable
+    class LayerAccessor : public boost::noncopyable
     {
     private:
-      BitmapStack&  stack_;
-      size_t        index_;
-      Bitmap*       bitmap_;
+      RadiologyScene&  scene_;
+      size_t           index_;
+      Layer*           layer_;
 
     public:
-      BitmapAccessor(BitmapStack& stack,
-                     size_t index) :
-        stack_(stack),
+      LayerAccessor(RadiologyScene& scene,
+                    size_t index) :
+        scene_(scene),
         index_(index)
       {
-        Bitmaps::iterator bitmap = stack.bitmaps_.find(index);
-        if (bitmap == stack.bitmaps_.end())
+        Layers::iterator layer = scene.layers_.find(index);
+        if (layer == scene.layers_.end())
         {
-          bitmap_ = NULL;
+          layer_ = NULL;
         }
         else
         {
-          assert(bitmap->second != NULL);
-          bitmap_ = bitmap->second;
+          assert(layer->second != NULL);
+          layer_ = layer->second;
         }
       }
 
-      BitmapAccessor(BitmapStack& stack,
-                     double x,
-                     double y) :
-        stack_(stack),
+      LayerAccessor(RadiologyScene& scene,
+                    double x,
+                    double y) :
+        scene_(scene),
         index_(0)  // Dummy initialization
       {
-        if (stack.LookupBitmap(index_, x, y))
+        if (scene.LookupLayer(index_, x, y))
         {
-          Bitmaps::iterator bitmap = stack.bitmaps_.find(index_);
+          Layers::iterator layer = scene.layers_.find(index_);
           
-          if (bitmap == stack.bitmaps_.end())
+          if (layer == scene.layers_.end())
           {
             throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
           }
           else
           {
-            assert(bitmap->second != NULL);
-            bitmap_ = bitmap->second;
+            assert(layer->second != NULL);
+            layer_ = layer->second;
           }
         }
         else
         {
-          bitmap_ = NULL;
+          layer_ = NULL;
         }
       }
 
       void Invalidate()
       {
-        bitmap_ = NULL;
+        layer_ = NULL;
       }
 
       bool IsValid() const
       {
-        return bitmap_ != NULL;
+        return layer_ != NULL;
       }
 
-      BitmapStack& GetStack() const
+      RadiologyScene& GetScene() const
       {
         if (IsValid())
         {
-          return stack_;
+          return scene_;
         }
         else
         {
@@ -655,11 +646,11 @@
         }
       }
 
-      Bitmap& GetBitmap() const
+      Layer& GetLayer() const
       {
         if (IsValid())
         {
-          return *bitmap_;
+          return *layer_;
         }
         else
         {
@@ -669,17 +660,18 @@
     };    
 
 
-    class AlphaBitmap : public Bitmap
+  private:
+    class AlphaLayer : public Layer
     {
     private:
-      const BitmapStack&                     stack_;
+      const RadiologyScene&                  scene_;
       std::auto_ptr<Orthanc::ImageAccessor>  alpha_;      // Grayscale8
       bool                                   useWindowing_;
       float                                  foreground_;
 
     public:
-      AlphaBitmap(const BitmapStack& stack) :
-        stack_(stack),
+      AlphaLayer(const RadiologyScene& scene) :
+        scene_(scene),
         useWindowing_(true),
         foreground_(0)
       {
@@ -719,6 +711,13 @@
       }                   
 
 
+      virtual bool GetDefaultWindowing(float& center,
+                                       float& width) const
+      {
+        return false;
+      }
+      
+
       virtual void Render(Orthanc::ImageAccessor& buffer,
                           const Matrix& viewTransform,
                           ImageInterpolation interpolation) const
@@ -755,7 +754,7 @@
         if (useWindowing_)
         {
           float center, width;
-          if (stack_.GetWindowing(center, width))
+          if (scene_.GetWindowing(center, width))
           {
             value = center + width / 2.0f;
           }
@@ -775,6 +774,7 @@
         }        
       }
 
+      
       virtual bool GetRange(float& minValue,
                             float& maxValue) const
       {
@@ -836,7 +836,7 @@
     }
       
 
-    class DicomBitmap : public Bitmap
+    class DicomLayer : public Layer
     {
     private:
       std::auto_ptr<Orthanc::ImageAccessor>  source_;  // Content of PixelData
@@ -974,22 +974,43 @@
 
 
 
-    typedef std::map<size_t, Bitmap*>  Bitmaps;
+    typedef std::map<size_t, Layer*>  Layers;
         
     OrthancApiClient&  orthanc_;
-    size_t             countBitmaps_;
+    size_t             countLayers_;
     bool               hasWindowing_;
     float              windowingCenter_;
     float              windowingWidth_;
-    Bitmaps            bitmaps_;
+    Layers             layers_;
+
+
+    Layer& RegisterLayer(Layer* layer)
+    {
+      if (layer == NULL)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
+      std::auto_ptr<Layer> raii(layer);
+      
+      size_t index = countLayers_++;
+      raii->SetIndex(index);
+      layers_[index] = raii.release();
+
+      EmitMessage(GeometryChangedMessage(*this));
+      EmitMessage(ContentChangedMessage(*this));
+
+      return *layer;
+    }
+    
 
   public:
-    BitmapStack(MessageBroker& broker,
-                OrthancApiClient& orthanc) :
+    RadiologyScene(MessageBroker& broker,
+                   OrthancApiClient& orthanc) :
       IObserver(broker),
       IObservable(broker),
       orthanc_(orthanc),
-      countBitmaps_(0),
+      countLayers_(0),
       hasWindowing_(false),
       windowingCenter_(0),  // Dummy initialization
       windowingWidth_(0)    // Dummy initialization
@@ -997,9 +1018,9 @@
     }
 
 
-    virtual ~BitmapStack()
+    virtual ~RadiologyScene()
     {
-      for (Bitmaps::iterator it = bitmaps_.begin(); it != bitmaps_.end(); it++)
+      for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++)
       {
         assert(it->second != NULL);
         delete it->second;
@@ -1044,38 +1065,18 @@
     }
 
 
-    Bitmap& RegisterBitmap(Bitmap* bitmap)
+    Layer& LoadText(const Orthanc::Font& font,
+                    const std::string& utf8)
     {
-      if (bitmap == NULL)
-      {
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-      }
-
-      std::auto_ptr<Bitmap> raii(bitmap);
-      
-      size_t index = countBitmaps_++;
-      raii->SetIndex(index);
-      bitmaps_[index] = raii.release();
-
-      EmitMessage(GeometryChangedMessage(*this));
-      EmitMessage(ContentChangedMessage(*this));
-
-      return *bitmap;
-    }
-    
-
-    Bitmap& LoadText(const Orthanc::Font& font,
-                     const std::string& utf8)
-    {
-      std::auto_ptr<AlphaBitmap>  alpha(new AlphaBitmap(*this));
+      std::auto_ptr<AlphaLayer>  alpha(new AlphaLayer(*this));
       alpha->LoadText(font, utf8);
 
-      return RegisterBitmap(alpha.release());
+      return RegisterLayer(alpha.release());
     }
 
     
-    Bitmap& LoadTestBlock(unsigned int width,
-                          unsigned int height)
+    Layer& LoadTestBlock(unsigned int width,
+                         unsigned int height)
     {
       std::auto_ptr<Orthanc::Image>  block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false));
 
@@ -1098,26 +1099,26 @@
         Orthanc::ImageProcessing::Set(region, color);
       }
 
-      std::auto_ptr<AlphaBitmap>  alpha(new AlphaBitmap(*this));
+      std::auto_ptr<AlphaLayer>  alpha(new AlphaLayer(*this));
       alpha->SetAlpha(block.release());
 
-      return RegisterBitmap(alpha.release());
+      return RegisterLayer(alpha.release());
     }
 
     
-    Bitmap& LoadFrame(const std::string& instance,
-                     unsigned int frame,
-                     bool httpCompression)
+    Layer& LoadDicomFrame(const std::string& instance,
+                          unsigned int frame,
+                          bool httpCompression)
     {
-      Bitmap& bitmap = RegisterBitmap(new DicomBitmap);
+      Layer& layer = RegisterLayer(new DicomLayer);
 
       {
         IWebService::Headers headers;
         std::string uri = "/instances/" + instance + "/tags";
         orthanc_.GetBinaryAsync(uri, headers,
-                                new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
-                                (*this, &BitmapStack::OnTagsReceived), NULL,
-                                new Orthanc::SingleValueObject<size_t>(bitmap.GetIndex()));
+                                new Callable<RadiologyScene, OrthancApiClient::BinaryResponseReadyMessage>
+                                (*this, &RadiologyScene::OnTagsReceived), NULL,
+                                new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
       }
 
       {
@@ -1131,12 +1132,12 @@
         
         std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast<std::string>(frame) + "/image-uint16";
         orthanc_.GetBinaryAsync(uri, headers,
-                                new Callable<BitmapStack, OrthancApiClient::BinaryResponseReadyMessage>
-                                (*this, &BitmapStack::OnFrameReceived), NULL,
-                                new Orthanc::SingleValueObject<size_t>(bitmap.GetIndex()));
+                                new Callable<RadiologyScene, OrthancApiClient::BinaryResponseReadyMessage>
+                                (*this, &RadiologyScene::OnFrameReceived), NULL,
+                                new Orthanc::SingleValueObject<size_t>(layer.GetIndex()));
       }
 
-      return bitmap;
+      return layer;
     }
 
     
@@ -1145,19 +1146,19 @@
       size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue();
 
       LOG(INFO) << "JSON received: " << message.GetUri().c_str()
-                << " (" << message.GetAnswerSize() << " bytes) for bitmap " << index;
+                << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
       
-      Bitmaps::iterator bitmap = bitmaps_.find(index);
-      if (bitmap != bitmaps_.end())
+      Layers::iterator layer = layers_.find(index);
+      if (layer != layers_.end())
       {
-        assert(bitmap->second != NULL);
+        assert(layer->second != NULL);
         
         OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize());
-        dynamic_cast<DicomBitmap*>(bitmap->second)->SetDicomTags(dicom);
+        dynamic_cast<DicomLayer*>(layer->second)->SetDicomTags(dicom);
 
         float c, w;
         if (!hasWindowing_ &&
-            bitmap->second->GetDefaultWindowing(c, w))
+            layer->second->GetDefaultWindowing(c, w))
         {
           hasWindowing_ = true;
           windowingCenter_ = c;
@@ -1174,12 +1175,12 @@
       size_t index = dynamic_cast<const Orthanc::SingleValueObject<size_t>&>(message.GetPayload()).GetValue();
       
       LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str()
-                << " (" << message.GetAnswerSize() << " bytes) for bitmap " << index;
+                << " (" << message.GetAnswerSize() << " bytes) for layer " << index;
       
-      Bitmaps::iterator bitmap = bitmaps_.find(index);
-      if (bitmap != bitmaps_.end())
+      Layers::iterator layer = layers_.find(index);
+      if (layer != layers_.end())
       {
-        assert(bitmap->second != NULL);
+        assert(layer->second != NULL);
 
         std::string content;
         if (message.GetAnswerSize() > 0)
@@ -1189,7 +1190,7 @@
         
         std::auto_ptr<Orthanc::PamReader> reader(new Orthanc::PamReader);
         reader->ReadFromMemory(content);
-        dynamic_cast<DicomBitmap*>(bitmap->second)->SetSourceImage(reader.release());
+        dynamic_cast<DicomLayer*>(layer->second)->SetSourceImage(reader.release());
 
         EmitMessage(ContentChangedMessage(*this));
       }
@@ -1200,8 +1201,8 @@
     {
       Extent2D extent;
 
-      for (Bitmaps::const_iterator it = bitmaps_.begin();
-           it != bitmaps_.end(); ++it)
+      for (Layers::const_iterator it = layers_.begin();
+           it != layers_.end(); ++it)
       {
         assert(it->second != NULL);
         extent.Union(it->second->GetExtent());
@@ -1218,10 +1219,10 @@
       Orthanc::ImageProcessing::Set(buffer, 0);
 
       // Render layers in the background-to-foreground order
-      for (size_t index = 0; index < countBitmaps_; index++)
+      for (size_t index = 0; index < countLayers_; index++)
       {
-        Bitmaps::const_iterator it = bitmaps_.find(index);
-        if (it != bitmaps_.end())
+        Layers::const_iterator it = layers_.find(index);
+        if (it != layers_.end())
         {
           assert(it->second != NULL);
           it->second->Render(buffer, viewTransform, interpolation);
@@ -1230,16 +1231,16 @@
     }
 
 
-    bool LookupBitmap(size_t& index /* out */,
-                      double x,
-                      double y) const
+    bool LookupLayer(size_t& index /* out */,
+                     double x,
+                     double y) const
     {
       // Render layers in the foreground-to-background order
-      for (size_t i = countBitmaps_; i > 0; i--)
+      for (size_t i = countLayers_; i > 0; i--)
       {
         index = i - 1;
-        Bitmaps::const_iterator it = bitmaps_.find(index);
-        if (it != bitmaps_.end())
+        Layers::const_iterator it = layers_.find(index);
+        if (it != layers_.end())
         {
           assert(it->second != NULL);
           if (it->second->Contains(x, y))
@@ -1254,12 +1255,12 @@
 
     
     void DrawBorder(CairoContext& context,
-                    unsigned int bitmap,
+                    unsigned int layer,
                     double zoom)
     {
-      Bitmaps::const_iterator found = bitmaps_.find(bitmap);
+      Layers::const_iterator found = layers_.find(layer);
         
-      if (found != bitmaps_.end())
+      if (found != layers_.end())
       {
         context.SetSourceColor(255, 0, 0);
         found->second->DrawBorders(context, zoom);
@@ -1272,8 +1273,8 @@
     {
       bool first = true;
       
-      for (Bitmaps::const_iterator it = bitmaps_.begin();
-           it != bitmaps_.end(); it++)
+      for (Layers::const_iterator it = layers_.begin();
+           it != layers_.end(); it++)
       {
         assert(it->second != NULL);
 
@@ -1302,11 +1303,14 @@
     }
 
 
+    // Export using PAM is faster than using PNG, but requires Orthanc
+    // core >= 1.4.3
     void Export(const Orthanc::DicomMap& dicom,
                 double pixelSpacingX,
                 double pixelSpacingY,
                 bool invert,
-                ImageInterpolation interpolation)
+                ImageInterpolation interpolation,
+                bool usePam)
     {
       if (pixelSpacingX <= 0 ||
           pixelSpacingY <= 0)
@@ -1314,7 +1318,7 @@
         throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
       
-      LOG(WARNING) << "Exporting DICOM";
+      LOG(INFO) << "Exporting DICOM";
 
       Extent2D extent = GetSceneExtent();
 
@@ -1345,17 +1349,16 @@
       {
         std::string content;
 
-#if EXPORT_USING_PAM == 1
+        if (usePam)
         {
           Orthanc::PamWriter writer;
           writer.WriteToMemory(content, rendered);
         }
-#else
+        else
         {
           Orthanc::PngWriter writer;
           writer.WriteToMemory(content, rendered);
         }
-#endif      
 
         Orthanc::Toolbox::EncodeBase64(base64, content);
       }
@@ -1380,7 +1383,10 @@
       json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] =
         (invert ? "MONOCHROME1" : "MONOCHROME2");
 
-      // WARNING: The order of PixelSpacing is Y/X
+      // WARNING: The order of PixelSpacing is Y/X. We use "%0.8f" to
+      // avoid floating-point numbers to grow over 16 characters,
+      // which would be invalid according to DICOM standard
+      // ("dciodvfy" would complain).
       char buf[32];
       sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX);
       
@@ -1396,24 +1402,23 @@
           boost::lexical_cast<std::string>(boost::math::iround(width));
       }
 
-#if EXPORT_USING_PAM == 1
-      json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + base64;
-#else
-      json["Content"] = "data:" + std::string(Orthanc::MIME_PNG) + ";base64," + base64;
-#endif
+      // This is Data URI scheme: https://en.wikipedia.org/wiki/Data_URI_scheme
+      json["Content"] = ("data:" +
+                         std::string(usePam ? Orthanc::MIME_PAM : Orthanc::MIME_PNG) +
+                         ";base64," + base64);
 
       orthanc_.PostJsonAsyncExpectJson(
         "/tools/create-dicom", json,
-        new Callable<BitmapStack, OrthancApiClient::JsonResponseReadyMessage>
-        (*this, &BitmapStack::OnDicomExported),
+        new Callable<RadiologyScene, OrthancApiClient::JsonResponseReadyMessage>
+        (*this, &RadiologyScene::OnDicomExported),
         NULL, NULL);
     }
 
 
     void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message)
     {
-      LOG(WARNING) << "DICOM export was successful:"
-                   << message.GetJson().toStyledString();
+      LOG(INFO) << "DICOM export was successful:"
+                << message.GetJson().toStyledString();
     }
   };
 
@@ -1498,63 +1503,63 @@
   };
 
 
-  class BitmapCommandBase : public UndoRedoStack::ICommand
+  class RadiologyLayerCommand : public UndoRedoStack::ICommand
   {
   private:
-    BitmapStack&  stack_;
-    size_t        bitmap_;
+    RadiologyScene&  scene_;
+    size_t           layer_;
 
   protected:
-    virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const = 0;
-
-    virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const = 0;
+    virtual void UndoInternal(RadiologyScene::Layer& layer) const = 0;
+
+    virtual void RedoInternal(RadiologyScene::Layer& layer) const = 0;
 
   public:
-    BitmapCommandBase(BitmapStack& stack,
-                      size_t bitmap) :
-      stack_(stack),
-      bitmap_(bitmap)
+    RadiologyLayerCommand(RadiologyScene& scene,
+                          size_t layer) :
+      scene_(scene),
+      layer_(layer)
     {
     }
 
-    BitmapCommandBase(const BitmapStack::BitmapAccessor& accessor) :
-      stack_(accessor.GetStack()),
-      bitmap_(accessor.GetIndex())
+    RadiologyLayerCommand(const RadiologyScene::LayerAccessor& accessor) :
+      scene_(accessor.GetScene()),
+      layer_(accessor.GetIndex())
     {
     }
 
     virtual void Undo() const
     {
-      BitmapStack::BitmapAccessor accessor(stack_, bitmap_);
+      RadiologyScene::LayerAccessor accessor(scene_, layer_);
 
       if (accessor.IsValid())
       {
-        UndoInternal(accessor.GetBitmap());
+        UndoInternal(accessor.GetLayer());
       }
     }
 
     virtual void Redo() const
     {
-      BitmapStack::BitmapAccessor accessor(stack_, bitmap_);
+      RadiologyScene::LayerAccessor accessor(scene_, layer_);
 
       if (accessor.IsValid())
       {
-        RedoInternal(accessor.GetBitmap());
+        RedoInternal(accessor.GetLayer());
       }
     }
   };
 
 
-  class RotateBitmapTracker : public IWorldSceneMouseTracker
+  class RadiologyLayerRotateTracker : public IWorldSceneMouseTracker
   {
   private:
-    UndoRedoStack&               undoRedoStack_;
-    BitmapStack::BitmapAccessor  accessor_;
-    double                       centerX_;
-    double                       centerY_;
-    double                       originalAngle_;
-    double                       clickAngle_;
-    bool                         roundAngles_;
+    UndoRedoStack&                 undoRedoStack_;
+    RadiologyScene::LayerAccessor  accessor_;
+    double                         centerX_;
+    double                         centerY_;
+    double                         originalAngle_;
+    double                         clickAngle_;
+    bool                           roundAngles_;
 
     bool ComputeAngle(double& angle /* out */,
                       double sceneX,
@@ -1578,7 +1583,7 @@
     }
 
 
-    class UndoRedoCommand : public BitmapCommandBase
+    class UndoRedoCommand : public RadiologyLayerCommand
     {
     private:
       double  sourceAngle_;
@@ -1590,44 +1595,44 @@
       }
       
     protected:
-      virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void UndoInternal(RadiologyScene::Layer& layer) const
       {
         LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
-        bitmap.SetAngle(sourceAngle_);
+        layer.SetAngle(sourceAngle_);
       }
 
-      virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void RedoInternal(RadiologyScene::Layer& layer) const
       {
         LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees";
-        bitmap.SetAngle(targetAngle_);
+        layer.SetAngle(targetAngle_);
       }
 
     public:
-      UndoRedoCommand(const RotateBitmapTracker& tracker) :
-        BitmapCommandBase(tracker.accessor_),
+      UndoRedoCommand(const RadiologyLayerRotateTracker& tracker) :
+        RadiologyLayerCommand(tracker.accessor_),
         sourceAngle_(tracker.originalAngle_),
-        targetAngle_(tracker.accessor_.GetBitmap().GetAngle())
+        targetAngle_(tracker.accessor_.GetLayer().GetAngle())
       {
       }
     };
 
       
   public:
-    RotateBitmapTracker(UndoRedoStack& undoRedoStack,
-                        BitmapStack& stack,
-                        const ViewportGeometry& view,
-                        size_t bitmap,
-                        double x,
-                        double y,
-                        bool roundAngles) :
+    RadiologyLayerRotateTracker(UndoRedoStack& undoRedoStack,
+                                RadiologyScene& scene,
+                                const ViewportGeometry& view,
+                                size_t layer,
+                                double x,
+                                double y,
+                                bool roundAngles) :
       undoRedoStack_(undoRedoStack),
-      accessor_(stack, bitmap),
+      accessor_(scene, layer),
       roundAngles_(roundAngles)
     {
       if (accessor_.IsValid())
       {
-        accessor_.GetBitmap().GetCenter(centerX_, centerY_);
-        originalAngle_ = accessor_.GetBitmap().GetAngle();
+        accessor_.GetLayer().GetCenter(centerX_, centerY_);
+        originalAngle_ = accessor_.GetLayer().GetAngle();
 
         double sceneX, sceneY;
         view.MapDisplayToScene(sceneX, sceneY, x, y);
@@ -1677,24 +1682,24 @@
           angle = boost::math::round<double>((angle / ROUND_ANGLE) * ROUND_ANGLE);
         }
           
-        accessor_.GetBitmap().SetAngle(angle);
+        accessor_.GetLayer().SetAngle(angle);
       }
     }
   };
     
     
-  class MoveBitmapTracker : public IWorldSceneMouseTracker
+  class RadiologyLayerMoveTracker : public IWorldSceneMouseTracker
   {
   private:
-    UndoRedoStack&               undoRedoStack_;
-    BitmapStack::BitmapAccessor  accessor_;
-    double                       clickX_;
-    double                       clickY_;
-    double                       panX_;
-    double                       panY_;
-    bool                         oneAxis_;
-
-    class UndoRedoCommand : public BitmapCommandBase
+    UndoRedoStack&                 undoRedoStack_;
+    RadiologyScene::LayerAccessor  accessor_;
+    double                         clickX_;
+    double                         clickY_;
+    double                         panX_;
+    double                         panY_;
+    bool                           oneAxis_;
+
+    class UndoRedoCommand : public RadiologyLayerCommand
     {
     private:
       double  sourceX_;
@@ -1703,45 +1708,45 @@
       double  targetY_;
 
     protected:
-      virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void UndoInternal(RadiologyScene::Layer& layer) const
       {
-        bitmap.SetPan(sourceX_, sourceY_);
+        layer.SetPan(sourceX_, sourceY_);
       }
 
-      virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void RedoInternal(RadiologyScene::Layer& layer) const
       {
-        bitmap.SetPan(targetX_, targetY_);
+        layer.SetPan(targetX_, targetY_);
       }
 
     public:
-      UndoRedoCommand(const MoveBitmapTracker& tracker) :
-        BitmapCommandBase(tracker.accessor_),
+      UndoRedoCommand(const RadiologyLayerMoveTracker& tracker) :
+        RadiologyLayerCommand(tracker.accessor_),
         sourceX_(tracker.panX_),
         sourceY_(tracker.panY_),
-        targetX_(tracker.accessor_.GetBitmap().GetPanX()),
-        targetY_(tracker.accessor_.GetBitmap().GetPanY())
+        targetX_(tracker.accessor_.GetLayer().GetPanX()),
+        targetY_(tracker.accessor_.GetLayer().GetPanY())
       {
       }
     };
 
 
   public:
-    MoveBitmapTracker(UndoRedoStack& undoRedoStack,
-                      BitmapStack& stack,
-                      size_t bitmap,
-                      double x,
-                      double y,
-                      bool oneAxis) :
+    RadiologyLayerMoveTracker(UndoRedoStack& undoRedoStack,
+                              RadiologyScene& scene,
+                              size_t layer,
+                              double x,
+                              double y,
+                              bool oneAxis) :
       undoRedoStack_(undoRedoStack),
-      accessor_(stack, bitmap),
+      accessor_(scene, layer),
       clickX_(x),
       clickY_(y),
       oneAxis_(oneAxis)
     {
       if (accessor_.IsValid())
       {
-        panX_ = accessor_.GetBitmap().GetPanX();
-        panY_ = accessor_.GetBitmap().GetPanY();
+        panX_ = accessor_.GetLayer().GetPanX();
+        panY_ = accessor_.GetLayer().GetPanY();
       }
     }
 
@@ -1778,34 +1783,34 @@
         {
           if (fabs(dx) > fabs(dy))
           {
-            accessor_.GetBitmap().SetPan(dx + panX_, panY_);
+            accessor_.GetLayer().SetPan(dx + panX_, panY_);
           }
           else
           {
-            accessor_.GetBitmap().SetPan(panX_, dy + panY_);
+            accessor_.GetLayer().SetPan(panX_, dy + panY_);
           }
         }
         else
         {
-          accessor_.GetBitmap().SetPan(dx + panX_, dy + panY_);
+          accessor_.GetLayer().SetPan(dx + panX_, dy + panY_);
         }
       }
     }
   };
 
 
-  class CropBitmapTracker : public IWorldSceneMouseTracker
+  class RadiologyLayerCropTracker : public IWorldSceneMouseTracker
   {
   private:
-    UndoRedoStack&               undoRedoStack_;
-    BitmapStack::BitmapAccessor  accessor_;
-    BitmapStack::Corner          corner_;
-    unsigned int                 cropX_;
-    unsigned int                 cropY_;
-    unsigned int                 cropWidth_;
-    unsigned int                 cropHeight_;
-
-    class UndoRedoCommand : public BitmapCommandBase
+    UndoRedoStack&                 undoRedoStack_;
+    RadiologyScene::LayerAccessor  accessor_;
+    RadiologyScene::Corner         corner_;
+    unsigned int                   cropX_;
+    unsigned int                   cropY_;
+    unsigned int                   cropWidth_;
+    unsigned int                   cropHeight_;
+
+    class UndoRedoCommand : public RadiologyLayerCommand
     {
     private:
       unsigned int  sourceCropX_;
@@ -1818,45 +1823,45 @@
       unsigned int  targetCropHeight_;
 
     protected:
-      virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void UndoInternal(RadiologyScene::Layer& layer) const
       {
-        bitmap.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
+        layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_);
       }
 
-      virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void RedoInternal(RadiologyScene::Layer& layer) const
       {
-        bitmap.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
+        layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_);
       }
 
     public:
-      UndoRedoCommand(const CropBitmapTracker& tracker) :
-        BitmapCommandBase(tracker.accessor_),
+      UndoRedoCommand(const RadiologyLayerCropTracker& tracker) :
+        RadiologyLayerCommand(tracker.accessor_),
         sourceCropX_(tracker.cropX_),
         sourceCropY_(tracker.cropY_),
         sourceCropWidth_(tracker.cropWidth_),
         sourceCropHeight_(tracker.cropHeight_)
       {
-        tracker.accessor_.GetBitmap().GetCrop(targetCropX_, targetCropY_,
-                                              targetCropWidth_, targetCropHeight_);
+        tracker.accessor_.GetLayer().GetCrop(targetCropX_, targetCropY_,
+                                             targetCropWidth_, targetCropHeight_);
       }
     };
 
 
   public:
-    CropBitmapTracker(UndoRedoStack& undoRedoStack,
-                      BitmapStack& stack,
-                      const ViewportGeometry& view,
-                      size_t bitmap,
-                      double x,
-                      double y,
-                      BitmapStack::Corner corner) :
+    RadiologyLayerCropTracker(UndoRedoStack& undoRedoStack,
+                              RadiologyScene& scene,
+                              const ViewportGeometry& view,
+                              size_t layer,
+                              double x,
+                              double y,
+                              RadiologyScene::Corner corner) :
       undoRedoStack_(undoRedoStack),
-      accessor_(stack, bitmap),
+      accessor_(scene, layer),
       corner_(corner)
     {
       if (accessor_.IsValid())
       {
-        accessor_.GetBitmap().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);          
+        accessor_.GetLayer().GetCrop(cropX_, cropY_, cropWidth_, cropHeight_);          
       }
     }
 
@@ -1888,13 +1893,13 @@
       {
         unsigned int x, y;
         
-        BitmapStack::Bitmap& bitmap = accessor_.GetBitmap();
-        if (bitmap.GetPixel(x, y, sceneX, sceneY))
+        RadiologyScene::Layer& layer = accessor_.GetLayer();
+        if (layer.GetPixel(x, y, sceneX, sceneY))
         {
           unsigned int targetX, targetWidth;
 
-          if (corner_ == BitmapStack::Corner_TopLeft ||
-              corner_ == BitmapStack::Corner_BottomLeft)
+          if (corner_ == RadiologyScene::Corner_TopLeft ||
+              corner_ == RadiologyScene::Corner_BottomLeft)
           {
             targetX = std::min(x, cropX_ + cropWidth_);
             targetWidth = cropX_ + cropWidth_ - targetX;
@@ -1907,8 +1912,8 @@
 
           unsigned int targetY, targetHeight;
 
-          if (corner_ == BitmapStack::Corner_TopLeft ||
-              corner_ == BitmapStack::Corner_TopRight)
+          if (corner_ == RadiologyScene::Corner_TopLeft ||
+              corner_ == RadiologyScene::Corner_TopRight)
           {
             targetY = std::min(y, cropY_ + cropHeight_);
             targetHeight = cropY_ + cropHeight_ - targetY;
@@ -1919,27 +1924,27 @@
             targetHeight = std::max(y, cropY_) - cropY_;
           }
 
-          bitmap.SetCrop(targetX, targetY, targetWidth, targetHeight);
+          layer.SetCrop(targetX, targetY, targetWidth, targetHeight);
         }
       }
     }
   };
     
     
-  class ResizeBitmapTracker : public IWorldSceneMouseTracker
+  class RadiologyLayerResizeTracker : public IWorldSceneMouseTracker
   {
   private:
-    UndoRedoStack&               undoRedoStack_;
-    BitmapStack::BitmapAccessor  accessor_;
-    bool                         roundScaling_;
-    double                       originalSpacingX_;
-    double                       originalSpacingY_;
-    double                       originalPanX_;
-    double                       originalPanY_;
-    BitmapStack::Corner          oppositeCorner_;
-    double                       oppositeX_;
-    double                       oppositeY_;
-    double                       baseScaling_;
+    UndoRedoStack&                 undoRedoStack_;
+    RadiologyScene::LayerAccessor  accessor_;
+    bool                           roundScaling_;
+    double                         originalSpacingX_;
+    double                         originalSpacingY_;
+    double                         originalPanX_;
+    double                         originalPanY_;
+    RadiologyScene::Corner         oppositeCorner_;
+    double                         oppositeX_;
+    double                         oppositeY_;
+    double                         baseScaling_;
 
     static double ComputeDistance(double x1,
                                   double y1,
@@ -1951,7 +1956,7 @@
       return sqrt(dx * dx + dy * dy);
     }
       
-    class UndoRedoCommand : public BitmapCommandBase
+    class UndoRedoCommand : public RadiologyLayerCommand
     {
     private:
       double   sourceSpacingX_;
@@ -1964,77 +1969,77 @@
       double   targetPanY_;
 
     protected:
-      virtual void UndoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void UndoInternal(RadiologyScene::Layer& layer) const
       {
-        bitmap.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
-        bitmap.SetPan(sourcePanX_, sourcePanY_);
+        layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_);
+        layer.SetPan(sourcePanX_, sourcePanY_);
       }
 
-      virtual void RedoInternal(BitmapStack::Bitmap& bitmap) const
+      virtual void RedoInternal(RadiologyScene::Layer& layer) const
       {
-        bitmap.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
-        bitmap.SetPan(targetPanX_, targetPanY_);
+        layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_);
+        layer.SetPan(targetPanX_, targetPanY_);
       }
 
     public:
-      UndoRedoCommand(const ResizeBitmapTracker& tracker) :
-        BitmapCommandBase(tracker.accessor_),
+      UndoRedoCommand(const RadiologyLayerResizeTracker& tracker) :
+        RadiologyLayerCommand(tracker.accessor_),
         sourceSpacingX_(tracker.originalSpacingX_),
         sourceSpacingY_(tracker.originalSpacingY_),
         sourcePanX_(tracker.originalPanX_),
         sourcePanY_(tracker.originalPanY_),
-        targetSpacingX_(tracker.accessor_.GetBitmap().GetPixelSpacingX()),
-        targetSpacingY_(tracker.accessor_.GetBitmap().GetPixelSpacingY()),
-        targetPanX_(tracker.accessor_.GetBitmap().GetPanX()),
-        targetPanY_(tracker.accessor_.GetBitmap().GetPanY())
+        targetSpacingX_(tracker.accessor_.GetLayer().GetPixelSpacingX()),
+        targetSpacingY_(tracker.accessor_.GetLayer().GetPixelSpacingY()),
+        targetPanX_(tracker.accessor_.GetLayer().GetPanX()),
+        targetPanY_(tracker.accessor_.GetLayer().GetPanY())
       {
       }
     };
 
 
   public:
-    ResizeBitmapTracker(UndoRedoStack& undoRedoStack,
-                        BitmapStack& stack,
-                        size_t bitmap,
-                        double x,
-                        double y,
-                        BitmapStack::Corner corner,
-                        bool roundScaling) :
+    RadiologyLayerResizeTracker(UndoRedoStack& undoRedoStack,
+                                RadiologyScene& scene,
+                                size_t layer,
+                                double x,
+                                double y,
+                                RadiologyScene::Corner corner,
+                                bool roundScaling) :
       undoRedoStack_(undoRedoStack),
-      accessor_(stack, bitmap),
+      accessor_(scene, layer),
       roundScaling_(roundScaling)
     {
       if (accessor_.IsValid() &&
-          accessor_.GetBitmap().IsResizeable())
+          accessor_.GetLayer().IsResizeable())
       {
-        originalSpacingX_ = accessor_.GetBitmap().GetPixelSpacingX();
-        originalSpacingY_ = accessor_.GetBitmap().GetPixelSpacingY();
-        originalPanX_ = accessor_.GetBitmap().GetPanX();
-        originalPanY_ = accessor_.GetBitmap().GetPanY();
+        originalSpacingX_ = accessor_.GetLayer().GetPixelSpacingX();
+        originalSpacingY_ = accessor_.GetLayer().GetPixelSpacingY();
+        originalPanX_ = accessor_.GetLayer().GetPanX();
+        originalPanY_ = accessor_.GetLayer().GetPanY();
 
         switch (corner)
         {
-          case BitmapStack::Corner_TopLeft:
-            oppositeCorner_ = BitmapStack::Corner_BottomRight;
+          case RadiologyScene::Corner_TopLeft:
+            oppositeCorner_ = RadiologyScene::Corner_BottomRight;
             break;
 
-          case BitmapStack::Corner_TopRight:
-            oppositeCorner_ = BitmapStack::Corner_BottomLeft;
+          case RadiologyScene::Corner_TopRight:
+            oppositeCorner_ = RadiologyScene::Corner_BottomLeft;
             break;
 
-          case BitmapStack::Corner_BottomLeft:
-            oppositeCorner_ = BitmapStack::Corner_TopRight;
+          case RadiologyScene::Corner_BottomLeft:
+            oppositeCorner_ = RadiologyScene::Corner_TopRight;
             break;
 
-          case BitmapStack::Corner_BottomRight:
-            oppositeCorner_ = BitmapStack::Corner_TopLeft;
+          case RadiologyScene::Corner_BottomRight:
+            oppositeCorner_ = RadiologyScene::Corner_TopLeft;
             break;
 
           default:
             throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
         }
 
-        accessor_.GetBitmap().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
+        accessor_.GetLayer().GetCorner(oppositeX_, oppositeY_, oppositeCorner_);
 
         double d = ComputeDistance(x, y, oppositeX_, oppositeY_);
         if (d >= std::numeric_limits<float>::epsilon())
@@ -2063,7 +2068,7 @@
     virtual void MouseUp()
     {
       if (accessor_.IsValid() &&
-          accessor_.GetBitmap().IsResizeable())
+          accessor_.GetLayer().IsResizeable())
       {
         undoRedoStack_.Add(new UndoRedoCommand(*this));
       }
@@ -2077,7 +2082,7 @@
       static const double ROUND_SCALING = 0.1;
         
       if (accessor_.IsValid() &&
-          accessor_.GetBitmap().IsResizeable())
+          accessor_.GetLayer().IsResizeable())
       {
         double scaling = ComputeDistance(oppositeX_, oppositeY_, sceneX, sceneY) * baseScaling_;
 
@@ -2086,21 +2091,21 @@
           scaling = boost::math::round<double>((scaling / ROUND_SCALING) * ROUND_SCALING);
         }
           
-        BitmapStack::Bitmap& bitmap = accessor_.GetBitmap();
-        bitmap.SetPixelSpacing(scaling * originalSpacingX_,
-                               scaling * originalSpacingY_);
+        RadiologyScene::Layer& layer = accessor_.GetLayer();
+        layer.SetPixelSpacing(scaling * originalSpacingX_,
+                              scaling * originalSpacingY_);
 
         // Keep the opposite corner at a fixed location
         double ox, oy;
-        bitmap.GetCorner(ox, oy, oppositeCorner_);
-        bitmap.SetPan(bitmap.GetPanX() + oppositeX_ - ox,
-                      bitmap.GetPanY() + oppositeY_ - oy);
+        layer.GetCorner(ox, oy, oppositeCorner_);
+        layer.SetPan(layer.GetPanX() + oppositeX_ - ox,
+                     layer.GetPanY() + oppositeY_ - oy);
       }
     }
   };
 
 
-  class WindowingTracker : public IWorldSceneMouseTracker
+  class RadiologyWindowingTracker : public IWorldSceneMouseTracker
   {   
   public:
     enum Action
@@ -2112,17 +2117,17 @@
     };
     
   private:
-    UndoRedoStack&  undoRedoStack_;
-    BitmapStack&    stack_;
-    int             clickX_;
-    int             clickY_;
-    Action          leftAction_;
-    Action          rightAction_;
-    Action          upAction_;
-    Action          downAction_;
-    float           strength_;
-    float           sourceCenter_;
-    float           sourceWidth_;
+    UndoRedoStack&    undoRedoStack_;
+    RadiologyScene&   scene_;
+    int               clickX_;
+    int               clickY_;
+    Action            leftAction_;
+    Action            rightAction_;
+    Action            upAction_;
+    Action            downAction_;
+    float             strength_;
+    float             sourceCenter_;
+    float             sourceWidth_;
 
     static void ComputeAxisEffect(int& deltaCenter,
                                   int& deltaWidth,
@@ -2184,44 +2189,44 @@
     class UndoRedoCommand : public UndoRedoStack::ICommand
     {
     private:
-      BitmapStack&  stack_;
-      float         sourceCenter_;
-      float         sourceWidth_;
-      float         targetCenter_;
-      float         targetWidth_;
+      RadiologyScene&  scene_;
+      float            sourceCenter_;
+      float            sourceWidth_;
+      float            targetCenter_;
+      float            targetWidth_;
 
     public:
-      UndoRedoCommand(const WindowingTracker& tracker) :
-        stack_(tracker.stack_),
+      UndoRedoCommand(const RadiologyWindowingTracker& tracker) :
+        scene_(tracker.scene_),
         sourceCenter_(tracker.sourceCenter_),
         sourceWidth_(tracker.sourceWidth_)
       {
-        stack_.GetWindowingWithDefault(targetCenter_, targetWidth_);
+        scene_.GetWindowingWithDefault(targetCenter_, targetWidth_);
       }
 
       virtual void Undo() const
       {
-        stack_.SetWindowing(sourceCenter_, sourceWidth_);
+        scene_.SetWindowing(sourceCenter_, sourceWidth_);
       }
       
       virtual void Redo() const
       {
-        stack_.SetWindowing(targetCenter_, targetWidth_);
+        scene_.SetWindowing(targetCenter_, targetWidth_);
       }
     };
 
 
   public:
-    WindowingTracker(UndoRedoStack& undoRedoStack,
-                     BitmapStack& stack,
-                     int x,
-                     int y,
-                     Action leftAction,
-                     Action rightAction,
-                     Action upAction,
-                     Action downAction) :
+    RadiologyWindowingTracker(UndoRedoStack& undoRedoStack,
+                              RadiologyScene& scene,
+                              int x,
+                              int y,
+                              Action leftAction,
+                              Action rightAction,
+                              Action upAction,
+                              Action downAction) :
       undoRedoStack_(undoRedoStack),
-      stack_(stack),
+      scene_(scene),
       clickX_(x),
       clickY_(y),
       leftAction_(leftAction),
@@ -2229,10 +2234,10 @@
       upAction_(upAction),
       downAction_(downAction)
     {
-      stack_.GetWindowingWithDefault(sourceCenter_, sourceWidth_);
+      scene_.GetWindowingWithDefault(sourceCenter_, sourceWidth_);
 
       float minValue, maxValue;
-      stack.GetRange(minValue, maxValue);
+      scene.GetRange(minValue, maxValue);
 
       assert(minValue <= maxValue);
 
@@ -2291,30 +2296,30 @@
 
       float newCenter = sourceCenter_ + (deltaCenter / SCALE * strength_);
       float newWidth = sourceWidth_ + (deltaWidth / SCALE * strength_);
-      stack_.SetWindowing(newCenter, newWidth);
+      scene_.SetWindowing(newCenter, newWidth);
     }
   };
 
 
-  class BitmapStackWidget :
+  class RadiologyWidget :
     public WorldSceneWidget,
     public IObserver
   {
   private:
-    BitmapStack&                   stack_;
+    RadiologyScene&                scene_;
     std::auto_ptr<Orthanc::Image>  floatBuffer_;
     std::auto_ptr<CairoSurface>    cairoBuffer_;
     bool                           invert_;
     ImageInterpolation             interpolation_;
     bool                           hasSelection_;
-    size_t                         selectedBitmap_;
+    size_t                         selectedLayer_;
 
     virtual bool RenderInternal(unsigned int width,
                                 unsigned int height,
                                 ImageInterpolation interpolation)
     {
       float windowCenter, windowWidth;
-      stack_.GetWindowingWithDefault(windowCenter, windowWidth);
+      scene_.GetWindowingWithDefault(windowCenter, windowWidth);
       
       float x0 = windowCenter - windowWidth / 2.0f;
       float x1 = windowCenter + windowWidth / 2.0f;
@@ -2339,7 +2344,7 @@
           cairoBuffer_.reset(new CairoSurface(width, height));
         }
 
-        stack_.Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
+        scene_.Render(*floatBuffer_, GetView().GetMatrix(), interpolation);
         
         // Conversion from Float32 to BGRA32 (cairo). Very similar to
         // GrayscaleFrameRenderer => TODO MERGE?
@@ -2390,7 +2395,7 @@
   protected:
     virtual Extent2D GetSceneExtent()
     {
-      return stack_.GetSceneExtent();
+      return scene_.GetSceneExtent();
     }
 
     virtual bool RenderScene(CairoContext& context,
@@ -2416,31 +2421,36 @@
 
       if (hasSelection_)
       {
-        stack_.DrawBorder(context, selectedBitmap_, view.GetZoom());
+        scene_.DrawBorder(context, selectedLayer_, view.GetZoom());
       }
 
       return true;
     }
 
   public:
-    BitmapStackWidget(MessageBroker& broker,
-                      BitmapStack& stack,
-                      const std::string& name) :
+    RadiologyWidget(MessageBroker& broker,
+                    RadiologyScene& scene,
+                    const std::string& name) :
       WorldSceneWidget(name),
       IObserver(broker),
-      stack_(stack),
+      scene_(scene),
       invert_(false),
       interpolation_(ImageInterpolation_Nearest),
       hasSelection_(false),
-      selectedBitmap_(0)    // Dummy initialization
+      selectedLayer_(0)    // Dummy initialization
     {
-      stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::GeometryChangedMessage>(*this, &BitmapStackWidget::OnGeometryChanged));
-      stack.RegisterObserverCallback(new Callable<BitmapStackWidget, BitmapStack::ContentChangedMessage>(*this, &BitmapStackWidget::OnContentChanged));
+      scene.RegisterObserverCallback(
+        new Callable<RadiologyWidget, RadiologyScene::GeometryChangedMessage>
+        (*this, &RadiologyWidget::OnGeometryChanged));
+
+      scene.RegisterObserverCallback(
+        new Callable<RadiologyWidget, RadiologyScene::ContentChangedMessage>
+        (*this, &RadiologyWidget::OnContentChanged));
     }
 
-    BitmapStack& GetStack() const
+    RadiologyScene& GetScene() const
     {
-      return stack_;
+      return scene_;
     }
 
     void Unselect()
@@ -2448,17 +2458,17 @@
       hasSelection_ = false;
     }
 
-    void Select(size_t bitmap)
+    void Select(size_t layer)
     {
       hasSelection_ = true;
-      selectedBitmap_ = bitmap;
+      selectedLayer_ = layer;
     }
 
-    bool LookupSelectedBitmap(size_t& bitmap)
+    bool LookupSelectedLayer(size_t& layer)
     {
       if (hasSelection_)
       {
-        bitmap = selectedBitmap_;
+        layer = selectedLayer_;
         return true;
       }
       else
@@ -2467,13 +2477,13 @@
       }
     }
 
-    void OnGeometryChanged(const BitmapStack::GeometryChangedMessage& message)
+    void OnGeometryChanged(const RadiologyScene::GeometryChangedMessage& message)
     {
       LOG(INFO) << "Geometry has changed";
       FitContent();
     }
 
-    void OnContentChanged(const BitmapStack::ContentChangedMessage& message)
+    void OnContentChanged(const RadiologyScene::ContentChangedMessage& message)
     {
       LOG(INFO) << "Content has changed";
       NotifyContentChanged();
@@ -2515,325 +2525,326 @@
   };
 
   
-  class BitmapStackInteractor :
-    public IWorldSceneInteractor,
-    public IObserver
+  namespace Samples
   {
-  private:
-    enum Tool
+    class RadiologyEditorInteractor :
+      public IWorldSceneInteractor,
+      public IObserver
     {
-      Tool_Move,
-      Tool_Rotate,
-      Tool_Crop,
-      Tool_Resize,
-      Tool_Windowing
-    };
+    private:
+      enum Tool
+      {
+        Tool_Move,
+        Tool_Rotate,
+        Tool_Crop,
+        Tool_Resize,
+        Tool_Windowing
+      };
         
 
-    UndoRedoStack      undoRedoStack_;
-    Tool               tool_;
-
-
-    static double GetHandleSize()
-    {
-      return 10.0;
-    }
+      UndoRedoStack      undoRedoStack_;
+      Tool               tool_;
+
+
+      static double GetHandleSize()
+      {
+        return 10.0;
+      }
     
          
-  public:
-    BitmapStackInteractor(MessageBroker& broker) :
-      IObserver(broker),
-      tool_(Tool_Move)
-    {
-    }
+    public:
+      RadiologyEditorInteractor(MessageBroker& broker) :
+        IObserver(broker),
+        tool_(Tool_Move)
+      {
+      }
     
-    virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget,
-                                                        const ViewportGeometry& view,
-                                                        MouseButton button,
-                                                        KeyboardModifiers modifiers,
-                                                        int viewportX,
-                                                        int viewportY,
-                                                        double x,
-                                                        double y,
-                                                        IStatusBar* statusBar)
-    {
-      BitmapStackWidget& widget = dynamic_cast<BitmapStackWidget&>(worldWidget);
-
-      if (button == MouseButton_Left)
+      virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget,
+                                                          const ViewportGeometry& view,
+                                                          MouseButton button,
+                                                          KeyboardModifiers modifiers,
+                                                          int viewportX,
+                                                          int viewportY,
+                                                          double x,
+                                                          double y,
+                                                          IStatusBar* statusBar)
       {
-        size_t selected;
-        
-        if (tool_ == Tool_Windowing)
-        {
-          return new WindowingTracker(undoRedoStack_, widget.GetStack(),
-                                      viewportX, viewportY,
-                                      WindowingTracker::Action_DecreaseWidth,
-                                      WindowingTracker::Action_IncreaseWidth,
-                                      WindowingTracker::Action_DecreaseCenter,
-                                      WindowingTracker::Action_IncreaseCenter);
-        }
-        else if (!widget.LookupSelectedBitmap(selected))
+        RadiologyWidget& widget = dynamic_cast<RadiologyWidget&>(worldWidget);
+
+        if (button == MouseButton_Left)
         {
-          // No bitmap is currently selected
-          size_t bitmap;
-          if (widget.GetStack().LookupBitmap(bitmap, x, y))
+          size_t selected;
+        
+          if (tool_ == Tool_Windowing)
           {
-            widget.Select(bitmap);
+            return new RadiologyWindowingTracker(undoRedoStack_, widget.GetScene(),
+                                                 viewportX, viewportY,
+                                                 RadiologyWindowingTracker::Action_DecreaseWidth,
+                                                 RadiologyWindowingTracker::Action_IncreaseWidth,
+                                                 RadiologyWindowingTracker::Action_DecreaseCenter,
+                                                 RadiologyWindowingTracker::Action_IncreaseCenter);
           }
-
-          return NULL;
-        }
-        else if (tool_ == Tool_Crop ||
-                 tool_ == Tool_Resize)
-        {
-          BitmapStack::BitmapAccessor accessor(widget.GetStack(), selected);
-          BitmapStack::Corner corner;
-          if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+          else if (!widget.LookupSelectedLayer(selected))
           {
-            switch (tool_)
+            // No layer is currently selected
+            size_t layer;
+            if (widget.GetScene().LookupLayer(layer, x, y))
             {
-              case Tool_Crop:
-                return new CropBitmapTracker(undoRedoStack_, widget.GetStack(), view, selected, x, y, corner);
-
-              case Tool_Resize:
-                return new ResizeBitmapTracker(undoRedoStack_, widget.GetStack(), selected, x, y, corner,
-                                               (modifiers & KeyboardModifiers_Shift));
-
-              default:
-                throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+              widget.Select(layer);
             }
+
+            return NULL;
           }
-          else
+          else if (tool_ == Tool_Crop ||
+                   tool_ == Tool_Resize)
           {
-            size_t bitmap;
-            
-            if (widget.GetStack().LookupBitmap(bitmap, x, y))
+            RadiologyScene::LayerAccessor accessor(widget.GetScene(), selected);
+            RadiologyScene::Corner corner;
+            if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
             {
-              widget.Select(bitmap);
+              switch (tool_)
+              {
+                case Tool_Crop:
+                  return new RadiologyLayerCropTracker(undoRedoStack_, widget.GetScene(), view, selected, x, y, corner);
+
+                case Tool_Resize:
+                  return new RadiologyLayerResizeTracker(undoRedoStack_, widget.GetScene(), selected, x, y, corner,
+                                                         (modifiers & KeyboardModifiers_Shift));
+
+                default:
+                  throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+              }
             }
             else
             {
-              widget.Unselect();
-            }
+              size_t layer;
             
-            return NULL;
-          }
-        }
-        else
-        {
-          size_t bitmap;
-
-          if (widget.GetStack().LookupBitmap(bitmap, x, y))
-          {
-            if (bitmap == selected)
-            {
-              switch (tool_)
+              if (widget.GetScene().LookupLayer(layer, x, y))
               {
-                case Tool_Move:
-                  return new MoveBitmapTracker(undoRedoStack_, widget.GetStack(), bitmap, x, y,
-                                               (modifiers & KeyboardModifiers_Shift));
-
-                case Tool_Rotate:
-                  return new RotateBitmapTracker(undoRedoStack_, widget.GetStack(), view, bitmap, x, y,
-                                                 (modifiers & KeyboardModifiers_Shift));
-                
-                default:
-                  break;
+                widget.Select(layer);
               }
-
-              return NULL;
-            }
-            else
-            {
-              widget.Select(bitmap);
+              else
+              {
+                widget.Unselect();
+              }
+            
               return NULL;
             }
           }
           else
           {
-            widget.Unselect();
-            return NULL;
+            size_t layer;
+
+            if (widget.GetScene().LookupLayer(layer, x, y))
+            {
+              if (layer == selected)
+              {
+                switch (tool_)
+                {
+                  case Tool_Move:
+                    return new RadiologyLayerMoveTracker(undoRedoStack_, widget.GetScene(), layer, x, y,
+                                                         (modifiers & KeyboardModifiers_Shift));
+
+                  case Tool_Rotate:
+                    return new RadiologyLayerRotateTracker(undoRedoStack_, widget.GetScene(), view, layer, x, y,
+                                                           (modifiers & KeyboardModifiers_Shift));
+                
+                  default:
+                    break;
+                }
+
+                return NULL;
+              }
+              else
+              {
+                widget.Select(layer);
+                return NULL;
+              }
+            }
+            else
+            {
+              widget.Unselect();
+              return NULL;
+            }
+          }
+        }
+        else
+        {
+          return NULL;
+        }
+      }
+
+      virtual void MouseOver(CairoContext& context,
+                             WorldSceneWidget& worldWidget,
+                             const ViewportGeometry& view,
+                             double x,
+                             double y,
+                             IStatusBar* statusBar)
+      {
+        RadiologyWidget& widget = dynamic_cast<RadiologyWidget&>(worldWidget);
+
+#if 0
+        if (statusBar != NULL)
+        {
+          char buf[64];
+          sprintf(buf, "X = %.02f Y = %.02f (in cm)", x / 10.0, y / 10.0);
+          statusBar->SetMessage(buf);
+        }
+#endif
+
+        size_t selected;
+
+        if (widget.LookupSelectedLayer(selected) &&
+            (tool_ == Tool_Crop ||
+             tool_ == Tool_Resize))
+        {
+          RadiologyScene::LayerAccessor accessor(widget.GetScene(), selected);
+        
+          RadiologyScene::Corner corner;
+          if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+          {
+            accessor.GetLayer().GetCorner(x, y, corner);
+          
+            double z = 1.0 / view.GetZoom();
+          
+            context.SetSourceColor(255, 0, 0);
+            cairo_t* cr = context.GetObject();
+            cairo_set_line_width(cr, 2.0 * z);
+            cairo_move_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
+            cairo_line_to(cr, x + GetHandleSize() * z, y - GetHandleSize() * z);
+            cairo_line_to(cr, x + GetHandleSize() * z, y + GetHandleSize() * z);
+            cairo_line_to(cr, x - GetHandleSize() * z, y + GetHandleSize() * z);
+            cairo_line_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
+            cairo_stroke(cr);
           }
         }
       }
-      else
+
+      virtual void MouseWheel(WorldSceneWidget& widget,
+                              MouseWheelDirection direction,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
       {
-        return NULL;
-      }
-    }
-
-    virtual void MouseOver(CairoContext& context,
-                           WorldSceneWidget& worldWidget,
-                           const ViewportGeometry& view,
-                           double x,
-                           double y,
-                           IStatusBar* statusBar)
-    {
-      BitmapStackWidget& widget = dynamic_cast<BitmapStackWidget&>(worldWidget);
-
-#if 0
-      if (statusBar != NULL)
-      {
-        char buf[64];
-        sprintf(buf, "X = %.02f Y = %.02f (in cm)", x / 10.0, y / 10.0);
-        statusBar->SetMessage(buf);
       }
-#endif
-
-      size_t selected;
-
-      if (widget.LookupSelectedBitmap(selected) &&
-          (tool_ == Tool_Crop ||
-           tool_ == Tool_Resize))
+
+      virtual void KeyPressed(WorldSceneWidget& worldWidget,
+                              KeyboardKeys key,
+                              char keyChar,
+                              KeyboardModifiers modifiers,
+                              IStatusBar* statusBar)
       {
-        BitmapStack::BitmapAccessor accessor(widget.GetStack(), selected);
-        
-        BitmapStack::Corner corner;
-        if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize()))
+        RadiologyWidget& widget = dynamic_cast<RadiologyWidget&>(worldWidget);
+
+        switch (keyChar)
         {
-          accessor.GetBitmap().GetCorner(x, y, corner);
-          
-          double z = 1.0 / view.GetZoom();
+          case 'a':
+            widget.FitContent();
+            break;
+
+          case 'c':
+            tool_ = Tool_Crop;
+            break;
+
+          case 'e':
+          {
+            Orthanc::DicomMap tags;
+
+            // Minimal set of tags to generate a valid CR image
+            tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false);
+            tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false);
+            tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false);
+            //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false);
+            tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false);
+            tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false);
+            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false);
+            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false);
+            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false);
+            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false);
+            tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false);
+            tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false);
+            tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false);
+            tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false);
+            tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false);
+            tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false);
+
+            widget.GetScene().Export(tags, 0.1, 0.1, widget.IsInverted(),
+                                     widget.GetInterpolation(), EXPORT_USING_PAM);
+            break;
+          }
+
+          case 'i':
+            widget.SwitchInvert();
+            break;
+        
+          case 'm':
+            tool_ = Tool_Move;
+            break;
+
+          case 'n':
+          {
+            switch (widget.GetInterpolation())
+            {
+              case ImageInterpolation_Nearest:
+                LOG(INFO) << "Switching to bilinear interpolation";
+                widget.SetInterpolation(ImageInterpolation_Bilinear);
+                break;
+              
+              case ImageInterpolation_Bilinear:
+                LOG(INFO) << "Switching to nearest neighbor interpolation";
+                widget.SetInterpolation(ImageInterpolation_Nearest);
+                break;
+
+              default:
+                throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+            }
           
-          context.SetSourceColor(255, 0, 0);
-          cairo_t* cr = context.GetObject();
-          cairo_set_line_width(cr, 2.0 * z);
-          cairo_move_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
-          cairo_line_to(cr, x + GetHandleSize() * z, y - GetHandleSize() * z);
-          cairo_line_to(cr, x + GetHandleSize() * z, y + GetHandleSize() * z);
-          cairo_line_to(cr, x - GetHandleSize() * z, y + GetHandleSize() * z);
-          cairo_line_to(cr, x - GetHandleSize() * z, y - GetHandleSize() * z);
-          cairo_stroke(cr);
+            break;
+          }
+        
+          case 'r':
+            tool_ = Tool_Rotate;
+            break;
+
+          case 's':
+            tool_ = Tool_Resize;
+            break;
+
+          case 'w':
+            tool_ = Tool_Windowing;
+            break;
+
+          case 'y':
+            if (modifiers & KeyboardModifiers_Control)
+            {
+              undoRedoStack_.Redo();
+              widget.NotifyContentChanged();
+            }
+            break;
+
+          case 'z':
+            if (modifiers & KeyboardModifiers_Control)
+            {
+              undoRedoStack_.Undo();
+              widget.NotifyContentChanged();
+            }
+            break;
+        
+          default:
+            break;
         }
       }
-    }
-
-    virtual void MouseWheel(WorldSceneWidget& widget,
-                            MouseWheelDirection direction,
-                            KeyboardModifiers modifiers,
-                            IStatusBar* statusBar)
-    {
-    }
-
-    virtual void KeyPressed(WorldSceneWidget& worldWidget,
-                            KeyboardKeys key,
-                            char keyChar,
-                            KeyboardModifiers modifiers,
-                            IStatusBar* statusBar)
-    {
-      BitmapStackWidget& widget = dynamic_cast<BitmapStackWidget&>(worldWidget);
-
-      switch (keyChar)
-      {
-        case 'a':
-          widget.FitContent();
-          break;
-
-        case 'c':
-          tool_ = Tool_Crop;
-          break;
-
-        case 'e':
-        {
-          Orthanc::DicomMap tags;
-
-          // Minimal set of tags to generate a valid CR image
-          tags.SetValue(Orthanc::DICOM_TAG_ACCESSION_NUMBER, "NOPE", false);
-          tags.SetValue(Orthanc::DICOM_TAG_BODY_PART_EXAMINED, "PELVIS", false);
-          tags.SetValue(Orthanc::DICOM_TAG_INSTANCE_NUMBER, "1", false);
-          //tags.SetValue(Orthanc::DICOM_TAG_LATERALITY, "", false);
-          tags.SetValue(Orthanc::DICOM_TAG_MANUFACTURER, "OSIMIS", false);
-          tags.SetValue(Orthanc::DICOM_TAG_MODALITY, "CR", false);
-          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_BIRTH_DATE, "20000101", false);
-          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ID, "hello", false);
-          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_NAME, "HELLO^WORLD", false);
-          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_ORIENTATION, "", false);
-          tags.SetValue(Orthanc::DICOM_TAG_PATIENT_SEX, "M", false);
-          tags.SetValue(Orthanc::DICOM_TAG_REFERRING_PHYSICIAN_NAME, "HOUSE^MD", false);
-          tags.SetValue(Orthanc::DICOM_TAG_SERIES_NUMBER, "1", false);
-          tags.SetValue(Orthanc::DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.1", false);
-          tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false);
-          tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false);
-
-          widget.GetStack().Export(tags, 0.1, 0.1, widget.IsInverted(), widget.GetInterpolation());
-          break;
-        }
-
-        case 'i':
-          widget.SwitchInvert();
-          break;
-        
-        case 'm':
-          tool_ = Tool_Move;
-          break;
-
-        case 'n':
-        {
-          switch (widget.GetInterpolation())
-          {
-            case ImageInterpolation_Nearest:
-              LOG(INFO) << "Switching to bilinear interpolation";
-              widget.SetInterpolation(ImageInterpolation_Bilinear);
-              break;
-              
-            case ImageInterpolation_Bilinear:
-              LOG(INFO) << "Switching to nearest neighbor interpolation";
-              widget.SetInterpolation(ImageInterpolation_Nearest);
-              break;
-
-            default:
-              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
-          }
-          
-          break;
-        }
-        
-        case 'r':
-          tool_ = Tool_Rotate;
-          break;
-
-        case 's':
-          tool_ = Tool_Resize;
-          break;
-
-        case 'w':
-          tool_ = Tool_Windowing;
-          break;
-
-        case 'y':
-          if (modifiers & KeyboardModifiers_Control)
-          {
-            undoRedoStack_.Redo();
-            widget.NotifyContentChanged();
-          }
-          break;
-
-        case 'z':
-          if (modifiers & KeyboardModifiers_Control)
-          {
-            undoRedoStack_.Undo();
-            widget.NotifyContentChanged();
-          }
-          break;
-        
-        default:
-          break;
-      }
-    }
-  };
+    };
 
   
   
-  namespace Samples
-  {
     class SingleFrameEditorApplication :
       public SampleSingleCanvasApplicationBase,
       public IObserver
     {
     private:
       std::auto_ptr<OrthancApiClient>  orthancApiClient_;
-      std::auto_ptr<BitmapStack>       stack_;
-      BitmapStackInteractor            interactor_;
+      std::auto_ptr<RadiologyScene>    scene_;
+      RadiologyEditorInteractor        interactor_;
 
     public:
       SingleFrameEditorApplication(MessageBroker& broker) :
@@ -2876,7 +2887,7 @@
         statusBar.SetMessage("Use the key \"m\" to move objects");
         statusBar.SetMessage("Use the key \"n\" to switch between nearest neighbor and bilinear interpolation");
         statusBar.SetMessage("Use the key \"r\" to rotate objects");
-        statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM bitmaps)");
+        statusBar.SetMessage("Use the key \"s\" to resize objects (not applicable to DICOM layers)");
         statusBar.SetMessage("Use the key \"w\" to change windowing");
         
         statusBar.SetMessage("Use the key \"ctrl-z\" to undo action");
@@ -2896,29 +2907,29 @@
         Orthanc::FontRegistry fonts;
         fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);
         
-        stack_.reset(new BitmapStack(GetBroker(), *orthancApiClient_));
-        stack_->LoadFrame(instance, frame, false); //.SetPan(200, 0);
-        //stack_->LoadFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false);
+        scene_.reset(new RadiologyScene(GetBroker(), *orthancApiClient_));
+        scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0);
+        //scene_->LoadDicomFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false);
 
         {
-          BitmapStack::Bitmap& bitmap = stack_->LoadText(fonts.GetFont(0), "Hello\nworld");
-          //dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetForegroundValue(256);
-          dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetResizeable(true);
+          RadiologyScene::Layer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld");
+          //dynamic_cast<RadiologyScene::Layer&>(layer).SetForegroundValue(256);
+          dynamic_cast<RadiologyScene::Layer&>(layer).SetResizeable(true);
         }
         
         {
-          BitmapStack::Bitmap& bitmap = stack_->LoadTestBlock(100, 50);
-          //dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetForegroundValue(256);
-          dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetResizeable(true);
-          dynamic_cast<BitmapStack::AlphaBitmap&>(bitmap).SetPan(0, 200);
+          RadiologyScene::Layer& layer = scene_->LoadTestBlock(100, 50);
+          //dynamic_cast<RadiologyScene::Layer&>(layer).SetForegroundValue(256);
+          dynamic_cast<RadiologyScene::Layer&>(layer).SetResizeable(true);
+          dynamic_cast<RadiologyScene::Layer&>(layer).SetPan(0, 200);
         }
         
         
-        mainWidget_ = new BitmapStackWidget(GetBroker(), *stack_, "main-widget");
+        mainWidget_ = new RadiologyWidget(GetBroker(), *scene_, "main-widget");
         mainWidget_->SetTransmitMouseOver(true);
         mainWidget_->SetInteractor(interactor_);
 
-        //stack_->SetWindowing(128, 256);
+        //scene_->SetWindowing(128, 256);
       }
     };
   }