Mercurial > hg > orthanc-stone
changeset 821:5c7b08bf84af
merge
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 29 May 2019 13:42:34 +0200 |
parents | 270c31978df1 (diff) e42b491f1fb2 (current diff) |
children | 76e8224bc300 28f99af358fa |
files | Framework/Scene2DViewport/PointerTypes.h |
diffstat | 10 files changed, 1007 insertions(+), 30 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Wed May 29 10:51:28 2019 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Wed May 29 13:42:34 2019 +0200 @@ -42,20 +42,22 @@ DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane), that_(that) { - if (GetProjection() == VolumeProjection_Axial) + if (IsValid()) { - // For coronal and sagittal projections, we take the global - // revision of the volume because even if a single slice changes, - // this means the projection will yield a different result --> - // we must increase the revision as soon as any slice changes - SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex())); - } + if (GetProjection() == VolumeProjection_Axial) + { + // For coronal and sagittal projections, we take the global + // revision of the volume because even if a single slice changes, + // this means the projection will yield a different result --> + // we must increase the revision as soon as any slice changes + SetRevision(that_.seriesGeometry_.GetSliceRevision(GetSliceIndex())); + } - if (that_.strategy_.get() != NULL && - IsValid() && - GetProjection() == VolumeProjection_Axial) - { - that_.strategy_->SetCurrent(GetSliceIndex()); + if (that_.strategy_.get() != NULL && + GetProjection() == VolumeProjection_Axial) + { + that_.strategy_->SetCurrent(GetSliceIndex()); + } } } }; @@ -264,7 +266,7 @@ std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand); tmp->SetHttpHeader("Accept-Encoding", "gzip"); tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); - tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); + tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); command.reset(tmp.release()); }
--- a/Framework/Oracle/OrthancRestApiCommand.h Wed May 29 10:51:28 2019 +0200 +++ b/Framework/Oracle/OrthancRestApiCommand.h Wed May 29 13:42:34 2019 +0200 @@ -95,6 +95,11 @@ void SetBody(const Json::Value& json); + void SwapBody(std::string& body) + { + body_.swap(body); + } + void SetHttpHeader(const std::string& key, const std::string& value) {
--- a/Framework/Oracle/ThreadedOracle.cpp Wed May 29 10:51:28 2019 +0200 +++ b/Framework/Oracle/ThreadedOracle.cpp Wed May 29 13:42:34 2019 +0200 @@ -248,7 +248,7 @@ client.SetTimeout(command.GetTimeout()); CopyHttpHeaders(client, command.GetHttpHeaders()); - + std::string answer; Orthanc::HttpClient::HttpHeaders answerHeaders; client.ApplyAndThrowException(answer, answerHeaders);
--- a/Samples/Sdl/Loader.cpp Wed May 29 10:51:28 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Wed May 29 13:42:34 2019 +0200 @@ -166,9 +166,9 @@ { printf("Geometry ready\n"); - //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); + plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); - plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); + //plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); Refresh();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/BasicMPR.cpp Wed May 29 13:42:34 2019 +0200 @@ -0,0 +1,880 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2019 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 <http://www.gnu.org/licenses/>. + **/ + + + +#include <emscripten.h> +#include <emscripten/fetch.h> +#include <emscripten/html5.h> + +#include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" +#include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" +#include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" +#include "../../Framework/Scene2D/OpenGLCompositor.h" +#include "../../Framework/Scene2D/PanSceneTracker.h" +#include "../../Framework/Scene2D/RotateSceneTracker.h" +#include "../../Framework/Scene2D/ZoomSceneTracker.h" +#include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" +#include "../../Framework/Scene2DViewport/ViewportController.h" +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Volumes/VolumeSceneLayerSource.h" + +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + + +static const unsigned int FONT_SIZE = 32; + + +namespace OrthancStone +{ + class WebAssemblyViewport : public boost::noncopyable + { + private: + // the construction order is important because compositor_ + // will hold a reference to the scene that belong to the + // controller_ object + OpenGL::WebAssemblyOpenGLContext context_; + boost::shared_ptr<ViewportController> controller_; + OpenGLCompositor compositor_; + + void SetupEvents(const std::string& canvas); + + public: + WebAssemblyViewport(MessageBroker& broker, + const std::string& canvas) : + context_(canvas), + controller_(new ViewportController(broker)), + compositor_(context_, *controller_->GetScene()) + { + compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, + FONT_SIZE, Orthanc::Encoding_Latin1); + SetupEvents(canvas); + } + + Scene2D& GetScene() + { + return *controller_->GetScene(); + } + + const boost::shared_ptr<ViewportController>& GetController() + { + return controller_; + } + + void UpdateSize() + { + context_.UpdateSize(); + compositor_.UpdateSize(); + Refresh(); + } + + void Refresh() + { + compositor_.Refresh(); + } + + const std::string& GetCanvasIdentifier() const + { + return context_.GetCanvasIdentifier(); + } + + ScenePoint2D GetPixelCenterCoordinates(int x, int y) const + { + return compositor_.GetPixelCenterCoordinates(x, y); + } + + unsigned int GetCanvasWidth() const + { + return context_.GetCanvasWidth(); + } + + unsigned int GetCanvasHeight() const + { + return context_.GetCanvasHeight(); + } + }; + + class ActiveTracker : public boost::noncopyable + { + private: + boost::shared_ptr<IFlexiblePointerTracker> tracker_; + std::string canvasIdentifier_; + bool insideCanvas_; + + public: + ActiveTracker(const boost::shared_ptr<IFlexiblePointerTracker>& tracker, + const WebAssemblyViewport& viewport) : + tracker_(tracker), + canvasIdentifier_(viewport.GetCanvasIdentifier()), + insideCanvas_(true) + { + if (tracker_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + bool IsAlive() const + { + return tracker_->IsAlive(); + } + + void PointerMove(const PointerEvent& event) + { + tracker_->PointerMove(event); + } + + void PointerUp(const PointerEvent& event) + { + tracker_->PointerUp(event); + } + }; +} + +static OrthancStone::PointerEvent* ConvertMouseEvent( + const EmscriptenMouseEvent& source, + OrthancStone::WebAssemblyViewport& viewport) +{ + std::auto_ptr<OrthancStone::PointerEvent> target( + new OrthancStone::PointerEvent); + + target->AddPosition(viewport.GetPixelCenterCoordinates( + source.targetX, source.targetY)); + target->SetAltModifier(source.altKey); + target->SetControlModifier(source.ctrlKey); + target->SetShiftModifier(source.shiftKey); + + return target.release(); +} + +std::auto_ptr<OrthancStone::ActiveTracker> tracker_; + +EM_BOOL OnMouseEvent(int eventType, + const EmscriptenMouseEvent *mouseEvent, + void *userData) +{ + if (mouseEvent != NULL && + userData != NULL) + { + OrthancStone::WebAssemblyViewport& viewport = + *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData); + + switch (eventType) + { + case EMSCRIPTEN_EVENT_CLICK: + { + static unsigned int count = 0; + char buf[64]; + sprintf(buf, "click %d", count++); + + std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer); + layer->SetText(buf); + viewport.GetScene().SetLayer(100, layer.release()); + viewport.Refresh(); + break; + } + + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; + + { + std::auto_ptr<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, viewport)); + + switch (mouseEvent->button) + { + case 0: // Left button + emscripten_console_log("Creating RotateSceneTracker"); + t.reset(new OrthancStone::RotateSceneTracker( + viewport.GetController(), *event)); + break; + + case 1: // Middle button + emscripten_console_log("Creating PanSceneTracker"); + LOG(INFO) << "Creating PanSceneTracker" ; + t.reset(new OrthancStone::PanSceneTracker( + viewport.GetController(), *event)); + break; + + case 2: // Right button + emscripten_console_log("Creating ZoomSceneTracker"); + t.reset(new OrthancStone::ZoomSceneTracker( + viewport.GetController(), *event, viewport.GetCanvasWidth())); + break; + + default: + break; + } + } + + if (t.get() != NULL) + { + tracker_.reset( + new OrthancStone::ActiveTracker(t, viewport)); + viewport.Refresh(); + } + + break; + } + + case EMSCRIPTEN_EVENT_MOUSEMOVE: + if (tracker_.get() != NULL) + { + std::auto_ptr<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, viewport)); + tracker_->PointerMove(*event); + viewport.Refresh(); + } + break; + + case EMSCRIPTEN_EVENT_MOUSEUP: + if (tracker_.get() != NULL) + { + std::auto_ptr<OrthancStone::PointerEvent> event( + ConvertMouseEvent(*mouseEvent, viewport)); + tracker_->PointerUp(*event); + viewport.Refresh(); + if (!tracker_->IsAlive()) + tracker_.reset(); + } + break; + + default: + break; + } + } + + return true; +} + + +void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas) +{ + emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent); + emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent); + emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent); +} + + + + +namespace OrthancStone +{ + class VolumeSlicerViewport : public IObserver + { + private: + OrthancStone::WebAssemblyViewport viewport_; + std::auto_ptr<VolumeSceneLayerSource> source_; + VolumeProjection projection_; + std::vector<CoordinateSystem3D> planes_; + size_t currentPlane_; + + void Handle(const DicomVolumeImage::GeometryReadyMessage& message) + { + LOG(INFO) << "Geometry is available"; + + const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry(); + + const unsigned int depth = geometry.GetProjectionDepth(projection_); + currentPlane_ = depth / 2; + + planes_.resize(depth); + + for (unsigned int z = 0; z < depth; z++) + { + planes_[z] = geometry.GetProjectionSlice(projection_, z); + } + } + + public: + VolumeSlicerViewport(MessageBroker& broker, + const std::string& canvas, + VolumeProjection projection) : + IObserver(broker), + viewport_(broker, canvas), + projection_(projection), + currentPlane_(0) + { + } + + void UpdateSize() + { + viewport_.UpdateSize(); + } + + void SetSlicer(int layerDepth, + const boost::shared_ptr<IVolumeSlicer>& slicer, + IObservable& loader, + ILayerStyleConfigurator* configurator) + { + if (source_.get() != NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, + "Only one slicer can be registered"); + } + + loader.RegisterObserverCallback( + new Callable<VolumeSlicerViewport, DicomVolumeImage::GeometryReadyMessage> + (*this, &VolumeSlicerViewport::Handle)); + + source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer)); + + if (configurator != NULL) + { + source_->SetConfigurator(configurator); + } + } + + void Refresh() + { + if (source_.get() != NULL && + currentPlane_ < planes_.size()) + { + source_->Update(planes_[currentPlane_]); + } + } + }; + + + + + class WebAssemblyOracle : + public IOracle, + public IObservable + { + private: + typedef std::map<std::string, std::string> HttpHeaders; + + class FetchContext : public boost::noncopyable + { + private: + class Emitter : public IMessageEmitter + { + private: + WebAssemblyOracle& oracle_; + + public: + Emitter(WebAssemblyOracle& oracle) : + oracle_(oracle) + { + } + + virtual void EmitMessage(const IObserver& receiver, + const IMessage& message) + { + oracle_.EmitMessage(receiver, message); + } + }; + + Emitter emitter_; + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + std::string expectedContentType_; + + public: + FetchContext(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command, + const std::string& expectedContentType) : + emitter_(oracle), + receiver_(receiver), + command_(command), + expectedContentType_(expectedContentType) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const std::string& GetExpectedContentType() const + { + return expectedContentType_; + } + + void EmitMessage(const IMessage& message) + { + emitter_.EmitMessage(receiver_, message); + } + + IMessageEmitter& GetEmitter() + { + return emitter_; + } + + const IObserver& GetReceiver() const + { + return receiver_; + } + + IOracleCommand& GetCommand() const + { + return *command_; + } + + template <typename T> + const T& GetTypedCommand() const + { + return dynamic_cast<T&>(*command_); + } + }; + + static void FetchSucceeded(emscripten_fetch_t *fetch) + { + /** + * Firstly, make a local copy of the fetched information, and + * free data associated with the fetch. + **/ + + std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + std::string answer; + if (fetch->numBytes > 0) + { + answer.assign(fetch->data, fetch->numBytes); + } + + /** + * TODO - HACK - As of emscripten-1.38.31, the fetch API does + * not contain a way to retrieve the HTTP headers of the + * answer. We make the assumption that the "Content-Type" header + * of the response is the same as the "Accept" header of the + * query. This should be fixed in future versions of emscripten. + * https://github.com/emscripten-core/emscripten/pull/8486 + **/ + + HttpHeaders headers; + if (!context->GetExpectedContentType().empty()) + { + headers["Content-Type"] = context->GetExpectedContentType(); + } + + + emscripten_fetch_close(fetch); + + + /** + * Secondly, use the retrieved data. + **/ + + try + { + if (context.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + switch (context->GetCommand().GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + { + OrthancRestApiCommand::SuccessMessage message + (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); + context->EmitMessage(message); + break; + } + + case IOracleCommand::Type_GetOrthancImage: + { + context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer + (context->GetEmitter(), context->GetReceiver(), answer, headers); + break; + } + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + { + context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer + (context->GetEmitter(), context->GetReceiver(), answer); + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " + << context->GetCommand().GetType(); + } + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); + } + } + + static void FetchFailed(emscripten_fetch_t *fetch) + { + std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); + + LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; + + /** + * TODO - The following code leads to an infinite recursion, at + * least with Firefox running on incognito mode => WHY? + **/ + //emscripten_fetch_close(fetch); // Also free data on failure. + } + + + class FetchCommand : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + const IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + std::string expectedContentType_; + + public: + FetchCommand(WebAssemblyOracle& oracle, + const IObserver& receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver), + command_(command), + method_(Orthanc::HttpMethod_Get), + timeout_(0) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetBody(std::string& body /* will be swapped */) + { + body_.swap(body); + } + + void SetHttpHeaders(const HttpHeaders& headers) + { + headers_ = headers; + } + + void SetTimeout(unsigned int timeout) + { + timeout_ = timeout; + } + + void Execute() + { + if (command_.get() == NULL) + { + // Cannot call Execute() twice + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + const char* method; + + switch (method_) + { + case Orthanc::HttpMethod_Get: + method = "GET"; + break; + + case Orthanc::HttpMethod_Post: + method = "POST"; + break; + + case Orthanc::HttpMethod_Delete: + method = "DELETE"; + break; + + case Orthanc::HttpMethod_Put: + method = "PUT"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + strcpy(attr.requestMethod, method); + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + attr.onsuccess = FetchSucceeded; + attr.onerror = FetchFailed; + attr.timeoutMSecs = timeout_ * 1000; + + std::vector<const char*> headers; + headers.reserve(2 * headers_.size() + 1); + + std::string expectedContentType; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + std::string key; + Orthanc::Toolbox::ToLowerCase(key, it->first); + + if (key == "accept") + { + expectedContentType = it->second; + } + + if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header + { + headers.push_back(it->first.c_str()); + headers.push_back(it->second.c_str()); + } + } + + headers.push_back(NULL); // Termination of the array of HTTP headers + + attr.requestHeaders = &headers[0]; + + if (!body_.empty()) + { + attr.requestDataSize = body_.size(); + attr.requestData = body_.c_str(); + } + + // Must be the last call to prevent memory leak on error + attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); + emscripten_fetch(&attr, uri_.c_str()); + } + }; + + + void Execute(const IObserver& receiver, + OrthancRestApiCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Put || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + } + + + void Execute(const IObserver& receiver, + GetOrthancImageCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void Execute(const IObserver& receiver, + GetOrthancWebViewerJpegCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetUri(command->GetUri()); + fetch.SetHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + public: + WebAssemblyOracle(MessageBroker& broker) : + IObservable(broker) + { + } + + virtual void Schedule(const IObserver& receiver, + IOracleCommand* command) + { + std::auto_ptr<IOracleCommand> protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + switch (command->GetType()) + { + case IOracleCommand::Type_OrthancRestApi: + Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); + break; + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); + } + } + + virtual void Start() + { + } + + virtual void Stop() + { + } + }; +} + + + + +boost::shared_ptr<OrthancStone::DicomVolumeImage> ct_(new OrthancStone::DicomVolumeImage); + +boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_; + +std::auto_ptr<OrthancStone::VolumeSlicerViewport> viewport1_; +std::auto_ptr<OrthancStone::VolumeSlicerViewport> viewport2_; +std::auto_ptr<OrthancStone::VolumeSlicerViewport> viewport3_; + +OrthancStone::MessageBroker broker_; +OrthancStone::WebAssemblyOracle oracle_(broker_); + + +EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) +{ + try + { + if (viewport1_.get() != NULL) + { + viewport1_->UpdateSize(); + } + + if (viewport2_.get() != NULL) + { + viewport2_->UpdateSize(); + } + + if (viewport3_.get() != NULL) + { + viewport3_->UpdateSize(); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception while updating canvas size: " << e.What(); + } + + return true; +} + + + + +EM_BOOL OnAnimationFrame(double time, void *userData) +{ + try + { + if (viewport1_.get() != NULL) + { + viewport1_->Refresh(); + } + + if (viewport2_.get() != NULL) + { + viewport2_->Refresh(); + } + + if (viewport3_.get() != NULL) + { + viewport3_->Refresh(); + } + + return true; + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What(); + return false; + } +} + + +extern "C" +{ + int main(int argc, char const *argv[]) + { + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + // Orthanc::Logging::EnableTraceLevel(true); + EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); + } + + EMSCRIPTEN_KEEPALIVE + void Initialize() + { + try + { + loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_)); + + viewport1_.reset(new OrthancStone::VolumeSlicerViewport(broker_, "mycanvas1", OrthancStone::VolumeProjection_Axial)); + viewport1_->SetSlicer(0, loader_, *loader_, new OrthancStone::GrayscaleStyleConfigurator); + viewport1_->UpdateSize(); + + viewport2_.reset(new OrthancStone::VolumeSlicerViewport(broker_, "mycanvas2", OrthancStone::VolumeProjection_Coronal)); + viewport2_->SetSlicer(0, loader_, *loader_, new OrthancStone::GrayscaleStyleConfigurator); + viewport2_->UpdateSize(); + + viewport3_.reset(new OrthancStone::VolumeSlicerViewport(broker_, "mycanvas3", OrthancStone::VolumeProjection_Sagittal)); + viewport3_->SetSlicer(0, loader_, *loader_, new OrthancStone::GrayscaleStyleConfigurator); + viewport3_->UpdateSize(); + + emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); + + emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); + + oracle_.Start(); + loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception during Initialize(): " << e.What(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/BasicMPR.html Wed May 29 13:42:34 2019 +0200 @@ -0,0 +1,69 @@ +<!doctype html> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <!-- Disable pinch zoom on mobile devices --> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <meta name="HandheldFriendly" content="true" /> + + + <title>Stone of Orthanc</title> + + <style> + html, body { + width: 100%; + height: 100%; + margin: 0px; + border: 0; + overflow: hidden; /* Disable scrollbars */ + display: block; /* No floating content on sides */ + } + + #mycanvas1 { + position:absolute; + left:0%; + top:0%; + background-color: red; + width: 50%; + height: 100%; + } + + #mycanvas2 { + position:absolute; + left:50%; + top:0%; + background-color: green; + width: 50%; + height: 50%; + } + + #mycanvas3 { + position:absolute; + left:50%; + top:50%; + background-color: blue; + width: 50%; + height: 50%; + } + </style> + </head> + <body> + <canvas id="mycanvas1" oncontextmenu="return false;"></canvas> + <canvas id="mycanvas2" oncontextmenu="return false;"></canvas> + <canvas id="mycanvas3" oncontextmenu="return false;"></canvas> + + <script type="text/javascript"> + if (!('WebAssembly' in window)) { + alert('Sorry, your browser does not support WebAssembly :('); + } else { + window.addEventListener('WebAssemblyLoaded', function() { + Module.ccall('Initialize', null, null, null); + }); + } + </script> + + <script type="text/javascript" async src="BasicMPR.js"></script> + </body> +</html>
--- a/Samples/WebAssembly/BasicScene.cpp Wed May 29 10:51:28 2019 +0200 +++ b/Samples/WebAssembly/BasicScene.cpp Wed May 29 13:42:34 2019 +0200 @@ -113,14 +113,14 @@ chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5)); chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 255, 0, 0); chain.clear(); chain.push_back(ScenePoint2D(-5, -5)); chain.push_back(ScenePoint2D(5, -5)); chain.push_back(ScenePoint2D(5, 5)); chain.push_back(ScenePoint2D(-5, 5)); - layer->AddChain(chain, true); + layer->AddChain(chain, true, 0, 255, 0); double dy = 1.01; chain.clear(); @@ -128,9 +128,8 @@ chain.push_back(ScenePoint2D(4, -4 + dy)); chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy)); chain.push_back(ScenePoint2D(4, 2)); - layer->AddChain(chain, false); + layer->AddChain(chain, false, 0, 0, 255); - layer->SetColor(0,255, 255); scene->SetLayer(50, layer.release()); }
--- a/Samples/WebAssembly/CMakeLists.txt Wed May 29 10:51:28 2019 +0200 +++ b/Samples/WebAssembly/CMakeLists.txt Wed May 29 13:42:34 2019 +0200 @@ -13,6 +13,7 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXIT_RUNTIME=1") ##################################################################### @@ -68,20 +69,40 @@ ${ORTHANC_STONE_SOURCES} ) -add_executable(BasicScene - BasicScene.cpp - ) + +if (OFF) + add_executable(BasicScene + BasicScene.cpp + ) + + target_link_libraries(BasicScene OrthancStone) + + install( + TARGETS BasicScene + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() -target_link_libraries(BasicScene OrthancStone) + +if (ON) + add_executable(BasicMPR + BasicMPR.cpp + ) -install( - TARGETS BasicScene - RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} - ) + target_link_libraries(BasicMPR OrthancStone) + + install( + TARGETS BasicMPR + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() + install( FILES + ${CMAKE_CURRENT_BINARY_DIR}/BasicMPR.wasm ${CMAKE_CURRENT_BINARY_DIR}/BasicScene.wasm + ${CMAKE_SOURCE_DIR}/BasicMPR.html ${CMAKE_SOURCE_DIR}/BasicScene.html ${CMAKE_SOURCE_DIR}/Configuration.json ${CMAKE_SOURCE_DIR}/index.html
--- a/Samples/WebAssembly/NOTES.txt Wed May 29 10:51:28 2019 +0200 +++ b/Samples/WebAssembly/NOTES.txt Wed May 29 13:42:34 2019 +0200 @@ -1,7 +1,7 @@ $ source ~/Downloads/emsdk/emsdk_env.sh $ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone $ ninja install -$ sudo docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose +$ docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro -v /tmp/stone-db/:/var/lib/orthanc/db/ jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose notes BGO : source ~/apps/emsdk/emsdk_env.sh @@ -11,4 +11,4 @@ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly/installDir ninja install -docker run -p 4242:4242 -p 8042:8042 --rm -v "C:/osi/dev/orthanc-stone/Samples/WebAssembly/installDir:/root/stone:ro" jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose \ No newline at end of file +docker run -p 4242:4242 -p 8042:8042 --rm -v "C:/osi/dev/orthanc-stone/Samples/WebAssembly/installDir:/root/stone:ro" jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose
--- a/Samples/WebAssembly/index.html Wed May 29 10:51:28 2019 +0200 +++ b/Samples/WebAssembly/index.html Wed May 29 13:42:34 2019 +0200 @@ -10,6 +10,7 @@ <h1>Available samples</h1> <ul> <li><a href="BasicScene.html">Basic scene</a></li> + <li><a href="BasicMPR.html">Basic MPR display</a></li> </ul> </body> </html>