# HG changeset patch # User Sebastien Jodogne # Date 1541603822 -3600 # Node ID d20d75f20c5d3c46a9caabf68a802b2e303aa56b # Parent 939f626eb6d7368fa2eb67c946f77cbe7634ef4a better following of the MVC design pattern diff -r 939f626eb6d7 -r d20d75f20c5d Applications/Samples/SingleFrameEditorApplication.h --- a/Applications/Samples/SingleFrameEditorApplication.h Tue Nov 06 20:12:42 2018 +0100 +++ b/Applications/Samples/SingleFrameEditorApplication.h Wed Nov 07 16:17:02 2018 +0100 @@ -218,8 +218,8 @@ public: - Bitmap(size_t index) : - index_(index), + Bitmap() : + index_(0), hasSize_(false), width_(0), height_(0), @@ -238,6 +238,11 @@ { } + void SetIndex(size_t index) + { + index_ = index; + } + size_t GetIndex() const { return index_; @@ -703,9 +708,7 @@ float foreground_; public: - AlphaBitmap(size_t index, - const BitmapStack& stack) : - Bitmap(index), + AlphaBitmap(const BitmapStack& stack) : stack_(stack), useWindowing_(true), foreground_(0) @@ -750,6 +753,11 @@ const Matrix& viewTransform, ImageInterpolation interpolation) const { + if (alpha_.get() == NULL) + { + return; + } + if (buffer.GetFormat() != Orthanc::PixelFormat_Float32) { throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); @@ -850,11 +858,6 @@ } public: - DicomBitmap(size_t index) : - Bitmap(index) - { - } - void SetDicomTags(const OrthancPlugins::FullOrthancDataset& dataset) { converter_.reset(new DicomFrameConverter); @@ -978,8 +981,6 @@ float windowingCenter_; float windowingWidth_; Bitmaps bitmaps_; - bool hasSelection_; - size_t selectedBitmap_; public: BitmapStack(MessageBroker& broker, @@ -990,40 +991,11 @@ countBitmaps_(0), hasWindowing_(false), windowingCenter_(0), // Dummy initialization - windowingWidth_(0), // Dummy initialization - hasSelection_(false), - selectedBitmap_(0) // Dummy initialization + windowingWidth_(0) // Dummy initialization { } - void Unselect() - { - hasSelection_ = false; - } - - - void Select(size_t bitmap) - { - hasSelection_ = true; - selectedBitmap_ = bitmap; - } - - - bool GetSelectedBitmap(size_t& bitmap) const - { - if (hasSelection_) - { - bitmap = selectedBitmap_; - return true; - } - else - { - return false; - } - } - - virtual ~BitmapStack() { for (Bitmaps::iterator it = bitmaps_.begin(); it != bitmaps_.end(); it++) @@ -1068,33 +1040,42 @@ hasWindowing_ = true; windowingCenter_ = center; windowingWidth_ = width; - - //EmitMessage(ContentChangedMessage(*this)); + } + + + Bitmap& RegisterBitmap(Bitmap* bitmap) + { + if (bitmap == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + std::auto_ptr raii(bitmap); + + size_t index = countBitmaps_++; + raii->SetIndex(index); + bitmaps_[index] = raii.release(); + + EmitMessage(GeometryChangedMessage(*this)); + EmitMessage(ContentChangedMessage(*this)); + + return *bitmap; } Bitmap& LoadText(const Orthanc::Font& font, const std::string& utf8) { - size_t bitmap = countBitmaps_++; - - std::auto_ptr alpha(new AlphaBitmap(bitmap, *this)); + std::auto_ptr alpha(new AlphaBitmap(*this)); alpha->LoadText(font, utf8); - AlphaBitmap* ptr = alpha.get(); - bitmaps_[bitmap] = alpha.release(); - - return *ptr; + return RegisterBitmap(alpha.release()); } Bitmap& LoadTestBlock(unsigned int width, unsigned int height) { - size_t bitmap = countBitmaps_++; - - std::auto_ptr alpha(new AlphaBitmap(bitmap, *this)); - std::auto_ptr block(new Orthanc::Image(Orthanc::PixelFormat_Grayscale8, width, height, false)); for (unsigned int padding = 0; @@ -1116,12 +1097,10 @@ Orthanc::ImageProcessing::Set(region, color); } + std::auto_ptr alpha(new AlphaBitmap(*this)); alpha->SetAlpha(block.release()); - AlphaBitmap* ptr = alpha.get(); - bitmaps_[bitmap] = alpha.release(); - - return *ptr; + return RegisterBitmap(alpha.release()); } @@ -1129,17 +1108,15 @@ unsigned int frame, bool httpCompression) { - size_t bitmap = countBitmaps_++; - - bitmaps_[bitmap] = new DicomBitmap(bitmap); - + Bitmap& bitmap = RegisterBitmap(new DicomBitmap); + { IWebService::Headers headers; std::string uri = "/instances/" + instance + "/tags"; orthanc_.GetBinaryAsync(uri, headers, new Callable (*this, &BitmapStack::OnTagsReceived), NULL, - new Orthanc::SingleValueObject(bitmap)); + new Orthanc::SingleValueObject(bitmap.GetIndex())); } { @@ -1155,10 +1132,10 @@ orthanc_.GetBinaryAsync(uri, headers, new Callable (*this, &BitmapStack::OnFrameReceived), NULL, - new Orthanc::SingleValueObject(bitmap)); + new Orthanc::SingleValueObject(bitmap.GetIndex())); } - return *bitmaps_[bitmap]; + return bitmap; } @@ -1274,19 +1251,17 @@ return false; } - void DrawControls(CairoContext& context, - double zoom) + + void DrawBorder(CairoContext& context, + unsigned int bitmap, + double zoom) { - if (hasSelection_) - { - Bitmaps::const_iterator bitmap = bitmaps_.find(selectedBitmap_); + Bitmaps::const_iterator found = bitmaps_.find(bitmap); - if (bitmap != bitmaps_.end()) - { - context.SetSourceColor(255, 0, 0); - //view.ApplyTransform(context); - bitmap->second->DrawBorders(context, zoom); - } + if (found != bitmaps_.end()) + { + context.SetSourceColor(255, 0, 0); + found->second->DrawBorders(context, zoom); } } @@ -1324,6 +1299,121 @@ maxValue = 0; } } + + + void Export(const Orthanc::DicomMap& dicom, + double pixelSpacingX, + double pixelSpacingY, + bool invert, + ImageInterpolation interpolation) + { + if (pixelSpacingX <= 0 || + pixelSpacingY <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + LOG(WARNING) << "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 EXPORT_USING_PAM == 1 + { + Orthanc::PamWriter writer; + writer.WriteToMemory(content, rendered); + } +#else + { + Orthanc::PngWriter writer; + writer.WriteToMemory(content, rendered); + } +#endif + + 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 + 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)); + } + +#if EXPORT_USING_PAM == 1 + json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + base64; +#else + json["Content"] = "data:" + std::string(Orthanc::MIME_PNG) + ";base64," + base64; +#endif + + orthanc_.PostJsonAsyncExpectJson( + "/tools/create-dicom", json, + new Callable + (*this, &BitmapStack::OnDicomExported), + NULL, NULL); + } + + + void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) + { + LOG(WARNING) << "DICOM export was successful:" + << message.GetJson().toStyledString(); + } }; @@ -2207,7 +2297,6 @@ class BitmapStackWidget : public WorldSceneWidget, - public IObservable, public IObserver { private: @@ -2216,6 +2305,8 @@ std::auto_ptr cairoBuffer_; bool invert_; ImageInterpolation interpolation_; + bool hasSelection_; + size_t selectedBitmap_; virtual bool RenderInternal(unsigned int width, unsigned int height, @@ -2295,7 +2386,6 @@ } } - protected: virtual Extent2D GetSceneExtent() { @@ -2323,7 +2413,10 @@ cairo_paint(cr); } - stack_.DrawControls(context, view.GetZoom()); + if (hasSelection_) + { + stack_.DrawBorder(context, selectedBitmap_, view.GetZoom()); + } return true; } @@ -2333,11 +2426,12 @@ BitmapStack& stack, const std::string& name) : WorldSceneWidget(name), - IObservable(broker), IObserver(broker), stack_(stack), invert_(false), - interpolation_(ImageInterpolation_Nearest) + interpolation_(ImageInterpolation_Nearest), + hasSelection_(false), + selectedBitmap_(0) // Dummy initialization { stack.RegisterObserverCallback(new Callable(*this, &BitmapStackWidget::OnGeometryChanged)); stack.RegisterObserverCallback(new Callable(*this, &BitmapStackWidget::OnContentChanged)); @@ -2348,6 +2442,30 @@ return stack_; } + void Unselect() + { + hasSelection_ = false; + } + + void Select(size_t bitmap) + { + hasSelection_ = true; + selectedBitmap_ = bitmap; + } + + bool LookupSelectedBitmap(size_t& bitmap) + { + if (hasSelection_) + { + bitmap = selectedBitmap_; + return true; + } + else + { + return false; + } + } + void OnGeometryChanged(const BitmapStack::GeometryChangedMessage& message) { LOG(INFO) << "Geometry has changed"; @@ -2375,7 +2493,7 @@ NotifyContentChanged(); } - bool IsInvert() const + bool IsInverted() const { return invert_; } @@ -2413,7 +2531,6 @@ UndoRedoStack undoRedoStack_; Tool tool_; - OrthancApiClient *orthanc_; static double GetHandleSize() @@ -2421,28 +2538,15 @@ return 10.0; } - - static BitmapStackWidget& GetWidget(WorldSceneWidget& widget) - { - return dynamic_cast(widget); - } - - - static BitmapStack& GetStack(WorldSceneWidget& widget) - { - return GetWidget(widget).GetStack(); - } - - + public: BitmapStackInteractor(MessageBroker& broker) : IObserver(broker), - tool_(Tool_Move), - orthanc_(NULL) + tool_(Tool_Move) { } - virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& worldWidget, const ViewportGeometry& view, MouseButton button, KeyboardModifiers modifiers, @@ -2452,26 +2556,28 @@ double y, IStatusBar* statusBar) { + BitmapStackWidget& widget = dynamic_cast(worldWidget); + if (button == MouseButton_Left) { size_t selected; - + if (tool_ == Tool_Windowing) { - return new WindowingTracker(undoRedoStack_, GetStack(widget), + return new WindowingTracker(undoRedoStack_, widget.GetStack(), viewportX, viewportY, WindowingTracker::Action_DecreaseWidth, WindowingTracker::Action_IncreaseWidth, WindowingTracker::Action_DecreaseCenter, WindowingTracker::Action_IncreaseCenter); } - else if (!GetStack(widget).GetSelectedBitmap(selected)) + else if (!widget.LookupSelectedBitmap(selected)) { + // No bitmap is currently selected size_t bitmap; - if (GetStack(widget).LookupBitmap(bitmap, x, y)) + if (widget.GetStack().LookupBitmap(bitmap, x, y)) { - LOG(INFO) << "Click on bitmap " << bitmap; - GetStack(widget).Select(bitmap); + widget.Select(bitmap); } return NULL; @@ -2479,17 +2585,17 @@ else if (tool_ == Tool_Crop || tool_ == Tool_Resize) { - BitmapStack::BitmapAccessor accessor(GetStack(widget), selected); + BitmapStack::BitmapAccessor accessor(widget.GetStack(), selected); BitmapStack::Corner corner; if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) { switch (tool_) { case Tool_Crop: - return new CropBitmapTracker(undoRedoStack_, GetStack(widget), view, selected, x, y, corner); + return new CropBitmapTracker(undoRedoStack_, widget.GetStack(), view, selected, x, y, corner); case Tool_Resize: - return new ResizeBitmapTracker(undoRedoStack_, GetStack(widget), selected, x, y, corner, + return new ResizeBitmapTracker(undoRedoStack_, widget.GetStack(), selected, x, y, corner, (modifiers & KeyboardModifiers_Shift)); default: @@ -2500,10 +2606,13 @@ { size_t bitmap; - if (!GetStack(widget).LookupBitmap(bitmap, x, y) || - bitmap != selected) + if (widget.GetStack().LookupBitmap(bitmap, x, y)) { - GetStack(widget).Unselect(); + widget.Select(bitmap); + } + else + { + widget.Unselect(); } return NULL; @@ -2513,29 +2622,35 @@ { size_t bitmap; - if (GetStack(widget).LookupBitmap(bitmap, x, y) && - bitmap == selected) + if (widget.GetStack().LookupBitmap(bitmap, x, y)) { - switch (tool_) + if (bitmap == selected) { - case Tool_Move: - return new MoveBitmapTracker(undoRedoStack_, GetStack(widget), bitmap, x, y, - (modifiers & KeyboardModifiers_Shift)); - - case Tool_Rotate: - return new RotateBitmapTracker(undoRedoStack_, GetStack(widget), view, bitmap, x, y, + switch (tool_) + { + case Tool_Move: + return new MoveBitmapTracker(undoRedoStack_, widget.GetStack(), bitmap, x, y, (modifiers & KeyboardModifiers_Shift)); + + case Tool_Rotate: + return new RotateBitmapTracker(undoRedoStack_, widget.GetStack(), view, bitmap, x, y, + (modifiers & KeyboardModifiers_Shift)); - default: - break; + default: + break; + } + + return NULL; } - - return NULL; + else + { + widget.Select(bitmap); + return NULL; + } } else { - LOG(INFO) << "Click out of any bitmap"; - GetStack(widget).Unselect(); + widget.Unselect(); return NULL; } } @@ -2547,12 +2662,14 @@ } virtual void MouseOver(CairoContext& context, - WorldSceneWidget& widget, + WorldSceneWidget& worldWidget, const ViewportGeometry& view, double x, double y, IStatusBar* statusBar) { + BitmapStackWidget& widget = dynamic_cast(worldWidget); + #if 0 if (statusBar != NULL) { @@ -2563,11 +2680,12 @@ #endif size_t selected; - if (GetStack(widget).GetSelectedBitmap(selected) && + + if (widget.LookupSelectedBitmap(selected) && (tool_ == Tool_Crop || tool_ == Tool_Resize)) { - BitmapStack::BitmapAccessor accessor(GetStack(widget), selected); + BitmapStack::BitmapAccessor accessor(widget.GetStack(), selected); BitmapStack::Corner corner; if (accessor.GetBitmap().LookupCorner(corner, x, y, view.GetZoom(), GetHandleSize())) @@ -2596,12 +2714,14 @@ { } - virtual void KeyPressed(WorldSceneWidget& widget, + virtual void KeyPressed(WorldSceneWidget& worldWidget, KeyboardKeys key, char keyChar, KeyboardModifiers modifiers, IStatusBar* statusBar) { + BitmapStackWidget& widget = dynamic_cast(worldWidget); + switch (keyChar) { case 'a': @@ -2634,12 +2754,12 @@ tags.SetValue(Orthanc::DICOM_TAG_STUDY_ID, "STUDY", false); tags.SetValue(Orthanc::DICOM_TAG_VIEW_POSITION, "", false); - Export(GetWidget(widget), 0.1, 0.1, tags); + widget.GetStack().Export(tags, 0.1, 0.1, widget.IsInverted(), widget.GetInterpolation()); break; } case 'i': - GetWidget(widget).SwitchInvert(); + widget.SwitchInvert(); break; case 'm': @@ -2648,16 +2768,16 @@ case 'n': { - switch (GetWidget(widget).GetInterpolation()) + switch (widget.GetInterpolation()) { case ImageInterpolation_Nearest: LOG(INFO) << "Switching to bilinear interpolation"; - GetWidget(widget).SetInterpolation(ImageInterpolation_Bilinear); + widget.SetInterpolation(ImageInterpolation_Bilinear); break; case ImageInterpolation_Bilinear: LOG(INFO) << "Switching to nearest neighbor interpolation"; - GetWidget(widget).SetInterpolation(ImageInterpolation_Nearest); + widget.SetInterpolation(ImageInterpolation_Nearest); break; default: @@ -2699,132 +2819,6 @@ break; } } - - - void SetOrthanc(OrthancApiClient& orthanc) - { - orthanc_ = &orthanc; - } - - - void Export(const BitmapStackWidget& widget, - double pixelSpacingX, - double pixelSpacingY, - const Orthanc::DicomMap& dicom) - { - if (pixelSpacingX <= 0 || - pixelSpacingY <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - if (orthanc_ == NULL) - { - return; - } - - LOG(WARNING) << "Exporting DICOM"; - - Extent2D extent = widget.GetStack().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())); - - widget.GetStack().Render(layers, view, widget.GetInterpolation()); - - Orthanc::Image rendered(Orthanc::PixelFormat_Grayscale16, - layers.GetWidth(), layers.GetHeight(), false); - Orthanc::ImageProcessing::Convert(rendered, layers); - - std::string base64; - - { - std::string content; - -#if EXPORT_USING_PAM == 1 - { - Orthanc::PamWriter writer; - writer.WriteToMemory(content, rendered); - } -#else - { - Orthanc::PngWriter writer; - writer.WriteToMemory(content, rendered); - } -#endif - - 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()] = - (widget.IsInvert() ? "MONOCHROME1" : "MONOCHROME2"); - - - // WARNING: The order of PixelSpacing is Y/X - char buf[32]; - sprintf(buf, "%0.8f\\%0.8f", pixelSpacingY, pixelSpacingX); - - json["Tags"][Orthanc::DICOM_TAG_PIXEL_SPACING.Format()] = buf; - - float center, width; - if (widget.GetStack().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)); - } - -#if EXPORT_USING_PAM == 1 - json["Content"] = "data:" + std::string(Orthanc::MIME_PAM) + ";base64," + base64; -#else - json["Content"] = "data:" + std::string(Orthanc::MIME_PNG) + ";base64," + base64; -#endif - - orthanc_->PostJsonAsyncExpectJson( - "/tools/create-dicom", json, - new Callable - (*this, &BitmapStackInteractor::OnDicomExported), - NULL, NULL); - } - - - void OnDicomExported(const OrthancApiClient::JsonResponseReadyMessage& message) - { - LOG(WARNING) << "DICOM export was successful:" - << message.GetJson().toStyledString(); - } }; @@ -2846,6 +2840,11 @@ interactor_(broker) { } + + virtual ~SingleFrameEditorApplication() + { + LOG(WARNING) << "Destroying the application"; + } virtual void DeclareStartupOptions(boost::program_options::options_description& options) { @@ -2892,7 +2891,6 @@ int frame = parameters["frame"].as(); orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService())); - interactor_.SetOrthanc(*orthancApiClient_); Orthanc::FontRegistry fonts; fonts.AddFromResource(Orthanc::EmbeddedResources::FONT_UBUNTU_MONO_BOLD_16);