# HG changeset patch # User Sebastien Jodogne # Date 1542030730 -3600 # Node ID 6834c236b36dcee37a7d9622290d37e8922cb9c4 # Parent 842a3c7cfdc0d3efe53ece697eadcdf826f8dacb reorganization diff -r 842a3c7cfdc0 -r 6834c236b36d Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Mon Nov 12 11:44:20 2018 +0100 +++ b/Applications/Samples/SingleFrameEditorApplication.h Mon Nov 12 14:52:10 2018 +0100 @@ -23,9 +23,9 @@ #include "SampleApplicationBase.h" -#include "../../Framework/Toolbox/ImageGeometry.h" -#include "../../Framework/Toolbox/OrthancApiClient.h" -#include "../../Framework/Toolbox/DicomFrameConverter.h" +#include "../../Framework/Radiography/RadiographyScene.h" + +#include "../../Framework/Toolbox/UndoRedoStack.h" #include #include @@ -51,1478 +51,26 @@ namespace OrthancStone { - class RadiologyScene : - public IObserver, - public IObservable + class RadiographyLayerCommand : public UndoRedoStack::ICommand { - public: - typedef OriginMessage GeometryChangedMessage; - typedef OriginMessage ContentChangedMessage; - - - enum Corner - { - Corner_TopLeft, - Corner_TopRight, - Corner_BottomLeft, - Corner_BottomRight - }; - - - - class Layer : public boost::noncopyable - { - friend class RadiologyScene; - - private: - size_t index_; - bool hasSize_; - unsigned int width_; - unsigned int height_; - bool hasCrop_; - unsigned int cropX_; - unsigned int cropY_; - unsigned int cropWidth_; - unsigned int cropHeight_; - Matrix transform_; - Matrix transformInverse_; - double pixelSpacingX_; - double pixelSpacingY_; - double panX_; - double panY_; - double angle_; - bool resizeable_; - - - protected: - const Matrix& GetTransform() const - { - return transform_; - } - - - private: - static void ApplyTransform(double& x /* inout */, - double& y /* inout */, - const Matrix& transform) - { - Vector p; - LinearAlgebra::AssignVector(p, x, y, 1); - - Vector q = LinearAlgebra::Product(transform, p); - - if (!LinearAlgebra::IsNear(q[2], 1.0)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - x = q[0]; - y = q[1]; - } - } - - - void UpdateTransform() - { - transform_ = CreateScalingMatrix(pixelSpacingX_, pixelSpacingY_); - - double centerX, centerY; - GetCenter(centerX, centerY); - - transform_ = LinearAlgebra::Product( - CreateOffsetMatrix(panX_ + centerX, panY_ + centerY), - CreateRotationMatrix(angle_), - CreateOffsetMatrix(-centerX, -centerY), - transform_); - - LinearAlgebra::InvertMatrix(transformInverse_, transform_); - } - - - void AddToExtent(Extent2D& extent, - double x, - double y) const - { - ApplyTransform(x, y, transform_); - extent.AddPoint(x, y); - } - - - void GetCornerInternal(double& x, - double& y, - Corner corner, - unsigned int cropX, - unsigned int cropY, - unsigned int cropWidth, - unsigned int cropHeight) const - { - double dx = static_cast(cropX); - double dy = static_cast(cropY); - double dwidth = static_cast(cropWidth); - double dheight = static_cast(cropHeight); - - switch (corner) - { - case Corner_TopLeft: - x = dx; - y = dy; - break; - - case Corner_TopRight: - x = dx + dwidth; - y = dy; - break; - - case Corner_BottomLeft: - x = dx; - y = dy + dheight; - break; - - case Corner_BottomRight: - x = dx + dwidth; - y = dy + dheight; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - ApplyTransform(x, y, transform_); - } - - - 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(cx); - double dy = static_cast(cy); - double dwidth = static_cast(width); - double dheight = static_cast(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: - Layer() : - index_(0), - hasSize_(false), - width_(0), - height_(0), - hasCrop_(false), - pixelSpacingX_(1), - pixelSpacingY_(1), - panX_(0), - panY_(0), - angle_(0), - resizeable_(false) - { - UpdateTransform(); - } - - virtual ~Layer() - { - } - - size_t GetIndex() const - { - return index_; - } - - void ResetCrop() - { - hasCrop_ = false; - } - - void SetCrop(unsigned int x, - unsigned int y, - unsigned int width, - unsigned int height) - { - if (!hasSize_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - if (x + width > width_ || - y + height > height_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - hasCrop_ = true; - cropX_ = x; - cropY_ = y; - cropWidth_ = width; - cropHeight_ = height; - - UpdateTransform(); - } - - void GetCrop(unsigned int& x, - unsigned int& y, - unsigned int& width, - unsigned int& height) const - { - if (hasCrop_) - { - x = cropX_; - y = cropY_; - width = cropWidth_; - height = cropHeight_; - } - else - { - x = 0; - y = 0; - width = width_; - height = height_; - } - } - - void SetAngle(double angle) - { - angle_ = angle; - UpdateTransform(); - } - - double GetAngle() const - { - return angle_; - } - - void SetSize(unsigned int width, - unsigned int height) - { - if (hasSize_ && - (width != width_ || - height != height_)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); - } - - hasSize_ = true; - width_ = width; - height_ = height; - - UpdateTransform(); - } - - - unsigned int GetWidth() const - { - return width_; - } - - - unsigned int GetHeight() const - { - return height_; - } - - - Extent2D GetExtent() const - { - Extent2D extent; - - unsigned int x, y, width, height; - GetCrop(x, y, width, height); - - double dx = static_cast(x); - double dy = static_cast(y); - double dwidth = static_cast(width); - double dheight = static_cast(height); - - AddToExtent(extent, dx, dy); - AddToExtent(extent, dx + dwidth, dy); - AddToExtent(extent, dx, dy + dheight); - AddToExtent(extent, dx + dwidth, dy + dheight); - - return extent; - } - - - bool GetPixel(unsigned int& imageX, - unsigned int& imageY, - double sceneX, - double sceneY) const - { - if (width_ == 0 || - height_ == 0) - { - return false; - } - else - { - ApplyTransform(sceneX, sceneY, transformInverse_); - - int x = static_cast(std::floor(sceneX)); - int y = static_cast(std::floor(sceneY)); - - if (x < 0) - { - imageX = 0; - } - else if (x >= static_cast(width_)) - { - imageX = width_; - } - else - { - imageX = static_cast(x); - } - - if (y < 0) - { - imageY = 0; - } - else if (y >= static_cast(height_)) - { - imageY = height_; - } - else - { - imageY = static_cast(y); - } - - return true; - } - } - - - void SetPan(double x, - double y) - { - panX_ = x; - panY_ = y; - UpdateTransform(); - } - - - void SetPixelSpacing(double x, - double y) - { - pixelSpacingX_ = x; - pixelSpacingY_ = y; - UpdateTransform(); - } - - double GetPixelSpacingX() const - { - return pixelSpacingX_; - } - - double GetPixelSpacingY() const - { - return pixelSpacingY_; - } - - double GetPanX() const - { - return panX_; - } - - double GetPanY() const - { - return panY_; - } - - void GetCenter(double& centerX, - double& centerY) const - { - centerX = static_cast(width_) / 2.0; - centerY = static_cast(height_) / 2.0; - ApplyTransform(centerX, centerY, transform_); - } - - - void GetCorner(double& x /* out */, - double& y /* out */, - Corner corner) const - { - unsigned int cropX, cropY, cropWidth, cropHeight; - GetCrop(cropX, cropY, cropWidth, cropHeight); - GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight); - } - - - bool LookupCorner(Corner& corner /* out */, - double x, - double y, - double zoom, - double viewportDistance) const - { - static const Corner CORNERS[] = { - Corner_TopLeft, - Corner_TopRight, - Corner_BottomLeft, - Corner_BottomRight - }; - - unsigned int cropX, cropY, cropWidth, cropHeight; - GetCrop(cropX, cropY, cropWidth, cropHeight); - - double threshold = Square(viewportDistance / zoom); - - for (size_t i = 0; i < 4; i++) - { - double cx, cy; - GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight); + private: + RadiographyScene& scene_; + size_t layer_; - double d = Square(cx - x) + Square(cy - y); - - if (d <= threshold) - { - corner = CORNERS[i]; - return true; - } - } - - return false; - } - - bool IsResizeable() const - { - return resizeable_; - } - - void SetResizeable(bool resizeable) - { - resizeable_ = resizeable; - } - - virtual bool GetDefaultWindowing(float& center, - float& width) const = 0; - - virtual void Render(Orthanc::ImageAccessor& buffer, - const Matrix& viewTransform, - ImageInterpolation interpolation) const = 0; - - virtual bool GetRange(float& minValue, - float& maxValue) const = 0; - }; - - - class LayerAccessor : public boost::noncopyable - { - private: - RadiologyScene& scene_; - size_t index_; - Layer* layer_; - - public: - LayerAccessor(RadiologyScene& scene, - size_t index) : - scene_(scene), - index_(index) - { - Layers::iterator layer = scene.layers_.find(index); - if (layer == scene.layers_.end()) - { - layer_ = NULL; - } - else - { - assert(layer->second != NULL); - layer_ = layer->second; - } - } - - LayerAccessor(RadiologyScene& scene, - double x, - double y) : - scene_(scene), - index_(0) // Dummy initialization - { - if (scene.LookupLayer(index_, x, y)) - { - Layers::iterator layer = scene.layers_.find(index_); - - if (layer == scene.layers_.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - assert(layer->second != NULL); - layer_ = layer->second; - } - } - else - { - layer_ = NULL; - } - } - - void Invalidate() - { - layer_ = NULL; - } - - bool IsValid() const - { - return layer_ != NULL; - } - - RadiologyScene& GetScene() const - { - if (IsValid()) - { - return scene_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - size_t GetIndex() const - { - if (IsValid()) - { - return index_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - Layer& GetLayer() const - { - if (IsValid()) - { - return *layer_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - }; - - - private: - class AlphaLayer : public Layer - { - private: - const RadiologyScene& scene_; - std::auto_ptr alpha_; // Grayscale8 - bool useWindowing_; - float foreground_; - - public: - AlphaLayer(const RadiologyScene& scene) : - scene_(scene), - useWindowing_(true), - foreground_(0) - { - } - - - void SetForegroundValue(float foreground) - { - useWindowing_ = false; - foreground_ = foreground; - } - - - void SetAlpha(Orthanc::ImageAccessor* image) - { - std::auto_ptr raii(image); - - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - SetSize(image->GetWidth(), image->GetHeight()); - alpha_ = raii; - } - - - void LoadText(const Orthanc::Font& font, - const std::string& utf8) - { - SetAlpha(font.RenderAlpha(utf8)); - } - - - virtual bool GetDefaultWindowing(float& center, - float& width) const - { - return false; - } - - - virtual void Render(Orthanc::ImageAccessor& buffer, - const Matrix& viewTransform, - ImageInterpolation interpolation) const - { - if (alpha_.get() == NULL) - { - return; - } - - if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); - } - - unsigned int cropX, cropY, cropWidth, cropHeight; - GetCrop(cropX, cropY, cropWidth, cropHeight); - - Matrix m = LinearAlgebra::Product(viewTransform, - GetTransform(), - CreateOffsetMatrix(cropX, cropY)); - - Orthanc::ImageAccessor cropped; - alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); - - Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); - ApplyProjectiveTransform(tmp, cropped, m, interpolation, true /* clear */); - - // Blit - const unsigned int width = buffer.GetWidth(); - const unsigned int height = buffer.GetHeight(); - - float value = foreground_; - - if (useWindowing_) - { - float center, width; - if (scene_.GetWindowing(center, width)) - { - value = center + width / 2.0f; - } - } - - for (unsigned int y = 0; y < height; y++) - { - float *q = reinterpret_cast(buffer.GetRow(y)); - const uint8_t *p = reinterpret_cast(tmp.GetRow(y)); + protected: + virtual void UndoInternal(RadiographyScene::Layer& layer) const = 0; - for (unsigned int x = 0; x < width; x++, p++, q++) - { - float a = static_cast(*p) / 255.0f; - - *q = (a * value + (1.0f - a) * (*q)); - } - } - } - - - virtual bool GetRange(float& minValue, - float& maxValue) const - { - if (useWindowing_) - { - return false; - } - else - { - minValue = 0; - maxValue = 0; - - if (foreground_ < 0) - { - minValue = foreground_; - } - - if (foreground_ > 0) - { - maxValue = foreground_; - } - - return true; - } - } - }; - - - - private: - static Matrix CreateOffsetMatrix(double dx, - double dy) - { - Matrix m = LinearAlgebra::IdentityMatrix(3); - m(0, 2) = dx; - m(1, 2) = dy; - return m; - } - - - static Matrix CreateScalingMatrix(double sx, - double sy) - { - Matrix m = LinearAlgebra::IdentityMatrix(3); - m(0, 0) = sx; - m(1, 1) = sy; - return m; - } - - - static Matrix CreateRotationMatrix(double angle) - { - Matrix m; - const double v[] = { cos(angle), -sin(angle), 0, - sin(angle), cos(angle), 0, - 0, 0, 1 }; - LinearAlgebra::FillMatrix(m, 3, 3, v); - return m; - } - - - class DicomLayer : public Layer - { - private: - std::auto_ptr source_; // Content of PixelData - std::auto_ptr converter_; - std::auto_ptr converted_; // Float32 - - static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) - { - return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); - } - - - void ApplyConverter() - { - if (source_.get() != NULL && - converter_.get() != NULL) - { - converted_.reset(converter_->ConvertFrame(*source_)); - } - } - - public: - void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) - { - converter_.reset(new DicomFrameConverter); - converter_->ReadParameters(dataset); - ApplyConverter(); - - std::string tmp; - Vector pixelSpacing; - - if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && - LinearAlgebra::ParseVector(pixelSpacing, tmp) && - pixelSpacing.size() == 2) - { - SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]); - } - - //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY()); - - OrthancPlugins::DicomDatasetReader reader(dataset); - - unsigned int width, height; - if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || - !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS))) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - SetSize(width, height); - } - } - - - void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership - { - std::auto_ptr raii(image); - - if (image == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - SetSize(image->GetWidth(), image->GetHeight()); - - source_ = raii; - ApplyConverter(); - } - - - virtual void Render(Orthanc::ImageAccessor& buffer, - const Matrix& viewTransform, - ImageInterpolation interpolation) const - { - if (converted_.get() != NULL) - { - if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - unsigned int cropX, cropY, cropWidth, cropHeight; - GetCrop(cropX, cropY, cropWidth, cropHeight); - - Matrix m = LinearAlgebra::Product(viewTransform, - GetTransform(), - CreateOffsetMatrix(cropX, cropY)); - - Orthanc::ImageAccessor cropped; - converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); - - ApplyProjectiveTransform(buffer, cropped, m, interpolation, false); - } - } - - - virtual bool GetDefaultWindowing(float& center, - float& width) const - { - if (converter_.get() != NULL && - converter_->HasDefaultWindow()) - { - center = static_cast(converter_->GetDefaultWindowCenter()); - width = static_cast(converter_->GetDefaultWindowWidth()); - return true; - } - else - { - return false; - } - } - - - virtual bool GetRange(float& minValue, - float& maxValue) const - { - if (converted_.get() != NULL) - { - if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_); - return true; - } - else - { - return false; - } - } - }; - - - - - typedef std::map Layers; - - OrthancApiClient& orthanc_; - size_t countLayers_; - bool hasWindowing_; - float windowingCenter_; - float windowingWidth_; - Layers layers_; - - - Layer& RegisterLayer(Layer* layer) - { - if (layer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - std::auto_ptr raii(layer); - - size_t index = countLayers_++; - raii->SetIndex(index); - layers_[index] = raii.release(); - - EmitMessage(GeometryChangedMessage(*this)); - EmitMessage(ContentChangedMessage(*this)); - - return *layer; - } - + virtual void RedoInternal(RadiographyScene::Layer& layer) const = 0; public: - RadiologyScene(MessageBroker& broker, - OrthancApiClient& orthanc) : - IObserver(broker), - IObservable(broker), - orthanc_(orthanc), - countLayers_(0), - hasWindowing_(false), - windowingCenter_(0), // Dummy initialization - windowingWidth_(0) // Dummy initialization - { - } - - - virtual ~RadiologyScene() - { - for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) - { - assert(it->second != NULL); - delete it->second; - } - } - - - bool GetWindowing(float& center, - float& width) const - { - if (hasWindowing_) - { - center = windowingCenter_; - width = windowingWidth_; - return true; - } - else - { - return false; - } - } - - - void GetWindowingWithDefault(float& center, - float& width) const - { - if (!GetWindowing(center, width)) - { - center = 128; - width = 256; - } - } - - - void SetWindowing(float center, - float width) - - { - hasWindowing_ = true; - windowingCenter_ = center; - windowingWidth_ = width; - } - - - Layer& LoadText(const Orthanc::Font& font, - const std::string& utf8) - { - std::auto_ptr alpha(new AlphaLayer(*this)); - alpha->LoadText(font, utf8); - - return RegisterLayer(alpha.release()); - } - - - Layer& LoadTestBlock(unsigned int width, - unsigned int height) - { - std::auto_ptr block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); - - for (unsigned int padding = 0; - (width > 2 * padding) && (height > 2 * padding); - padding++) - { - uint8_t color; - if (255 > 10 * padding) - { - color = 255 - 10 * padding; - } - else - { - color = 0; - } - - Orthanc::ImageAccessor region; - block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); - Orthanc::ImageProcessing::Set(region, color); - } - - std::auto_ptr alpha(new AlphaLayer(*this)); - alpha->SetAlpha(block.release()); - - return RegisterLayer(alpha.release()); - } - - - Layer& LoadDicomFrame(const std::string& instance, - unsigned int frame, - bool httpCompression) - { - Layer& layer = RegisterLayer(new DicomLayer); - - { - IWebService::Headers headers; - std::string uri = "/instances/" + instance + "/tags"; - orthanc_.GetBinaryAsync(uri, headers, - new Callable - (*this, &RadiologyScene::OnTagsReceived), NULL, - new Orthanc::SingleValueObject(layer.GetIndex())); - } - - { - IWebService::Headers headers; - headers["Accept"] = "image/x-portable-arbitrarymap"; - - if (httpCompression) - { - headers["Accept-Encoding"] = "gzip"; - } - - std::string uri = "/instances/" + instance + "/frames/" + boost::lexical_cast(frame) + "/image-uint16"; - orthanc_.GetBinaryAsync(uri, headers, - new Callable - (*this, &RadiologyScene::OnFrameReceived), NULL, - new Orthanc::SingleValueObject(layer.GetIndex())); - } - - return layer; - } - - - void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) - { - size_t index = dynamic_cast&>(message.GetPayload()).GetValue(); - - LOG(INFO) << "JSON received: " << message.GetUri().c_str() - << " (" << message.GetAnswerSize() << " bytes) for layer " << index; - - Layers::iterator layer = layers_.find(index); - if (layer != layers_.end()) - { - assert(layer->second != NULL); - - OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); - dynamic_cast(layer->second)->SetDicomTags(dicom); - - float c, w; - if (!hasWindowing_ && - layer->second->GetDefaultWindowing(c, w)) - { - hasWindowing_ = true; - windowingCenter_ = c; - windowingWidth_ = w; - } - - EmitMessage(GeometryChangedMessage(*this)); - } - } - - - void OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) - { - size_t index = dynamic_cast&>(message.GetPayload()).GetValue(); - - LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() - << " (" << message.GetAnswerSize() << " bytes) for layer " << index; - - Layers::iterator layer = layers_.find(index); - if (layer != layers_.end()) - { - assert(layer->second != NULL); - - std::string content; - if (message.GetAnswerSize() > 0) - { - content.assign(reinterpret_cast(message.GetAnswer()), message.GetAnswerSize()); - } - - std::auto_ptr reader(new Orthanc::PamReader); - reader->ReadFromMemory(content); - dynamic_cast(layer->second)->SetSourceImage(reader.release()); - - EmitMessage(ContentChangedMessage(*this)); - } - } - - - Extent2D GetSceneExtent() const - { - Extent2D extent; - - for (Layers::const_iterator it = layers_.begin(); - it != layers_.end(); ++it) - { - assert(it->second != NULL); - extent.Union(it->second->GetExtent()); - } - - return extent; - } - - - void Render(Orthanc::ImageAccessor& buffer, - const Matrix& viewTransform, - ImageInterpolation interpolation) const - { - Orthanc::ImageProcessing::Set(buffer, 0); - - // Render layers in the background-to-foreground order - for (size_t index = 0; index < countLayers_; index++) - { - Layers::const_iterator it = layers_.find(index); - if (it != layers_.end()) - { - assert(it->second != NULL); - it->second->Render(buffer, viewTransform, interpolation); - } - } - } - - - bool LookupLayer(size_t& index /* out */, - double x, - double y) const - { - // Render layers in the foreground-to-background order - for (size_t i = countLayers_; i > 0; i--) - { - index = i - 1; - Layers::const_iterator it = layers_.find(index); - if (it != layers_.end()) - { - assert(it->second != NULL); - if (it->second->Contains(x, y)) - { - return true; - } - } - } - - return false; - } - - - void DrawBorder(CairoContext& context, - unsigned int layer, - double zoom) - { - Layers::const_iterator found = layers_.find(layer); - - if (found != layers_.end()) - { - context.SetSourceColor(255, 0, 0); - found->second->DrawBorders(context, zoom); - } - } - - - void GetRange(float& minValue, - float& maxValue) const - { - bool first = true; - - for (Layers::const_iterator it = layers_.begin(); - it != layers_.end(); it++) - { - assert(it->second != NULL); - - float a, b; - if (it->second->GetRange(a, b)) - { - if (first) - { - minValue = a; - maxValue = b; - first = false; - } - else - { - minValue = std::min(a, minValue); - maxValue = std::max(b, maxValue); - } - } - } - - if (first) - { - minValue = 0; - maxValue = 0; - } - } - - - // 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, - bool usePam) - { - if (pixelSpacingX <= 0 || - pixelSpacingY <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - LOG(INFO) << "Exporting DICOM"; - - Extent2D extent = GetSceneExtent(); - - int w = std::ceil(extent.GetWidth() / pixelSpacingX); - int h = std::ceil(extent.GetHeight() / pixelSpacingY); - - if (w < 0 || h < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - Orthanc::Image layers(Orthanc::PixelFormat_Float32, - static_cast(w), - static_cast(h), false); - - Matrix view = LinearAlgebra::Product( - CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), - CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1())); - - Render(layers, view, interpolation); - - Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, - layers.GetWidth(), layers.GetHeight(), false); - Orthanc::ImageProcessing::Convert(rendered, layers); - - std::string base64; - - { - std::string content; - - if (usePam) - { - Orthanc::PamWriter writer; - writer.WriteToMemory(content, rendered); - } - else - { - Orthanc::PngWriter writer; - writer.WriteToMemory(content, rendered); - } - - Orthanc::Toolbox::EncodeBase64(base64, content); - } - - std::set tags; - dicom.GetTags(tags); - - Json::Value json = Json::objectValue; - json["Tags"] = Json::objectValue; - - for (std::set::const_iterator - tag = tags.begin(); tag != tags.end(); ++tag) - { - const Orthanc::DicomValue& value = dicom.GetValue(*tag); - if (!value.IsNull() && - !value.IsBinary()) - { - json["Tags"][tag->Format()] = value.GetContent(); - } - } - - json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = - (invert ? "MONOCHROME1" : "MONOCHROME2"); - - // 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); - - json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; - - float center, width; - if (GetWindowing(center, width)) - { - json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = - boost::lexical_cast(boost::math::iround(center)); - - json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = - boost::lexical_cast(boost::math::iround(width)); - } - - // 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 - (*this, &RadiologyScene::OnDicomExported), - NULL, NULL); - } - - - void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) - { - LOG(INFO) << "DICOM export was successful:" - << message.GetJson().toStyledString(); - } - }; - - - class UndoRedoStack : public boost::noncopyable - { - public: - class ICommand : public boost::noncopyable - { - public: - virtual ~ICommand() - { - } - - virtual void Undo() const = 0; - - virtual void Redo() const = 0; - }; - - private: - typedef std::list Stack; - - Stack stack_; - Stack::iterator current_; - - void Clear(Stack::iterator from) - { - for (Stack::iterator it = from; it != stack_.end(); ++it) - { - assert(*it != NULL); - delete *it; - } - - stack_.erase(from, stack_.end()); - } - - public: - UndoRedoStack() : - current_(stack_.end()) - { - } - - ~UndoRedoStack() - { - Clear(stack_.begin()); - } - - void Add(ICommand* command) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - Clear(current_); - - stack_.push_back(command); - current_ = stack_.end(); - } - - void Undo() - { - if (current_ != stack_.begin()) - { - --current_; - - assert(*current_ != NULL); - (*current_)->Undo(); - } - } - - void Redo() - { - if (current_ != stack_.end()) - { - assert(*current_ != NULL); - (*current_)->Redo(); - - ++current_; - } - } - }; - - - class RadiologyLayerCommand : public UndoRedoStack::ICommand - { - private: - RadiologyScene& scene_; - size_t layer_; - - protected: - virtual void UndoInternal(RadiologyScene::Layer& layer) const = 0; - - virtual void RedoInternal(RadiologyScene::Layer& layer) const = 0; - - public: - RadiologyLayerCommand(RadiologyScene& scene, - size_t layer) : + RadiographyLayerCommand(RadiographyScene& scene, + size_t layer) : scene_(scene), layer_(layer) { } - RadiologyLayerCommand(const RadiologyScene::LayerAccessor& accessor) : + RadiographyLayerCommand(const RadiographyScene::LayerAccessor& accessor) : scene_(accessor.GetScene()), layer_(accessor.GetIndex()) { @@ -1530,7 +78,7 @@ virtual void Undo() const { - RadiologyScene::LayerAccessor accessor(scene_, layer_); + RadiographyScene::LayerAccessor accessor(scene_, layer_); if (accessor.IsValid()) { @@ -1540,7 +88,7 @@ virtual void Redo() const { - RadiologyScene::LayerAccessor accessor(scene_, layer_); + RadiographyScene::LayerAccessor accessor(scene_, layer_); if (accessor.IsValid()) { @@ -1550,11 +98,11 @@ }; - class RadiologyLayerRotateTracker : public IWorldSceneMouseTracker + class RadiographyLayerRotateTracker : public IWorldSceneMouseTracker { private: UndoRedoStack& undoRedoStack_; - RadiologyScene::LayerAccessor accessor_; + RadiographyScene::LayerAccessor accessor_; double centerX_; double centerY_; double originalAngle_; @@ -1583,7 +131,7 @@ } - class UndoRedoCommand : public RadiologyLayerCommand + class UndoRedoCommand : public RadiographyLayerCommand { private: double sourceAngle_; @@ -1595,21 +143,21 @@ } protected: - virtual void UndoInternal(RadiologyScene::Layer& layer) const + virtual void UndoInternal(RadiographyScene::Layer& layer) const { LOG(INFO) << "Undo - Set angle to " << ToDegrees(sourceAngle_) << " degrees"; layer.SetAngle(sourceAngle_); } - virtual void RedoInternal(RadiologyScene::Layer& layer) const + virtual void RedoInternal(RadiographyScene::Layer& layer) const { LOG(INFO) << "Redo - Set angle to " << ToDegrees(sourceAngle_) << " degrees"; layer.SetAngle(targetAngle_); } public: - UndoRedoCommand(const RadiologyLayerRotateTracker& tracker) : - RadiologyLayerCommand(tracker.accessor_), + UndoRedoCommand(const RadiographyLayerRotateTracker& tracker) : + RadiographyLayerCommand(tracker.accessor_), sourceAngle_(tracker.originalAngle_), targetAngle_(tracker.accessor_.GetLayer().GetAngle()) { @@ -1618,13 +166,13 @@ public: - RadiologyLayerRotateTracker(UndoRedoStack& undoRedoStack, - RadiologyScene& scene, - const ViewportGeometry& view, - size_t layer, - double x, - double y, - bool roundAngles) : + RadiographyLayerRotateTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + const ViewportGeometry& view, + size_t layer, + double x, + double y, + bool roundAngles) : undoRedoStack_(undoRedoStack), accessor_(scene, layer), roundAngles_(roundAngles) @@ -1688,18 +236,18 @@ }; - class RadiologyLayerMoveTracker : public IWorldSceneMouseTracker + class RadiographyLayerMoveTracker : public IWorldSceneMouseTracker { private: - UndoRedoStack& undoRedoStack_; - RadiologyScene::LayerAccessor accessor_; - double clickX_; - double clickY_; - double panX_; - double panY_; - bool oneAxis_; + UndoRedoStack& undoRedoStack_; + RadiographyScene::LayerAccessor accessor_; + double clickX_; + double clickY_; + double panX_; + double panY_; + bool oneAxis_; - class UndoRedoCommand : public RadiologyLayerCommand + class UndoRedoCommand : public RadiographyLayerCommand { private: double sourceX_; @@ -1708,19 +256,19 @@ double targetY_; protected: - virtual void UndoInternal(RadiologyScene::Layer& layer) const + virtual void UndoInternal(RadiographyScene::Layer& layer) const { layer.SetPan(sourceX_, sourceY_); } - virtual void RedoInternal(RadiologyScene::Layer& layer) const + virtual void RedoInternal(RadiographyScene::Layer& layer) const { layer.SetPan(targetX_, targetY_); } public: - UndoRedoCommand(const RadiologyLayerMoveTracker& tracker) : - RadiologyLayerCommand(tracker.accessor_), + UndoRedoCommand(const RadiographyLayerMoveTracker& tracker) : + RadiographyLayerCommand(tracker.accessor_), sourceX_(tracker.panX_), sourceY_(tracker.panY_), targetX_(tracker.accessor_.GetLayer().GetPanX()), @@ -1731,12 +279,12 @@ public: - RadiologyLayerMoveTracker(UndoRedoStack& undoRedoStack, - RadiologyScene& scene, - size_t layer, - double x, - double y, - bool oneAxis) : + RadiographyLayerMoveTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + size_t layer, + double x, + double y, + bool oneAxis) : undoRedoStack_(undoRedoStack), accessor_(scene, layer), clickX_(x), @@ -1799,18 +347,18 @@ }; - class RadiologyLayerCropTracker : public IWorldSceneMouseTracker + class RadiographyLayerCropTracker : public IWorldSceneMouseTracker { private: - UndoRedoStack& undoRedoStack_; - RadiologyScene::LayerAccessor accessor_; - RadiologyScene::Corner corner_; - unsigned int cropX_; - unsigned int cropY_; - unsigned int cropWidth_; - unsigned int cropHeight_; + UndoRedoStack& undoRedoStack_; + RadiographyScene::LayerAccessor accessor_; + RadiographyScene::Corner corner_; + unsigned int cropX_; + unsigned int cropY_; + unsigned int cropWidth_; + unsigned int cropHeight_; - class UndoRedoCommand : public RadiologyLayerCommand + class UndoRedoCommand : public RadiographyLayerCommand { private: unsigned int sourceCropX_; @@ -1823,19 +371,19 @@ unsigned int targetCropHeight_; protected: - virtual void UndoInternal(RadiologyScene::Layer& layer) const + virtual void UndoInternal(RadiographyScene::Layer& layer) const { layer.SetCrop(sourceCropX_, sourceCropY_, sourceCropWidth_, sourceCropHeight_); } - virtual void RedoInternal(RadiologyScene::Layer& layer) const + virtual void RedoInternal(RadiographyScene::Layer& layer) const { layer.SetCrop(targetCropX_, targetCropY_, targetCropWidth_, targetCropHeight_); } public: - UndoRedoCommand(const RadiologyLayerCropTracker& tracker) : - RadiologyLayerCommand(tracker.accessor_), + UndoRedoCommand(const RadiographyLayerCropTracker& tracker) : + RadiographyLayerCommand(tracker.accessor_), sourceCropX_(tracker.cropX_), sourceCropY_(tracker.cropY_), sourceCropWidth_(tracker.cropWidth_), @@ -1848,13 +396,13 @@ public: - RadiologyLayerCropTracker(UndoRedoStack& undoRedoStack, - RadiologyScene& scene, - const ViewportGeometry& view, - size_t layer, - double x, - double y, - RadiologyScene::Corner corner) : + RadiographyLayerCropTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + const ViewportGeometry& view, + size_t layer, + double x, + double y, + RadiographyScene::Corner corner) : undoRedoStack_(undoRedoStack), accessor_(scene, layer), corner_(corner) @@ -1893,13 +441,13 @@ { unsigned int x, y; - RadiologyScene::Layer& layer = accessor_.GetLayer(); + RadiographyScene::Layer& layer = accessor_.GetLayer(); if (layer.GetPixel(x, y, sceneX, sceneY)) { unsigned int targetX, targetWidth; - if (corner_ == RadiologyScene::Corner_TopLeft || - corner_ == RadiologyScene::Corner_BottomLeft) + if (corner_ == RadiographyScene::Corner_TopLeft || + corner_ == RadiographyScene::Corner_BottomLeft) { targetX = std::min(x, cropX_ + cropWidth_); targetWidth = cropX_ + cropWidth_ - targetX; @@ -1912,8 +460,8 @@ unsigned int targetY, targetHeight; - if (corner_ == RadiologyScene::Corner_TopLeft || - corner_ == RadiologyScene::Corner_TopRight) + if (corner_ == RadiographyScene::Corner_TopLeft || + corner_ == RadiographyScene::Corner_TopRight) { targetY = std::min(y, cropY_ + cropHeight_); targetHeight = cropY_ + cropHeight_ - targetY; @@ -1931,20 +479,20 @@ }; - class RadiologyLayerResizeTracker : public IWorldSceneMouseTracker + class RadiographyLayerResizeTracker : public IWorldSceneMouseTracker { private: - UndoRedoStack& undoRedoStack_; - RadiologyScene::LayerAccessor accessor_; - bool roundScaling_; - double originalSpacingX_; - double originalSpacingY_; - double originalPanX_; - double originalPanY_; - RadiologyScene::Corner oppositeCorner_; - double oppositeX_; - double oppositeY_; - double baseScaling_; + UndoRedoStack& undoRedoStack_; + RadiographyScene::LayerAccessor accessor_; + bool roundScaling_; + double originalSpacingX_; + double originalSpacingY_; + double originalPanX_; + double originalPanY_; + RadiographyScene::Corner oppositeCorner_; + double oppositeX_; + double oppositeY_; + double baseScaling_; static double ComputeDistance(double x1, double y1, @@ -1956,7 +504,7 @@ return sqrt(dx * dx + dy * dy); } - class UndoRedoCommand : public RadiologyLayerCommand + class UndoRedoCommand : public RadiographyLayerCommand { private: double sourceSpacingX_; @@ -1969,21 +517,21 @@ double targetPanY_; protected: - virtual void UndoInternal(RadiologyScene::Layer& layer) const + virtual void UndoInternal(RadiographyScene::Layer& layer) const { layer.SetPixelSpacing(sourceSpacingX_, sourceSpacingY_); layer.SetPan(sourcePanX_, sourcePanY_); } - virtual void RedoInternal(RadiologyScene::Layer& layer) const + virtual void RedoInternal(RadiographyScene::Layer& layer) const { layer.SetPixelSpacing(targetSpacingX_, targetSpacingY_); layer.SetPan(targetPanX_, targetPanY_); } public: - UndoRedoCommand(const RadiologyLayerResizeTracker& tracker) : - RadiologyLayerCommand(tracker.accessor_), + UndoRedoCommand(const RadiographyLayerResizeTracker& tracker) : + RadiographyLayerCommand(tracker.accessor_), sourceSpacingX_(tracker.originalSpacingX_), sourceSpacingY_(tracker.originalSpacingY_), sourcePanX_(tracker.originalPanX_), @@ -1998,13 +546,13 @@ public: - RadiologyLayerResizeTracker(UndoRedoStack& undoRedoStack, - RadiologyScene& scene, - size_t layer, - double x, - double y, - RadiologyScene::Corner corner, - bool roundScaling) : + RadiographyLayerResizeTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + size_t layer, + double x, + double y, + RadiographyScene::Corner corner, + bool roundScaling) : undoRedoStack_(undoRedoStack), accessor_(scene, layer), roundScaling_(roundScaling) @@ -2019,20 +567,20 @@ switch (corner) { - case RadiologyScene::Corner_TopLeft: - oppositeCorner_ = RadiologyScene::Corner_BottomRight; + case RadiographyScene::Corner_TopLeft: + oppositeCorner_ = RadiographyScene::Corner_BottomRight; break; - case RadiologyScene::Corner_TopRight: - oppositeCorner_ = RadiologyScene::Corner_BottomLeft; + case RadiographyScene::Corner_TopRight: + oppositeCorner_ = RadiographyScene::Corner_BottomLeft; break; - case RadiologyScene::Corner_BottomLeft: - oppositeCorner_ = RadiologyScene::Corner_TopRight; + case RadiographyScene::Corner_BottomLeft: + oppositeCorner_ = RadiographyScene::Corner_TopRight; break; - case RadiologyScene::Corner_BottomRight: - oppositeCorner_ = RadiologyScene::Corner_TopLeft; + case RadiographyScene::Corner_BottomRight: + oppositeCorner_ = RadiographyScene::Corner_TopLeft; break; default: @@ -2091,7 +639,7 @@ scaling = boost::math::round((scaling / ROUND_SCALING) * ROUND_SCALING); } - RadiologyScene::Layer& layer = accessor_.GetLayer(); + RadiographyScene::Layer& layer = accessor_.GetLayer(); layer.SetPixelSpacing(scaling * originalSpacingX_, scaling * originalSpacingY_); @@ -2105,7 +653,7 @@ }; - class RadiologyWindowingTracker : public IWorldSceneMouseTracker + class RadiographyWindowingTracker : public IWorldSceneMouseTracker { public: enum Action @@ -2117,17 +665,17 @@ }; private: - UndoRedoStack& undoRedoStack_; - RadiologyScene& scene_; - int clickX_; - int clickY_; - Action leftAction_; - Action rightAction_; - Action upAction_; - Action downAction_; - float strength_; - float sourceCenter_; - float sourceWidth_; + UndoRedoStack& undoRedoStack_; + RadiographyScene& 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, @@ -2189,14 +737,14 @@ class UndoRedoCommand : public UndoRedoStack::ICommand { private: - RadiologyScene& scene_; - float sourceCenter_; - float sourceWidth_; - float targetCenter_; - float targetWidth_; + RadiographyScene& scene_; + float sourceCenter_; + float sourceWidth_; + float targetCenter_; + float targetWidth_; public: - UndoRedoCommand(const RadiologyWindowingTracker& tracker) : + UndoRedoCommand(const RadiographyWindowingTracker& tracker) : scene_(tracker.scene_), sourceCenter_(tracker.sourceCenter_), sourceWidth_(tracker.sourceWidth_) @@ -2217,14 +765,14 @@ public: - RadiologyWindowingTracker(UndoRedoStack& undoRedoStack, - RadiologyScene& scene, - int x, - int y, - Action leftAction, - Action rightAction, - Action upAction, - Action downAction) : + RadiographyWindowingTracker(UndoRedoStack& undoRedoStack, + RadiographyScene& scene, + int x, + int y, + Action leftAction, + Action rightAction, + Action upAction, + Action downAction) : undoRedoStack_(undoRedoStack), scene_(scene), clickX_(x), @@ -2301,12 +849,12 @@ }; - class RadiologyWidget : + class RadiographyWidget : public WorldSceneWidget, public IObserver { private: - RadiologyScene& scene_; + RadiographyScene& scene_; std::auto_ptr floatBuffer_; std::auto_ptr cairoBuffer_; bool invert_; @@ -2428,9 +976,9 @@ } public: - RadiologyWidget(MessageBroker& broker, - RadiologyScene& scene, - const std::string& name) : + RadiographyWidget(MessageBroker& broker, + RadiographyScene& scene, + const std::string& name) : WorldSceneWidget(name), IObserver(broker), scene_(scene), @@ -2440,15 +988,15 @@ selectedLayer_(0) // Dummy initialization { scene.RegisterObserverCallback( - new Callable - (*this, &RadiologyWidget::OnGeometryChanged)); + new Callable + (*this, &RadiographyWidget::OnGeometryChanged)); scene.RegisterObserverCallback( - new Callable - (*this, &RadiologyWidget::OnContentChanged)); + new Callable + (*this, &RadiographyWidget::OnContentChanged)); } - RadiologyScene& GetScene() const + RadiographyScene& GetScene() const { return scene_; } @@ -2477,13 +1025,13 @@ } } - void OnGeometryChanged(const RadiologyScene::GeometryChangedMessage& message) + void OnGeometryChanged(const RadiographyScene::GeometryChangedMessage& message) { LOG(INFO) << "Geometry has changed"; FitContent(); } - void OnContentChanged(const RadiologyScene::ContentChangedMessage& message) + void OnContentChanged(const RadiographyScene::ContentChangedMessage& message) { LOG(INFO) << "Content has changed"; NotifyContentChanged(); @@ -2527,7 +1075,7 @@ namespace Samples { - class RadiologyEditorInteractor : + class RadiographyEditorInteractor : public IWorldSceneInteractor, public IObserver { @@ -2553,7 +1101,7 @@ public: - RadiologyEditorInteractor(MessageBroker& broker) : + RadiographyEditorInteractor(MessageBroker& broker) : IObserver(broker), tool_(Tool_Move) { @@ -2569,7 +1117,7 @@ double y, IStatusBar* statusBar) { - RadiologyWidget& widget = dynamic_cast(worldWidget); + RadiographyWidget& widget = dynamic_cast(worldWidget); if (button == MouseButton_Left) { @@ -2577,12 +1125,13 @@ if (tool_ == Tool_Windowing) { - return new RadiologyWindowingTracker(undoRedoStack_, widget.GetScene(), - viewportX, viewportY, - RadiologyWindowingTracker::Action_DecreaseWidth, - RadiologyWindowingTracker::Action_IncreaseWidth, - RadiologyWindowingTracker::Action_DecreaseCenter, - RadiologyWindowingTracker::Action_IncreaseCenter); + return new RadiographyWindowingTracker( + undoRedoStack_, widget.GetScene(), + viewportX, viewportY, + RadiographyWindowingTracker::Action_DecreaseWidth, + RadiographyWindowingTracker::Action_IncreaseWidth, + RadiographyWindowingTracker::Action_DecreaseCenter, + RadiographyWindowingTracker::Action_IncreaseCenter); } else if (!widget.LookupSelectedLayer(selected)) { @@ -2598,18 +1147,20 @@ else if (tool_ == Tool_Crop || tool_ == Tool_Resize) { - RadiologyScene::LayerAccessor accessor(widget.GetScene(), selected); - RadiologyScene::Corner corner; + RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); + RadiographyScene::Corner corner; if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) { switch (tool_) { case Tool_Crop: - return new RadiologyLayerCropTracker(undoRedoStack_, widget.GetScene(), view, selected, x, y, corner); + return new RadiographyLayerCropTracker + (undoRedoStack_, widget.GetScene(), view, selected, x, y, corner); case Tool_Resize: - return new RadiologyLayerResizeTracker(undoRedoStack_, widget.GetScene(), selected, x, y, corner, - (modifiers & KeyboardModifiers_Shift)); + return new RadiographyLayerResizeTracker + (undoRedoStack_, widget.GetScene(), selected, x, y, corner, + (modifiers & KeyboardModifiers_Shift)); default: throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); @@ -2642,12 +1193,14 @@ switch (tool_) { case Tool_Move: - return new RadiologyLayerMoveTracker(undoRedoStack_, widget.GetScene(), layer, x, y, - (modifiers & KeyboardModifiers_Shift)); + return new RadiographyLayerMoveTracker + (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)); + return new RadiographyLayerRotateTracker + (undoRedoStack_, widget.GetScene(), view, layer, x, y, + (modifiers & KeyboardModifiers_Shift)); default: break; @@ -2681,7 +1234,7 @@ double y, IStatusBar* statusBar) { - RadiologyWidget& widget = dynamic_cast(worldWidget); + RadiographyWidget& widget = dynamic_cast(worldWidget); #if 0 if (statusBar != NULL) @@ -2698,9 +1251,9 @@ (tool_ == Tool_Crop || tool_ == Tool_Resize)) { - RadiologyScene::LayerAccessor accessor(widget.GetScene(), selected); + RadiographyScene::LayerAccessor accessor(widget.GetScene(), selected); - RadiologyScene::Corner corner; + RadiographyScene::Corner corner; if (accessor.GetLayer().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) { accessor.GetLayer().GetCorner(x, y, corner); @@ -2733,7 +1286,7 @@ KeyboardModifiers modifiers, IStatusBar* statusBar) { - RadiologyWidget& widget = dynamic_cast(worldWidget); + RadiographyWidget& widget = dynamic_cast(worldWidget); switch (keyChar) { @@ -2767,8 +1320,8 @@ 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); + widget.GetScene().ExportDicom(tags, 0.1, 0.1, widget.IsInverted(), + widget.GetInterpolation(), EXPORT_USING_PAM); break; } @@ -2843,8 +1396,8 @@ { private: std::auto_ptr orthancApiClient_; - std::auto_ptr scene_; - RadiologyEditorInteractor interactor_; + std::auto_ptr scene_; + RadiographyEditorInteractor interactor_; public: SingleFrameEditorApplication(MessageBroker& broker) : @@ -2907,25 +1460,25 @@ Orthanc::FontRegistry fonts; fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16); - scene_.reset(new RadiologyScene(GetBroker(), *orthancApiClient_)); + scene_.reset(new RadiographyScene(GetBroker(), *orthancApiClient_)); scene_->LoadDicomFrame(instance, frame, false); //.SetPan(200, 0); //scene_->LoadDicomFrame("61f3143e-96f34791-ad6bbb8d-62559e75-45943e1b", 0, false); { - RadiologyScene::Layer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld"); - //dynamic_cast(layer).SetForegroundValue(256); - dynamic_cast(layer).SetResizeable(true); + RadiographyScene::Layer& layer = scene_->LoadText(fonts.GetFont(0), "Hello\nworld"); + //dynamic_cast(layer).SetForegroundValue(256); + dynamic_cast(layer).SetResizeable(true); } { - RadiologyScene::Layer& layer = scene_->LoadTestBlock(100, 50); - //dynamic_cast(layer).SetForegroundValue(256); - dynamic_cast(layer).SetResizeable(true); - dynamic_cast(layer).SetPan(0, 200); + RadiographyScene::Layer& layer = scene_->LoadTestBlock(100, 50); + //dynamic_cast(layer).SetForegroundValue(256); + dynamic_cast(layer).SetResizeable(true); + dynamic_cast(layer).SetPan(0, 200); } - mainWidget_ = new RadiologyWidget(GetBroker(), *scene_, "main-widget"); + mainWidget_ = new RadiographyWidget(GetBroker(), *scene_, "main-widget"); mainWidget_->SetTransmitMouseOver(true); mainWidget_->SetInteractor(interactor_); diff -r 842a3c7cfdc0 -r 6834c236b36d Framework/Radiography/RadiographyScene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyScene.cpp Mon Nov 12 14:52:10 2018 +0100 @@ -0,0 +1,1262 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "RadiographyScene.h" + +#include "../Toolbox/ImageGeometry.h" +#include "../Toolbox/DicomFrameConverter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace OrthancStone +{ + static double Square(double x) + { + return x * x; + } + + + static Matrix CreateOffsetMatrix(double dx, + double dy) + { + Matrix m = LinearAlgebra::IdentityMatrix(3); + m(0, 2) = dx; + m(1, 2) = dy; + return m; + } + + + static Matrix CreateScalingMatrix(double sx, + double sy) + { + Matrix m = LinearAlgebra::IdentityMatrix(3); + m(0, 0) = sx; + m(1, 1) = sy; + return m; + } + + + static Matrix CreateRotationMatrix(double angle) + { + Matrix m; + const double v[] = { cos(angle), -sin(angle), 0, + sin(angle), cos(angle), 0, + 0, 0, 1 }; + LinearAlgebra::FillMatrix(m, 3, 3, v); + return m; + } + + + static void ApplyTransform(double& x /* inout */, + double& y /* inout */, + const Matrix& transform) + { + Vector p; + LinearAlgebra::AssignVector(p, x, y, 1); + + Vector q = LinearAlgebra::Product(transform, p); + + if (!LinearAlgebra::IsNear(q[2], 1.0)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + x = q[0]; + y = q[1]; + } + } + + + + void RadiographyScene::Layer::UpdateTransform() + { + transform_ = CreateScalingMatrix(pixelSpacingX_, pixelSpacingY_); + + double centerX, centerY; + GetCenter(centerX, centerY); + + transform_ = LinearAlgebra::Product( + CreateOffsetMatrix(panX_ + centerX, panY_ + centerY), + CreateRotationMatrix(angle_), + CreateOffsetMatrix(-centerX, -centerY), + transform_); + + LinearAlgebra::InvertMatrix(transformInverse_, transform_); + } + + + void RadiographyScene::Layer::AddToExtent(Extent2D& extent, + double x, + double y) const + { + ApplyTransform(x, y, transform_); + extent.AddPoint(x, y); + } + + + void RadiographyScene::Layer::GetCornerInternal(double& x, + double& y, + Corner corner, + unsigned int cropX, + unsigned int cropY, + unsigned int cropWidth, + unsigned int cropHeight) const + { + double dx = static_cast(cropX); + double dy = static_cast(cropY); + double dwidth = static_cast(cropWidth); + double dheight = static_cast(cropHeight); + + switch (corner) + { + case Corner_TopLeft: + x = dx; + y = dy; + break; + + case Corner_TopRight: + x = dx + dwidth; + y = dy; + break; + + case Corner_BottomLeft: + x = dx; + y = dy + dheight; + break; + + case Corner_BottomRight: + x = dx + dwidth; + y = dy + dheight; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + ApplyTransform(x, y, transform_); + } + + + bool RadiographyScene::Layer::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 RadiographyScene::Layer::DrawBorders(CairoContext& context, + double zoom) + { + unsigned int cx, cy, width, height; + GetCrop(cx, cy, width, height); + + double dx = static_cast(cx); + double dy = static_cast(cy); + double dwidth = static_cast(width); + double dheight = static_cast(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); + } + + + RadiographyScene::Layer::Layer() : + index_(0), + hasSize_(false), + width_(0), + height_(0), + hasCrop_(false), + pixelSpacingX_(1), + pixelSpacingY_(1), + panX_(0), + panY_(0), + angle_(0), + resizeable_(false) + { + UpdateTransform(); + } + + + void RadiographyScene::Layer::SetCrop(unsigned int x, + unsigned int y, + unsigned int width, + unsigned int height) + { + if (!hasSize_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + if (x + width > width_ || + y + height > height_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + hasCrop_ = true; + cropX_ = x; + cropY_ = y; + cropWidth_ = width; + cropHeight_ = height; + + UpdateTransform(); + } + + + void RadiographyScene::Layer::GetCrop(unsigned int& x, + unsigned int& y, + unsigned int& width, + unsigned int& height) const + { + if (hasCrop_) + { + x = cropX_; + y = cropY_; + width = cropWidth_; + height = cropHeight_; + } + else + { + x = 0; + y = 0; + width = width_; + height = height_; + } + } + + + void RadiographyScene::Layer::SetAngle(double angle) + { + angle_ = angle; + UpdateTransform(); + } + + + void RadiographyScene::Layer::SetSize(unsigned int width, + unsigned int height) + { + if (hasSize_ && + (width != width_ || + height != height_)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + hasSize_ = true; + width_ = width; + height_ = height; + + UpdateTransform(); + } + + + Extent2D RadiographyScene::Layer::GetExtent() const + { + Extent2D extent; + + unsigned int x, y, width, height; + GetCrop(x, y, width, height); + + double dx = static_cast(x); + double dy = static_cast(y); + double dwidth = static_cast(width); + double dheight = static_cast(height); + + AddToExtent(extent, dx, dy); + AddToExtent(extent, dx + dwidth, dy); + AddToExtent(extent, dx, dy + dheight); + AddToExtent(extent, dx + dwidth, dy + dheight); + + return extent; + } + + + bool RadiographyScene::Layer::GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const + { + if (width_ == 0 || + height_ == 0) + { + return false; + } + else + { + ApplyTransform(sceneX, sceneY, transformInverse_); + + int x = static_cast(std::floor(sceneX)); + int y = static_cast(std::floor(sceneY)); + + if (x < 0) + { + imageX = 0; + } + else if (x >= static_cast(width_)) + { + imageX = width_; + } + else + { + imageX = static_cast(x); + } + + if (y < 0) + { + imageY = 0; + } + else if (y >= static_cast(height_)) + { + imageY = height_; + } + else + { + imageY = static_cast(y); + } + + return true; + } + } + + + void RadiographyScene::Layer::SetPan(double x, + double y) + { + panX_ = x; + panY_ = y; + UpdateTransform(); + } + + + void RadiographyScene::Layer::SetPixelSpacing(double x, + double y) + { + pixelSpacingX_ = x; + pixelSpacingY_ = y; + UpdateTransform(); + } + + + void RadiographyScene::Layer::GetCenter(double& centerX, + double& centerY) const + { + centerX = static_cast(width_) / 2.0; + centerY = static_cast(height_) / 2.0; + ApplyTransform(centerX, centerY, transform_); + } + + + void RadiographyScene::Layer::GetCorner(double& x /* out */, + double& y /* out */, + Corner corner) const + { + unsigned int cropX, cropY, cropWidth, cropHeight; + GetCrop(cropX, cropY, cropWidth, cropHeight); + GetCornerInternal(x, y, corner, cropX, cropY, cropWidth, cropHeight); + } + + + bool RadiographyScene::Layer::LookupCorner(Corner& corner /* out */, + double x, + double y, + double zoom, + double viewportDistance) const + { + static const Corner CORNERS[] = { + Corner_TopLeft, + Corner_TopRight, + Corner_BottomLeft, + Corner_BottomRight + }; + + unsigned int cropX, cropY, cropWidth, cropHeight; + GetCrop(cropX, cropY, cropWidth, cropHeight); + + double threshold = Square(viewportDistance / zoom); + + for (size_t i = 0; i < 4; i++) + { + double cx, cy; + GetCornerInternal(cx, cy, CORNERS[i], cropX, cropY, cropWidth, cropHeight); + + double d = Square(cx - x) + Square(cy - y); + + if (d <= threshold) + { + corner = CORNERS[i]; + return true; + } + } + + return false; + } + + + + RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, + size_t index) : + scene_(scene), + index_(index) + { + Layers::iterator layer = scene.layers_.find(index); + if (layer == scene.layers_.end()) + { + layer_ = NULL; + } + else + { + assert(layer->second != NULL); + layer_ = layer->second; + } + } + + + RadiographyScene::LayerAccessor::LayerAccessor(RadiographyScene& scene, + double x, + double y) : + scene_(scene), + index_(0) // Dummy initialization + { + if (scene.LookupLayer(index_, x, y)) + { + Layers::iterator layer = scene.layers_.find(index_); + + if (layer == scene.layers_.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + assert(layer->second != NULL); + layer_ = layer->second; + } + } + else + { + layer_ = NULL; + } + } + + + RadiographyScene& RadiographyScene::LayerAccessor::GetScene() const + { + if (IsValid()) + { + return scene_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + size_t RadiographyScene::LayerAccessor::GetIndex() const + { + if (IsValid()) + { + return index_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + RadiographyScene::Layer& RadiographyScene::LayerAccessor::GetLayer() const + { + if (IsValid()) + { + return *layer_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + + class RadiographyScene::AlphaLayer : public Layer + { + private: + const RadiographyScene& scene_; + std::auto_ptr alpha_; // Grayscale8 + bool useWindowing_; + float foreground_; + + public: + AlphaLayer(const RadiographyScene& scene) : + scene_(scene), + useWindowing_(true), + foreground_(0) + { + } + + + void SetForegroundValue(float foreground) + { + useWindowing_ = false; + foreground_ = foreground; + } + + + void SetAlpha(Orthanc::ImageAccessor* image) + { + std::auto_ptr raii(image); + + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (image->GetFormat() != Orthanc::PixelFormat_Grayscale8) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + SetSize(image->GetWidth(), image->GetHeight()); + alpha_ = raii; + } + + + void LoadText(const Orthanc::Font& font, + const std::string& utf8) + { + SetAlpha(font.RenderAlpha(utf8)); + } + + + virtual bool GetDefaultWindowing(float& center, + float& width) const + { + return false; + } + + + virtual void Render(Orthanc::ImageAccessor& buffer, + const Matrix& viewTransform, + ImageInterpolation interpolation) const + { + if (alpha_.get() == NULL) + { + return; + } + + if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + unsigned int cropX, cropY, cropWidth, cropHeight; + GetCrop(cropX, cropY, cropWidth, cropHeight); + + Matrix m = LinearAlgebra::Product(viewTransform, + GetTransform(), + CreateOffsetMatrix(cropX, cropY)); + + Orthanc::ImageAccessor cropped; + alpha_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); + + Orthanc::Image tmp(Orthanc::PixelFormat_Grayscale8, buffer.GetWidth(), buffer.GetHeight(), false); + ApplyProjectiveTransform(tmp, cropped, m, interpolation, true /* clear */); + + // Blit + const unsigned int width = buffer.GetWidth(); + const unsigned int height = buffer.GetHeight(); + + float value = foreground_; + + if (useWindowing_) + { + float center, width; + if (scene_.GetWindowing(center, width)) + { + value = center + width / 2.0f; + } + } + + for (unsigned int y = 0; y < height; y++) + { + float *q = reinterpret_cast(buffer.GetRow(y)); + const uint8_t *p = reinterpret_cast(tmp.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++, q++) + { + float a = static_cast(*p) / 255.0f; + + *q = (a * value + (1.0f - a) * (*q)); + } + } + } + + + virtual bool GetRange(float& minValue, + float& maxValue) const + { + if (useWindowing_) + { + return false; + } + else + { + minValue = 0; + maxValue = 0; + + if (foreground_ < 0) + { + minValue = foreground_; + } + + if (foreground_ > 0) + { + maxValue = foreground_; + } + + return true; + } + } + }; + + + + class RadiographyScene::DicomLayer : public Layer + { + private: + std::auto_ptr source_; // Content of PixelData + std::auto_ptr converter_; + std::auto_ptr converted_; // Float32 + + static OrthancPlugins::DicomTag ConvertTag(const Orthanc::DicomTag& tag) + { + return OrthancPlugins::DicomTag(tag.GetGroup(), tag.GetElement()); + } + + + void ApplyConverter() + { + if (source_.get() != NULL && + converter_.get() != NULL) + { + converted_.reset(converter_->ConvertFrame(*source_)); + } + } + + public: + void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) + { + converter_.reset(new DicomFrameConverter); + converter_->ReadParameters(dataset); + ApplyConverter(); + + std::string tmp; + Vector pixelSpacing; + + if (dataset.GetStringValue(tmp, ConvertTag(Orthanc::DICOM_TAG_PIXEL_SPACING)) && + LinearAlgebra::ParseVector(pixelSpacing, tmp) && + pixelSpacing.size() == 2) + { + SetPixelSpacing(pixelSpacing[0], pixelSpacing[1]); + } + + //SetPan(-0.5 * GetPixelSpacingX(), -0.5 * GetPixelSpacingY()); + + OrthancPlugins::DicomDatasetReader reader(dataset); + + unsigned int width, height; + if (!reader.GetUnsignedIntegerValue(width, ConvertTag(Orthanc::DICOM_TAG_COLUMNS)) || + !reader.GetUnsignedIntegerValue(height, ConvertTag(Orthanc::DICOM_TAG_ROWS))) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + SetSize(width, height); + } + } + + + void SetSourceImage(Orthanc::ImageAccessor* image) // Takes ownership + { + std::auto_ptr raii(image); + + if (image == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + SetSize(image->GetWidth(), image->GetHeight()); + + source_ = raii; + ApplyConverter(); + } + + + virtual void Render(Orthanc::ImageAccessor& buffer, + const Matrix& viewTransform, + ImageInterpolation interpolation) const + { + if (converted_.get() != NULL) + { + if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + unsigned int cropX, cropY, cropWidth, cropHeight; + GetCrop(cropX, cropY, cropWidth, cropHeight); + + Matrix m = LinearAlgebra::Product(viewTransform, + GetTransform(), + CreateOffsetMatrix(cropX, cropY)); + + Orthanc::ImageAccessor cropped; + converted_->GetRegion(cropped, cropX, cropY, cropWidth, cropHeight); + + ApplyProjectiveTransform(buffer, cropped, m, interpolation, false); + } + } + + + virtual bool GetDefaultWindowing(float& center, + float& width) const + { + if (converter_.get() != NULL && + converter_->HasDefaultWindow()) + { + center = static_cast(converter_->GetDefaultWindowCenter()); + width = static_cast(converter_->GetDefaultWindowWidth()); + return true; + } + else + { + return false; + } + } + + + virtual bool GetRange(float& minValue, + float& maxValue) const + { + if (converted_.get() != NULL) + { + if (converted_->GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, *converted_); + return true; + } + else + { + return false; + } + } + }; + + + RadiographyScene::Layer& RadiographyScene::RegisterLayer(RadiographyScene::Layer* layer) + { + if (layer == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + std::auto_ptr raii(layer); + + size_t index = countLayers_++; + raii->SetIndex(index); + layers_[index] = raii.release(); + + EmitMessage(GeometryChangedMessage(*this)); + EmitMessage(ContentChangedMessage(*this)); + + return *layer; + } + + + RadiographyScene::RadiographyScene(MessageBroker& broker, + OrthancApiClient& orthanc) : + IObserver(broker), + IObservable(broker), + orthanc_(orthanc), + countLayers_(0), + hasWindowing_(false), + windowingCenter_(0), // Dummy initialization + windowingWidth_(0) // Dummy initialization + { + } + + + RadiographyScene::~RadiographyScene() + { + for (Layers::iterator it = layers_.begin(); it != layers_.end(); it++) + { + assert(it->second != NULL); + delete it->second; + } + } + + + bool RadiographyScene::GetWindowing(float& center, + float& width) const + { + if (hasWindowing_) + { + center = windowingCenter_; + width = windowingWidth_; + return true; + } + else + { + return false; + } + } + + + void RadiographyScene::GetWindowingWithDefault(float& center, + float& width) const + { + if (!GetWindowing(center, width)) + { + center = 128; + width = 256; + } + } + + + void RadiographyScene::SetWindowing(float center, + float width) + { + hasWindowing_ = true; + windowingCenter_ = center; + windowingWidth_ = width; + } + + + RadiographyScene::Layer& RadiographyScene::LoadText(const Orthanc::Font& font, + const std::string& utf8) + { + std::auto_ptr alpha(new AlphaLayer(*this)); + alpha->LoadText(font, utf8); + + return RegisterLayer(alpha.release()); + } + + + RadiographyScene::Layer& RadiographyScene::LoadTestBlock(unsigned int width, + unsigned int height) + { + std::auto_ptr block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); + + for (unsigned int padding = 0; + (width > 2 * padding) && (height > 2 * padding); + padding++) + { + uint8_t color; + if (255 > 10 * padding) + { + color = 255 - 10 * padding; + } + else + { + color = 0; + } + + Orthanc::ImageAccessor region; + block->GetRegion(region, padding, padding, width - 2 * padding, height - 2 * padding); + Orthanc::ImageProcessing::Set(region, color); + } + + std::auto_ptr alpha(new AlphaLayer(*this)); + alpha->SetAlpha(block.release()); + + return RegisterLayer(alpha.release()); + } + + + RadiographyScene::Layer& RadiographyScene::LoadDicomFrame(const std::string& instance, + unsigned int frame, + bool httpCompression) + { + Layer& layer = RegisterLayer(new DicomLayer); + + { + IWebService::Headers headers; + std::string uri = "/instances/" + instance + "/tags"; + + orthanc_.GetBinaryAsync( + uri, headers, + new Callable + (*this, &RadiographyScene::OnTagsReceived), NULL, + new Orthanc::SingleValueObject(layer.GetIndex())); + } + + { + IWebService::Headers headers; + headers["Accept"] = "image/x-portable-arbitrarymap"; + + if (httpCompression) + { + headers["Accept-Encoding"] = "gzip"; + } + + std::string uri = ("/instances/" + instance + "/frames/" + + boost::lexical_cast(frame) + "/image-uint16"); + + orthanc_.GetBinaryAsync( + uri, headers, + new Callable + (*this, &RadiographyScene::OnFrameReceived), NULL, + new Orthanc::SingleValueObject(layer.GetIndex())); + } + + return layer; + } + + + void RadiographyScene::OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + size_t index = dynamic_cast&> + (message.GetPayload()).GetValue(); + + LOG(INFO) << "JSON received: " << message.GetUri().c_str() + << " (" << message.GetAnswerSize() << " bytes) for layer " << index; + + Layers::iterator layer = layers_.find(index); + if (layer != layers_.end()) + { + assert(layer->second != NULL); + + OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer(), message.GetAnswerSize()); + dynamic_cast(layer->second)->SetDicomTags(dicom); + + float c, w; + if (!hasWindowing_ && + layer->second->GetDefaultWindowing(c, w)) + { + hasWindowing_ = true; + windowingCenter_ = c; + windowingWidth_ = w; + } + + EmitMessage(GeometryChangedMessage(*this)); + } + } + + + void RadiographyScene::OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message) + { + size_t index = dynamic_cast&>(message.GetPayload()).GetValue(); + + LOG(INFO) << "DICOM frame received: " << message.GetUri().c_str() + << " (" << message.GetAnswerSize() << " bytes) for layer " << index; + + Layers::iterator layer = layers_.find(index); + if (layer != layers_.end()) + { + assert(layer->second != NULL); + + std::string content; + if (message.GetAnswerSize() > 0) + { + content.assign(reinterpret_cast(message.GetAnswer()), message.GetAnswerSize()); + } + + std::auto_ptr reader(new Orthanc::PamReader); + reader->ReadFromMemory(content); + dynamic_cast(layer->second)->SetSourceImage(reader.release()); + + EmitMessage(ContentChangedMessage(*this)); + } + } + + + Extent2D RadiographyScene::GetSceneExtent() const + { + Extent2D extent; + + for (Layers::const_iterator it = layers_.begin(); + it != layers_.end(); ++it) + { + assert(it->second != NULL); + extent.Union(it->second->GetExtent()); + } + + return extent; + } + + + void RadiographyScene::Render(Orthanc::ImageAccessor& buffer, + const Matrix& viewTransform, + ImageInterpolation interpolation) const + { + Orthanc::ImageProcessing::Set(buffer, 0); + + // Render layers in the background-to-foreground order + for (size_t index = 0; index < countLayers_; index++) + { + Layers::const_iterator it = layers_.find(index); + if (it != layers_.end()) + { + assert(it->second != NULL); + it->second->Render(buffer, viewTransform, interpolation); + } + } + } + + + bool RadiographyScene::LookupLayer(size_t& index /* out */, + double x, + double y) const + { + // Render layers in the foreground-to-background order + for (size_t i = countLayers_; i > 0; i--) + { + index = i - 1; + Layers::const_iterator it = layers_.find(index); + if (it != layers_.end()) + { + assert(it->second != NULL); + if (it->second->Contains(x, y)) + { + return true; + } + } + } + + return false; + } + + + void RadiographyScene::DrawBorder(CairoContext& context, + unsigned int layer, + double zoom) + { + Layers::const_iterator found = layers_.find(layer); + + if (found != layers_.end()) + { + context.SetSourceColor(255, 0, 0); + found->second->DrawBorders(context, zoom); + } + } + + + void RadiographyScene::GetRange(float& minValue, + float& maxValue) const + { + bool first = true; + + for (Layers::const_iterator it = layers_.begin(); + it != layers_.end(); it++) + { + assert(it->second != NULL); + + float a, b; + if (it->second->GetRange(a, b)) + { + if (first) + { + minValue = a; + maxValue = b; + first = false; + } + else + { + minValue = std::min(a, minValue); + maxValue = std::max(b, maxValue); + } + } + } + + if (first) + { + minValue = 0; + maxValue = 0; + } + } + + + // Export using PAM is faster than using PNG, but requires Orthanc + // core >= 1.4.3 + void RadiographyScene::ExportDicom(const Orthanc::DicomMap& dicom, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam) + { + if (pixelSpacingX <= 0 || + pixelSpacingY <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + LOG(INFO) << "Exporting DICOM"; + + Extent2D extent = GetSceneExtent(); + + int w = std::ceil(extent.GetWidth() / pixelSpacingX); + int h = std::ceil(extent.GetHeight() / pixelSpacingY); + + if (w < 0 || h < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + Orthanc::Image layers(Orthanc::PixelFormat_Float32, + static_cast(w), + static_cast(h), false); + + Matrix view = LinearAlgebra::Product( + CreateScalingMatrix(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), + CreateOffsetMatrix(-extent.GetX1(), -extent.GetY1())); + + Render(layers, view, interpolation); + + Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, + layers.GetWidth(), layers.GetHeight(), false); + Orthanc::ImageProcessing::Convert(rendered, layers); + + std::string base64; + + { + std::string content; + + if (usePam) + { + Orthanc::PamWriter writer; + writer.WriteToMemory(content, rendered); + } + else + { + Orthanc::PngWriter writer; + writer.WriteToMemory(content, rendered); + } + + Orthanc::Toolbox::EncodeBase64(base64, content); + } + + std::set tags; + dicom.GetTags(tags); + + Json::Value json = Json::objectValue; + json["Tags"] = Json::objectValue; + + for (std::set::const_iterator + tag = tags.begin(); tag != tags.end(); ++tag) + { + const Orthanc::DicomValue& value = dicom.GetValue(*tag); + if (!value.IsNull() && + !value.IsBinary()) + { + json["Tags"][tag->Format()] = value.GetContent(); + } + } + + json["Tags"][Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION.Format()] = + (invert ? "MONOCHROME1" : "MONOCHROME2"); + + // 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); + + json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; + + float center, width; + if (GetWindowing(center, width)) + { + json["Tags"][Orthanc::DICOM_TAG_WINDOW_CENTER.Format()] = + boost::lexical_cast(boost::math::iround(center)); + + json["Tags"][Orthanc::DICOM_TAG_WINDOW_WIDTH.Format()] = + boost::lexical_cast(boost::math::iround(width)); + } + + // 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 + (*this, &RadiographyScene::OnDicomExported), + NULL, NULL); + } + + + void RadiographyScene::OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) + { + LOG(INFO) << "DICOM export was successful:" + << message.GetJson().toStyledString(); + } +} diff -r 842a3c7cfdc0 -r 6834c236b36d Framework/Radiography/RadiographyScene.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Radiography/RadiographyScene.h Mon Nov 12 14:52:10 2018 +0100 @@ -0,0 +1,325 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "../Toolbox/Extent2D.h" +#include "../Toolbox/LinearAlgebra.h" +#include "../Toolbox/OrthancApiClient.h" +#include "../Viewport/CairoContext.h" + + +namespace OrthancStone +{ + class RadiographyScene : + public IObserver, + public IObservable + { + public: + typedef OriginMessage GeometryChangedMessage; + typedef OriginMessage ContentChangedMessage; + + enum Corner + { + Corner_TopLeft, + Corner_TopRight, + Corner_BottomLeft, + Corner_BottomRight + }; + + class Layer : public boost::noncopyable + { + friend class RadiographyScene; + + private: + size_t index_; + bool hasSize_; + unsigned int width_; + unsigned int height_; + bool hasCrop_; + unsigned int cropX_; + unsigned int cropY_; + unsigned int cropWidth_; + unsigned int cropHeight_; + Matrix transform_; + Matrix transformInverse_; + double pixelSpacingX_; + double pixelSpacingY_; + double panX_; + double panY_; + double angle_; + bool resizeable_; + + + protected: + const Matrix& GetTransform() const + { + return transform_; + } + + + private: + void UpdateTransform(); + + void AddToExtent(Extent2D& extent, + double x, + double y) const; + + void GetCornerInternal(double& x, + double& y, + Corner corner, + unsigned int cropX, + unsigned int cropY, + unsigned int cropWidth, + unsigned int cropHeight) const; + + void SetIndex(size_t index) + { + index_ = index; + } + + bool Contains(double x, + double y) const; + + void DrawBorders(CairoContext& context, + double zoom); + + public: + Layer(); + + virtual ~Layer() + { + } + + size_t GetIndex() const + { + return index_; + } + + void ResetCrop() + { + hasCrop_ = false; + } + + void SetCrop(unsigned int x, + unsigned int y, + unsigned int width, + unsigned int height); + + void GetCrop(unsigned int& x, + unsigned int& y, + unsigned int& width, + unsigned int& height) const; + + void SetAngle(double angle); + + double GetAngle() const + { + return angle_; + } + + void SetSize(unsigned int width, + unsigned int height); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + Extent2D GetExtent() const; + + bool GetPixel(unsigned int& imageX, + unsigned int& imageY, + double sceneX, + double sceneY) const; + + void SetPan(double x, + double y); + + void SetPixelSpacing(double x, + double y); + + double GetPixelSpacingX() const + { + return pixelSpacingX_; + } + + double GetPixelSpacingY() const + { + return pixelSpacingY_; + } + + double GetPanX() const + { + return panX_; + } + + double GetPanY() const + { + return panY_; + } + + void GetCenter(double& centerX, + double& centerY) const; + + void GetCorner(double& x /* out */, + double& y /* out */, + Corner corner) const; + + bool LookupCorner(Corner& corner /* out */, + double x, + double y, + double zoom, + double viewportDistance) const; + + bool IsResizeable() const + { + return resizeable_; + } + + void SetResizeable(bool resizeable) + { + resizeable_ = resizeable; + } + + virtual bool GetDefaultWindowing(float& center, + float& width) const = 0; + + virtual void Render(Orthanc::ImageAccessor& buffer, + const Matrix& viewTransform, + ImageInterpolation interpolation) const = 0; + + virtual bool GetRange(float& minValue, + float& maxValue) const = 0; + }; + + + class LayerAccessor : public boost::noncopyable + { + private: + RadiographyScene& scene_; + size_t index_; + Layer* layer_; + + public: + LayerAccessor(RadiographyScene& scene, + size_t index); + + LayerAccessor(RadiographyScene& scene, + double x, + double y); + + void Invalidate() + { + layer_ = NULL; + } + + bool IsValid() const + { + return layer_ != NULL; + } + + RadiographyScene& GetScene() const; + + size_t GetIndex() const; + + Layer& GetLayer() const; + }; + + + private: + class AlphaLayer; + class DicomLayer; + + typedef std::map Layers; + + OrthancApiClient& orthanc_; + size_t countLayers_; + bool hasWindowing_; + float windowingCenter_; + float windowingWidth_; + Layers layers_; + + Layer& RegisterLayer(Layer* layer); + + void OnTagsReceived(const OrthancApiClient::BinaryResponseReadyMessage& message); + + void OnFrameReceived(const OrthancApiClient::BinaryResponseReadyMessage& message); + + void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message); + + public: + RadiographyScene(MessageBroker& broker, + OrthancApiClient& orthanc); + + virtual ~RadiographyScene(); + + bool GetWindowing(float& center, + float& width) const; + + void GetWindowingWithDefault(float& center, + float& width) const; + + void SetWindowing(float center, + float width); + + Layer& LoadText(const Orthanc::Font& font, + const std::string& utf8); + + Layer& LoadTestBlock(unsigned int width, + unsigned int height); + + Layer& LoadDicomFrame(const std::string& instance, + unsigned int frame, + bool httpCompression); + + Extent2D GetSceneExtent() const; + + void Render(Orthanc::ImageAccessor& buffer, + const Matrix& viewTransform, + ImageInterpolation interpolation) const; + + bool LookupLayer(size_t& index /* out */, + double x, + double y) const; + + void DrawBorder(CairoContext& context, + unsigned int layer, + double zoom); + + void GetRange(float& minValue, + float& maxValue) const; + + // Export using PAM is faster than using PNG, but requires Orthanc + // core >= 1.4.3 + void ExportDicom(const Orthanc::DicomMap& dicom, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation, + bool usePam); + }; +} diff -r 842a3c7cfdc0 -r 6834c236b36d Framework/Toolbox/UndoRedoStack.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/UndoRedoStack.cpp Mon Nov 12 14:52:10 2018 +0100 @@ -0,0 +1,89 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "UndoRedoStack.h" + +#include + +#include + +namespace OrthancStone +{ + void UndoRedoStack::Clear(UndoRedoStack::Stack::iterator from) + { + for (Stack::iterator it = from; it != stack_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + + stack_.erase(from, stack_.end()); + } + + + UndoRedoStack::UndoRedoStack() : + current_(stack_.end()) + { + } + + + UndoRedoStack::~UndoRedoStack() + { + Clear(stack_.begin()); + } + + + void UndoRedoStack::Add(ICommand* command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + Clear(current_); + + stack_.push_back(command); + current_ = stack_.end(); + } + + + void UndoRedoStack::Undo() + { + if (current_ != stack_.begin()) + { + --current_; + + assert(*current_ != NULL); + (*current_)->Undo(); + } + } + + void UndoRedoStack::Redo() + { + if (current_ != stack_.end()) + { + assert(*current_ != NULL); + (*current_)->Redo(); + + ++current_; + } + } +} diff -r 842a3c7cfdc0 -r 6834c236b36d Framework/Toolbox/UndoRedoStack.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/UndoRedoStack.h Mon Nov 12 14:52:10 2018 +0100 @@ -0,0 +1,64 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + + +namespace OrthancStone +{ + class UndoRedoStack : public boost::noncopyable + { + public: + class ICommand : public boost::noncopyable + { + public: + virtual ~ICommand() + { + } + + virtual void Undo() const = 0; + + virtual void Redo() const = 0; + }; + + private: + typedef std::list Stack; + + Stack stack_; + Stack::iterator current_; + + void Clear(Stack::iterator from); + + public: + UndoRedoStack(); + + ~UndoRedoStack(); + + void Add(ICommand* command); + + void Undo(); + + void Redo(); + }; +} diff -r 842a3c7cfdc0 -r 6834c236b36d Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Mon Nov 12 11:44:20 2018 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Mon Nov 12 14:52:10 2018 +0100 @@ -246,9 +246,13 @@ ${ORTHANC_STONE_ROOT}/Framework/Layers/LineMeasureTracker.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/RenderStyle.cpp ${ORTHANC_STONE_ROOT}/Framework/Layers/SliceOutlineRenderer.cpp + ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.cpp + ${ORTHANC_STONE_ROOT}/Framework/Radiography/RadiographyScene.h ${ORTHANC_STONE_ROOT}/Framework/SmartLoader.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp ${ORTHANC_STONE_ROOT}/Framework/StoneException.h + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.h ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomFrameConverter.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp