# HG changeset patch # User Alain Mazy # Date 1579621957 -3600 # Node ID 69177b10e2b9c5ccf9ed069fac8b683fc9120b69 # Parent 9c20ae049669d6fe4fe6f8f37f6542cc8de47b8b various fixes for RadiographyScene: support text layers outside the dicom layer, fix background in this case + extract dicom from rendered scene diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographyLayer.cpp --- a/Framework/Radiography/RadiographyLayer.cpp Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographyLayer.cpp Tue Jan 21 16:52:37 2020 +0100 @@ -59,15 +59,15 @@ void RadiographyLayer::UpdateTransform() { + // important to update transform_ before getting the center to use the right scaling !!! transform_ = AffineTransform2D::CreateScaling(geometry_.GetScalingX(), geometry_.GetScalingY()); double centerX, centerY; GetCenter(centerX, centerY); transform_ = AffineTransform2D::Combine( - AffineTransform2D::CreateOffset(geometry_.GetPanX() + centerX, geometry_.GetPanY() + centerY), - AffineTransform2D::CreateRotation(geometry_.GetAngle()), - AffineTransform2D::CreateOffset(-centerX, -centerY), + AffineTransform2D::CreateOffset(geometry_.GetPanX(), geometry_.GetPanY()), + AffineTransform2D::CreateRotation(geometry_.GetAngle(), centerX, centerY), transform_); transformInverse_ = AffineTransform2D::Invert(transform_); diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographyLayer.h --- a/Framework/Radiography/RadiographyLayer.h Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographyLayer.h Tue Jan 21 16:52:37 2020 +0100 @@ -217,16 +217,6 @@ const RadiographyScene& scene_; protected: - virtual const AffineTransform2D& GetTransform() const - { - return transform_; - } - - virtual const AffineTransform2D& GetTransformInverse() const - { - return transformInverse_; - } - void SetPreferredPhotomotricDisplayMode(RadiographyPhotometricDisplayMode prefferedPhotometricDisplayMode); private: @@ -254,6 +244,16 @@ { } + virtual const AffineTransform2D& GetTransform() const + { + return transform_; + } + + virtual const AffineTransform2D& GetTransformInverse() const + { + return transformInverse_; + } + size_t GetIndex() const { return index_; @@ -358,8 +358,6 @@ virtual bool GetRange(float& minValue, float& maxValue) const = 0; - friend class RadiographyMaskLayer; // because it needs to GetTransform on the dicomLayer it relates to - virtual size_t GetApproximateMemoryUsage() const // this is used to limit the number of scenes loaded in RAM when resources are limited (we actually only count the size used by the images, not the C structs) { return 0; diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographyMaskLayer.cpp --- a/Framework/Radiography/RadiographyMaskLayer.cpp Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographyMaskLayer.cpp Tue Jan 21 16:52:37 2020 +0100 @@ -82,7 +82,7 @@ float windowWidth, bool applyWindowing) const { - if (dicomLayer_.GetWidth() == 0) // nothing to do if the DICOM layer is not displayed (or not loaded) + if (dicomLayer_.GetWidth() == 0 || dicomLayer_.GetSourceImage() == NULL) // nothing to do if the DICOM layer is not displayed (or not loaded) return; if (invalidated_) diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographyScene.cpp --- a/Framework/Radiography/RadiographyScene.cpp Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographyScene.cpp Tue Jan 21 16:52:37 2020 +0100 @@ -658,6 +658,28 @@ } } + void RadiographyScene::ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer, + const Orthanc::ImageAccessor& renderedScene, + size_t layerIndex, + ImageInterpolation interpolation) + { + Extent2D sceneExtent = GetSceneExtent(); + + double pixelSpacingX = sceneExtent.GetWidth() / renderedScene.GetWidth(); + double pixelSpacingY = sceneExtent.GetHeight() / renderedScene.GetHeight(); + + AffineTransform2D view = AffineTransform2D::Combine( + AffineTransform2D::CreateScaling(1.0 / pixelSpacingX, 1.0 / pixelSpacingY), + AffineTransform2D::CreateOffset(-sceneExtent.GetX1(), -sceneExtent.GetY1())); + + AffineTransform2D layerToSceneTransform = AffineTransform2D::Combine( + view, + GetLayer(layerIndex).GetTransform()); + + AffineTransform2D sceneToLayerTransform = AffineTransform2D::Invert(layerToSceneTransform); + sceneToLayerTransform.Apply(layer, renderedScene, interpolation, false); + } + Orthanc::Image* RadiographyScene::ExportToImage(double pixelSpacingX, double pixelSpacingY, ImageInterpolation interpolation, @@ -690,7 +712,14 @@ AffineTransform2D::CreateOffset(-extent.GetX1(), -extent.GetY1())); // wipe background before rendering - Orthanc::ImageProcessing::Set(layers, 0); + if (GetPreferredPhotomotricDisplayMode() == RadiographyPhotometricDisplayMode_Monochrome1) + { + Orthanc::ImageProcessing::Set(layers, 65535.0f); + } + else + { + Orthanc::ImageProcessing::Set(layers, 0); + } Render(layers, view, interpolation, applyWindowing); diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographyScene.h --- a/Framework/Radiography/RadiographyScene.h Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographyScene.h Tue Jan 21 16:52:37 2020 +0100 @@ -36,6 +36,7 @@ public IObserver, public IObservable { + friend class RadiographySceneGeometryReader; public: class GeometryChangedMessage : public OriginMessage { @@ -167,9 +168,10 @@ float windowingWidth_; Layers layers_; - protected: + public: RadiographyLayer& RegisterLayer(RadiographyLayer* layer); + protected: virtual void _RegisterLayer(RadiographyLayer* layer); void SetLayerIndex(RadiographyLayer* layer, size_t index) @@ -347,5 +349,9 @@ int64_t maxValue /* for inversion */, bool applyWindowing); + void ExtractLayerFromRenderedScene(Orthanc::ImageAccessor& layer, + const Orthanc::ImageAccessor& renderedScene, + size_t layerIndex, + ImageInterpolation interpolation); }; } diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographySceneReader.cpp --- a/Framework/Radiography/RadiographySceneReader.cpp Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographySceneReader.cpp Tue Jan 21 16:52:37 2020 +0100 @@ -53,6 +53,16 @@ return dynamic_cast(&(scene_.LoadDicomFrame(orthancApiClient_, instanceId, frame, false, geometry))); } + RadiographyDicomLayer* RadiographySceneGeometryReader::LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry) + { + std::auto_ptr layer(new RadiographyPlaceholderLayer(dynamic_cast(scene_).GetBroker(), scene_)); + layer->SetGeometry(*geometry); + layer->SetSize(dicomImageWidth_, dicomImageHeight_); + scene_.RegisterLayer(layer.get()); + + return layer.release(); + } + void RadiographySceneBuilder::Read(const Json::Value& input) { unsigned int version = input["version"].asUInt(); @@ -130,82 +140,8 @@ } } - void RadiographySceneReader::Read(const Json::Value& input) - { - unsigned int version = input["version"].asUInt(); - - if (version != 1) - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - - if (input.isMember("hasWindowing") && input["hasWindowing"].asBool()) - { - scene_.SetWindowing(input["windowCenter"].asFloat(), input["windowWidth"].asFloat()); - } - - RadiographyDicomLayer* dicomLayer = NULL; - for(size_t layerIndex = 0; layerIndex < input["layers"].size(); layerIndex++) - { - const Json::Value& jsonLayer = input["layers"][(int)layerIndex]; - RadiographyLayer::Geometry geometry; - - if (jsonLayer["type"].asString() == "dicom") - { - ReadLayerGeometry(geometry, jsonLayer); - dicomLayer = dynamic_cast(&(scene_.LoadDicomFrame(orthancApiClient_, jsonLayer["instanceId"].asString(), jsonLayer["frame"].asUInt(), false, &geometry))); - } - else if (jsonLayer["type"].asString() == "mask") - { - if (dicomLayer == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // we always assumed the dicom layer was read before the mask - } - ReadLayerGeometry(geometry, jsonLayer); - float foreground = jsonLayer["foreground"].asFloat(); - std::vector corners; - for (size_t i = 0; i < jsonLayer["corners"].size(); i++) - { - Orthanc::ImageProcessing::ImagePoint corner(jsonLayer["corners"][(int)i]["x"].asInt(), - jsonLayer["corners"][(int)i]["y"].asInt()); - corners.push_back(corner); - } - scene_.LoadMask(corners, *dicomLayer, foreground, &geometry); - } - else if (jsonLayer["type"].asString() == "text") - { - ReadLayerGeometry(geometry, jsonLayer); - scene_.LoadText(jsonLayer["text"].asString(), jsonLayer["font"].asString(), jsonLayer["fontSize"].asUInt(), static_cast(jsonLayer["foreground"].asUInt()), &geometry, false); - } - else if (jsonLayer["type"].asString() == "alpha") - { - ReadLayerGeometry(geometry, jsonLayer); - - const std::string& pngContentBase64 = jsonLayer["content"].asString(); - std::string pngContent; - std::string mimeType; - Orthanc::Toolbox::DecodeDataUriScheme(mimeType, pngContent, pngContentBase64); - - std::auto_ptr image; - if (mimeType == "image/png") - { - image.reset(new Orthanc::PngReader()); - dynamic_cast(image.get())->ReadFromMemory(pngContent); - } - else - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - - RadiographyAlphaLayer& layer = dynamic_cast(scene_.LoadAlphaBitmap(image.release(), &geometry)); - - if (!jsonLayer["isUsingWindowing"].asBool()) - { - layer.SetForegroundValue((float)(jsonLayer["foreground"].asDouble())); - } - } - else - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } void RadiographySceneBuilder::ReadDicomLayerGeometry(RadiographyLayer::Geometry& geometry, const Json::Value& input) { diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Radiography/RadiographySceneReader.h --- a/Framework/Radiography/RadiographySceneReader.h Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Radiography/RadiographySceneReader.h Tue Jan 21 16:52:37 2020 +0100 @@ -33,6 +33,18 @@ namespace OrthancStone { + // a layer containing only the geometry of a DICOM layer (bit hacky !) + class RadiographyPlaceholderLayer : public RadiographyDicomLayer + { + public: + RadiographyPlaceholderLayer(MessageBroker& broker, const RadiographyScene& scene) : + RadiographyDicomLayer(broker, scene) + { + } + + }; + + // HACK: I had to introduce this builder class in order to be able to recreate a RadiographyScene // from a serialized scene that is passed to web-workers. // It needs some architecturing... @@ -77,7 +89,23 @@ { } - void Read(const Json::Value& input); + protected: + virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry); + }; + + // reads the whole scene but the DICOM image such that we have the full geometry + class RadiographySceneGeometryReader : public RadiographySceneBuilder + { + unsigned int dicomImageWidth_; + unsigned int dicomImageHeight_; + + public: + RadiographySceneGeometryReader(RadiographyScene& scene, unsigned int dicomImageWidth, unsigned int dicomImageHeight) : + RadiographySceneBuilder(scene), + dicomImageWidth_(dicomImageWidth), + dicomImageHeight_(dicomImageHeight) + { + } protected: virtual RadiographyDicomLayer* LoadDicom(const std::string& instanceId, unsigned int frame, RadiographyLayer::Geometry* geometry); diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Toolbox/AffineTransform2D.cpp --- a/Framework/Toolbox/AffineTransform2D.cpp Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Toolbox/AffineTransform2D.cpp Tue Jan 21 16:52:37 2020 +0100 @@ -246,6 +246,16 @@ return t; } + AffineTransform2D AffineTransform2D::CreateRotation(double angle, // CW rotation + double cx, // rotation center + double cy) // rotation center + { + return Combine( + CreateOffset(cx, cy), + CreateRotation(angle), + CreateOffset(-cx, -cy) + ); + } AffineTransform2D AffineTransform2D::CreateOpenGLClipspace(unsigned int canvasWidth, unsigned int canvasHeight) diff -r 9c20ae049669 -r 69177b10e2b9 Framework/Toolbox/AffineTransform2D.h --- a/Framework/Toolbox/AffineTransform2D.h Tue Jan 14 15:22:10 2020 +0100 +++ b/Framework/Toolbox/AffineTransform2D.h Tue Jan 21 16:52:37 2020 +0100 @@ -90,8 +90,12 @@ static AffineTransform2D CreateScaling(double sx, double sy); - - static AffineTransform2D CreateRotation(double angle); + + static AffineTransform2D CreateRotation(double angle); // CW rotation in radians + + static AffineTransform2D CreateRotation(double angle, // CW rotation in radians + double cx, // rotation center + double cy); // rotation center static AffineTransform2D CreateOpenGLClipspace(unsigned int canvasWidth, unsigned int canvasHeight);