Mercurial > hg > orthanc-stone
changeset 633:b0652595b62a
Merge
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Thu, 09 May 2019 14:38:27 +0200 |
parents | 500c3f70b6c2 (current diff) ea8322566596 (diff) |
children | 6a144a45b2d8 |
files | Samples/Sdl/CMakeLists.txt |
diffstat | 28 files changed, 1302 insertions(+), 122 deletions(-) [+] |
line wrap: on
line diff
--- a/Framework/Layers/DicomSeriesVolumeSlicer.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Layers/DicomSeriesVolumeSlicer.cpp Thu May 09 14:38:27 2019 +0200 @@ -36,17 +36,17 @@ { if (message.GetOrigin().GetSliceCount() > 0) { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } else { - EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } } void DicomSeriesVolumeSlicer::OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) { - EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } @@ -73,17 +73,17 @@ void DicomSeriesVolumeSlicer::OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) { // first notify that the pixel data of the frame is ready (targeted to, i.e: an image cache) - EmitMessage(FrameReadyMessage(*this, message.GetImage(), + BroadcastMessage(FrameReadyMessage(*this, message.GetImage(), message.GetEffectiveQuality(), message.GetSlice())); // then notify that the layer is ready for rendering RendererFactory factory(message); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, message.GetSlice().GetGeometry())); } void DicomSeriesVolumeSlicer::OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) { - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, message.GetSlice().GetGeometry())); }
--- a/Framework/Layers/DicomStructureSetSlicer.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Layers/DicomStructureSetSlicer.cpp Thu May 09 14:38:27 2019 +0200 @@ -164,7 +164,7 @@ if (loader_.HasStructureSet()) { RendererFactory factory(loader_.GetStructureSet(), viewportPlane); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane)); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, viewportPlane)); } } }
--- a/Framework/Layers/DicomStructureSetSlicer.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Layers/DicomStructureSetSlicer.h Thu May 09 14:38:27 2019 +0200 @@ -38,7 +38,7 @@ void OnStructureSetLoaded(const IVolumeLoader::ContentChangedMessage& message) { - EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); } public:
--- a/Framework/Messages/IMessage.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Messages/IMessage.h Thu May 09 14:38:27 2019 +0200 @@ -31,10 +31,10 @@ class IMessage : public boost::noncopyable { private: - int messageType_; + MessageType messageType_; protected: - IMessage(const int& messageType) : + IMessage(MessageType messageType) : messageType_(messageType) { } @@ -44,7 +44,7 @@ { } - virtual int GetType() const + virtual MessageType GetType() const { return messageType_; } @@ -53,7 +53,7 @@ // base class to derive from to implement your own messages // it handles the message type for you - template <int type> + template <MessageType type> class BaseMessage : public IMessage { public: @@ -63,7 +63,7 @@ }; BaseMessage() : - IMessage(static_cast<int>(Type)) + IMessage(static_cast<MessageType>(Type)) { } }; @@ -72,7 +72,7 @@ // simple message implementation when no payload is needed // sample usage: // typedef NoPayloadMessage<MessageType_VolumeSlicer_GeometryReady> GeometryReadyMessage; - template <int type> + template <MessageType type> class NoPayloadMessage : public BaseMessage<type> { public: @@ -85,7 +85,7 @@ // simple message implementation when no payload is needed but the origin is required // sample usage: // typedef OriginMessage<MessageType_SliceLoader_GeometryError, OrthancSlicesLoader> SliceGeometryErrorMessage; - template <int type, typename TOrigin> + template <MessageType type, typename TOrigin> class OriginMessage : public BaseMessage<type> { private:
--- a/Framework/Messages/IObservable.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Messages/IObservable.cpp Thu May 09 14:38:27 2019 +0200 @@ -84,7 +84,8 @@ } } - void IObservable::EmitMessage(const IMessage& message) + void IObservable::EmitMessageInternal(const IObserver* receiver, + const IMessage& message) { Callables::const_iterator found = callables_.find(message.GetType()); @@ -94,14 +95,33 @@ it = found->second.begin(); it != found->second.end(); ++it) { assert(*it != NULL); - if (broker_.IsActive(*(*it)->GetObserver())) + + const IObserver* observer = (*it)->GetObserver(); + if (broker_.IsActive(*observer)) { - (*it)->Apply(message); + if (receiver == NULL || // Are we broadcasting? + observer == receiver) // Not broadcasting, but this is the receiver + { + (*it)->Apply(message); + } } } } } + + void IObservable::BroadcastMessage(const IMessage& message) + { + EmitMessageInternal(NULL, message); + } + + + void IObservable::EmitMessage(const IObserver& observer, + const IMessage& message) + { + EmitMessageInternal(&observer, message); + } + void IObservable::RegisterForwarder(IMessageForwarder* forwarder) {
--- a/Framework/Messages/IObservable.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Messages/IObservable.h Thu May 09 14:38:27 2019 +0200 @@ -42,6 +42,9 @@ Callables callables_; Forwarders forwarders_; + void EmitMessageInternal(const IObserver* receiver, + const IMessage& message); + public: IObservable(MessageBroker& broker) : broker_(broker) @@ -60,7 +63,10 @@ void Unregister(IObserver* observer); - void EmitMessage(const IMessage& message); + void BroadcastMessage(const IMessage& message); + + void EmitMessage(const IObserver& observer, + const IMessage& message); // Takes ownsership void RegisterForwarder(IMessageForwarder* forwarder);
--- a/Framework/Messages/MessageForwarder.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Messages/MessageForwarder.cpp Thu May 09 14:38:27 2019 +0200 @@ -28,7 +28,7 @@ void IMessageForwarder::ForwardMessageInternal(const IMessage& message) { - emitter_.EmitMessage(message); + emitter_.BroadcastMessage(message); } void IMessageForwarder::RegisterForwarderInEmitter()
--- a/Framework/Radiography/RadiographyAlphaLayer.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Radiography/RadiographyAlphaLayer.cpp Thu May 09 14:38:27 2019 +0200 @@ -46,7 +46,7 @@ SetSize(image->GetWidth(), image->GetHeight()); alpha_ = raii; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyAlphaLayer::Render(Orthanc::ImageAccessor& buffer,
--- a/Framework/Radiography/RadiographyDicomLayer.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Radiography/RadiographyDicomLayer.cpp Thu May 09 14:38:27 2019 +0200 @@ -103,7 +103,7 @@ source_ = raii; ApplyConverter(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyDicomLayer::Render(Orthanc::ImageAccessor& buffer,
--- a/Framework/Radiography/RadiographyLayer.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Radiography/RadiographyLayer.cpp Thu May 09 14:38:27 2019 +0200 @@ -139,7 +139,7 @@ { prefferedPhotometricDisplayMode_ = prefferedPhotometricDisplayMode; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetCrop(unsigned int x, @@ -161,7 +161,7 @@ geometry_.SetCrop(x, y, width, height); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyLayer::SetGeometry(const Geometry& geometry) @@ -173,7 +173,7 @@ UpdateTransform(); } - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -201,7 +201,7 @@ geometry_.SetAngle(angle); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -220,7 +220,7 @@ height_ = height; UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -298,7 +298,7 @@ { geometry_.SetPan(x, y); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } @@ -307,7 +307,7 @@ { geometry_.SetPixelSpacing(x, y); UpdateTransform(); - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); }
--- a/Framework/Radiography/RadiographyMaskLayer.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Radiography/RadiographyMaskLayer.cpp Thu May 09 14:38:27 2019 +0200 @@ -63,7 +63,7 @@ corners_.push_back(corner); invalidated_ = true; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyMaskLayer::SetCorners(const std::vector<Orthanc::ImageProcessing::ImagePoint>& corners) @@ -71,7 +71,7 @@ corners_ = corners; invalidated_ = true; - EmitMessage(RadiographyLayer::LayerEditedMessage(*this)); + BroadcastMessage(RadiographyLayer::LayerEditedMessage(*this)); } void RadiographyMaskLayer::Render(Orthanc::ImageAccessor& buffer,
--- a/Framework/Radiography/RadiographyScene.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Radiography/RadiographyScene.cpp Thu May 09 14:38:27 2019 +0200 @@ -140,8 +140,8 @@ raii->SetIndex(index); layers_[index] = raii.release(); - EmitMessage(GeometryChangedMessage(*this, *layer)); - EmitMessage(ContentChangedMessage(*this, *layer)); + BroadcastMessage(GeometryChangedMessage(*this, *layer)); + BroadcastMessage(ContentChangedMessage(*this, *layer)); layer->RegisterObserverCallback(new Callable<RadiographyScene, RadiographyLayer::LayerEditedMessage>(*this, &RadiographyScene::OnLayerEdited)); return *layer; @@ -149,7 +149,7 @@ void RadiographyScene::OnLayerEdited(const RadiographyLayer::LayerEditedMessage& message) { - EmitMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); + BroadcastMessage(RadiographyScene::LayerEditedMessage(*this, message.GetOrigin())); } RadiographyScene::RadiographyScene(MessageBroker& broker) : @@ -266,7 +266,7 @@ windowingCenter_ = center; windowingWidth_ = width; - EmitMessage(RadiographyScene::WindowingChangedMessage(*this)); + BroadcastMessage(RadiographyScene::WindowingChangedMessage(*this)); } @@ -447,7 +447,7 @@ windowingWidth_ = w; } - EmitMessage(GeometryChangedMessage(*this, *(layer->second))); + BroadcastMessage(GeometryChangedMessage(*this, *(layer->second))); } } @@ -474,7 +474,7 @@ reader->ReadFromMemory(content); dynamic_cast<RadiographyDicomLayer*>(layer->second)->SetSourceImage(reader.release()); - EmitMessage(ContentChangedMessage(*this, *(layer->second))); + BroadcastMessage(ContentChangedMessage(*this, *(layer->second))); } }
--- a/Framework/Scene2D/FloatTextureSceneLayer.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Scene2D/FloatTextureSceneLayer.h Thu May 09 14:38:27 2019 +0200 @@ -33,6 +33,7 @@ float customWidth_; public: + // The pixel format must be "Float32" FloatTextureSceneLayer(const Orthanc::ImageAccessor& texture); void SetWindowing(ImageWindowing windowing);
--- a/Framework/SmartLoader.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/SmartLoader.cpp Thu May 09 14:38:27 2019 +0200 @@ -95,7 +95,7 @@ LOG(WARNING) << "ScheduleLayerCreation for CachedSlice (image is loaded): " << slice_->GetOrthancInstanceId(); RendererFactory factory(*this); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice_->GetGeometry())); } else { @@ -174,7 +174,7 @@ if (cachedSlice != NULL) { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*cachedSlice)); } } @@ -242,7 +242,7 @@ cachedSlices_[sliceKeyId] = boost::shared_ptr<CachedSlice>(cachedSlice); // re-emit original Layer message to observers - EmitMessage(message); + BroadcastMessage(message); } @@ -264,7 +264,7 @@ cachedSlices_[sliceKeyId] = cachedSlice; // re-emit original Layer message to observers - EmitMessage(message); + BroadcastMessage(message); } @@ -286,6 +286,6 @@ } // re-emit original Layer message to observers - EmitMessage(message); + BroadcastMessage(message); } }
--- a/Framework/StoneEnumerations.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/StoneEnumerations.cpp Thu May 09 14:38:27 2019 +0200 @@ -27,20 +27,18 @@ namespace OrthancStone { - bool StringToSopClassUid(SopClassUid& result, - const std::string& source) + SopClassUid StringToSopClassUid(const std::string& source) { std::string s = Orthanc::Toolbox::StripSpaces(source); if (s == "1.2.840.10008.5.1.4.1.1.481.2") { - result = SopClassUid_RTDose; - return true; + return SopClassUid_RTDose; } else { - //LOG(INFO) << "Unknown SOP class UID: " << source; - return false; + //LOG(INFO) << "Other SOP class UID: " << source; + return SopClassUid_Other; } }
--- a/Framework/StoneEnumerations.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/StoneEnumerations.h Thu May 09 14:38:27 2019 +0200 @@ -98,6 +98,7 @@ enum SopClassUid { + SopClassUid_Other, SopClassUid_RTDose }; @@ -192,8 +193,7 @@ }; - bool StringToSopClassUid(SopClassUid& result, - const std::string& source); + SopClassUid StringToSopClassUid(const std::string& source); void ComputeWindowing(float& targetCenter, float& targetWidth,
--- a/Framework/Toolbox/OrthancSlicesLoader.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Thu May 09 14:38:27 2019 +0200 @@ -177,7 +177,7 @@ { OrthancSlicesLoader::SliceImageReadyMessage msg (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality()); - EmitMessage(msg); + BroadcastMessage(msg); } @@ -185,7 +185,7 @@ { OrthancSlicesLoader::SliceImageErrorMessage msg (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality()); - EmitMessage(msg); + BroadcastMessage(msg); } @@ -210,18 +210,18 @@ if (ok) { LOG(INFO) << "Loaded a series with " << slices_.GetSliceCount() << " slice(s)"; - EmitMessage(SliceGeometryReadyMessage(*this)); + BroadcastMessage(SliceGeometryReadyMessage(*this)); } else { LOG(ERROR) << "This series is empty"; - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); } } void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message) { - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); state_ = State_Error; } @@ -296,7 +296,7 @@ else { LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId; - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); return; } } @@ -323,12 +323,12 @@ { LOG(INFO) << "Loaded instance geometry " << instanceId; slices_.AddSlice(slice.release()); - EmitMessage(SliceGeometryReadyMessage(*this)); + BroadcastMessage(SliceGeometryReadyMessage(*this)); } else { LOG(WARNING) << "Skipping invalid instance " << instanceId; - EmitMessage(SliceGeometryErrorMessage(*this)); + BroadcastMessage(SliceGeometryErrorMessage(*this)); } }
--- a/Framework/Toolbox/Slice.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Toolbox/Slice.cpp Thu May 09 14:38:27 2019 +0200 @@ -194,20 +194,16 @@ geometry_ = CoordinateSystem3D(position, orientation); bool ok = true; - SopClassUid tmp; - if (StringToSopClassUid(tmp, sopClassUid_)) + switch (StringToSopClassUid(sopClassUid_)) { - switch (tmp) - { - case SopClassUid_RTDose: - type_ = Type_OrthancRawFrame; - ok = ComputeRTDoseGeometry(dataset, frame); - break; + case SopClassUid_RTDose: + type_ = Type_OrthancRawFrame; + ok = ComputeRTDoseGeometry(dataset, frame); + break; - default: - break; - } + default: + break; } if (!ok)
--- a/Framework/Viewport/IViewport.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Viewport/IViewport.h Thu May 09 14:38:27 2019 +0200 @@ -89,7 +89,7 @@ // TODO Why should this be virtual? virtual void NotifyContentChanged() { - EmitMessage(ViewportChangedMessage(*this)); + BroadcastMessage(ViewportChangedMessage(*this)); } }; }
--- a/Framework/Volumes/StructureSetLoader.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Volumes/StructureSetLoader.cpp Thu May 09 14:38:27 2019 +0200 @@ -44,7 +44,7 @@ MessagingToolbox::ConvertDataset(slice, dataset); structureSet_->AddReferencedSlice(slice); - EmitMessage(ContentChangedMessage(*this)); + BroadcastMessage(ContentChangedMessage(*this)); } @@ -63,7 +63,7 @@ new Callable<StructureSetLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &StructureSetLoader::OnLookupCompleted)); } - EmitMessage(GeometryReadyMessage(*this)); + BroadcastMessage(GeometryReadyMessage(*this)); }
--- a/Framework/Widgets/SliceViewerWidget.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Framework/Widgets/SliceViewerWidget.cpp Thu May 09 14:38:27 2019 +0200 @@ -527,7 +527,7 @@ InvalidateAllLayers(); // TODO Removing this line avoid loading twice the image in WASM } - EmitMessage(DisplayedSliceMessage(*this, displayedSlice)); + BroadcastMessage(DisplayedSliceMessage(*this, displayedSlice)); } @@ -541,7 +541,7 @@ changedLayers_[i] = true; //layers_[i]->ScheduleLayerCreation(plane_); } - EmitMessage(GeometryChangedMessage(*this)); + BroadcastMessage(GeometryChangedMessage(*this)); } @@ -579,7 +579,7 @@ InvalidateLayer(index); } - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } @@ -594,7 +594,7 @@ } } - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } @@ -607,7 +607,7 @@ UpdateLayer(index, message.CreateRenderer(), message.GetSlice()); } - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } @@ -621,7 +621,7 @@ // TODO //UpdateLayer(index, new SliceOutlineRenderer(slice), slice); - EmitMessage(SliceViewerWidget::ContentChangedMessage(*this)); + BroadcastMessage(SliceViewerWidget::ContentChangedMessage(*this)); } }
--- a/Framework/dev.h Thu May 09 10:41:31 2019 +0200 +++ b/Framework/dev.h Thu May 09 14:38:27 2019 +0200 @@ -113,7 +113,7 @@ if (loader_.GetSliceCount() == 0) { LOG(ERROR) << "Empty volume image"; - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); return; } @@ -121,7 +121,7 @@ { if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i))) { - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); return; } } @@ -145,7 +145,7 @@ 0.001 /* this is expressed in mm */)) { LOG(ERROR) << "The distance between successive slices is not constant in a volume image"; - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); return; } } @@ -172,7 +172,7 @@ // TODO Check the DicomFrameConverter are constant - EmitMessage(ISlicedVolume::GeometryReadyMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this)); } @@ -181,7 +181,7 @@ assert(&message.GetOrigin() == &loader_); LOG(ERROR) << "Unable to download a volume image"; - EmitMessage(ISlicedVolume::GeometryErrorMessage(*this)); + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); } @@ -194,12 +194,12 @@ Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); } - EmitMessage(ISlicedVolume::SliceContentChangedMessage + BroadcastMessage(ISlicedVolume::SliceContentChangedMessage (*this, message.GetSliceIndex(), message.GetSlice())); if (pendingSlices_ == 1) { - EmitMessage(ISlicedVolume::VolumeReadyMessage(*this)); + BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this)); pendingSlices_ = 0; } else if (pendingSlices_ > 1) @@ -540,21 +540,21 @@ coronalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Coronal)); sagittalGeometry_.reset(new VolumeImageGeometry(volume_, VolumeProjection_Sagittal)); - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) { assert(&message.GetOrigin() == &volume_); - EmitMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); } void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) { assert(&message.GetOrigin() == &volume_); - EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); } void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message) @@ -564,7 +564,7 @@ //IVolumeSlicer::OnSliceContentChange(slice); // TODO Improve this? - EmitMessage(IVolumeSlicer::ContentChangedMessage(*this)); + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); } const VolumeImageGeometry& GetProjectionGeometry(VolumeProjection projection) @@ -697,14 +697,14 @@ RendererFactory factory(*frame, *slice, isFullQuality); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); return; } } // Error CoordinateSystem3D slice; - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); } }; @@ -906,7 +906,7 @@ IVolumeSlicer(broker), otherPlane_(otherPlane) { - EmitMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); } virtual bool GetExtent(std::vector<Vector>& points, @@ -929,7 +929,7 @@ viewportSlice.GetOrigin(), viewportSlice.GetNormal())) { // The two slice are parallel, don't try and display the intersection - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); } else { @@ -945,12 +945,12 @@ extent.GetX2(), extent.GetY2())) { RendererFactory factory(x1, y1, x2, y2, slice); - EmitMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); } else { // Error: Parallel slices - EmitMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); } } }
--- a/Samples/Sdl/CMakeLists.txt Thu May 09 10:41:31 2019 +0200 +++ b/Samples/Sdl/CMakeLists.txt Thu May 09 14:38:27 2019 +0200 @@ -83,3 +83,8 @@ target_link_libraries(TrackerSample OrthancStone) +add_executable(Loader + Loader.cpp + ) + +target_link_libraries(Loader OrthancStone)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/Sdl/Loader.cpp Thu May 09 14:38:27 2019 +0200 @@ -0,0 +1,1151 @@ +/** + * 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/>. + **/ + +// From Stone +#include "../../Framework/Messages/ICallable.h" +#include "../../Framework/Messages/IMessage.h" +#include "../../Framework/Messages/IObservable.h" +#include "../../Framework/Messages/MessageBroker.h" +#include "../../Framework/StoneInitialization.h" +#include "../../Framework/Toolbox/GeometryToolbox.h" +#include "../../Framework/Volumes/ImageBuffer3D.h" + +// From Orthanc framework +#include <Core/DicomFormat/DicomArray.h> +#include <Core/DicomFormat/DicomImageInformation.h> +#include <Core/HttpClient.h> +#include <Core/IDynamicObject.h> +#include <Core/Images/Image.h> +#include <Core/Images/ImageProcessing.h> +#include <Core/Images/PngWriter.h> +#include <Core/Logging.h> +#include <Core/MultiThreading/SharedMessageQueue.h> +#include <Core/OrthancException.h> +#include <Core/Toolbox.h> + +#include <json/reader.h> +#include <json/value.h> +#include <json/writer.h> + +#include <list> +#include <stdio.h> + + + +namespace Refactoring +{ + class IOracleCommand : public boost::noncopyable + { + public: + enum Type + { + Type_OrthancApi + }; + + virtual ~IOracleCommand() + { + } + + virtual Type GetType() const = 0; + }; + + + class IMessageEmitter : public boost::noncopyable + { + public: + virtual ~IMessageEmitter() + { + } + + virtual void EmitMessage(const OrthancStone::IObserver& observer, + const OrthancStone::IMessage& message) = 0; + }; + + + class IOracle : public boost::noncopyable + { + public: + virtual ~IOracle() + { + } + + virtual void Schedule(const OrthancStone::IObserver& receiver, + IOracleCommand* command) = 0; // Takes ownership + }; + + + + + class OracleCommandWithPayload : public IOracleCommand + { + private: + std::auto_ptr<Orthanc::IDynamicObject> payload_; + + public: + void SetPayload(Orthanc::IDynamicObject* payload) + { + if (payload == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + payload_.reset(payload); + } + } + + bool HasPayload() const + { + return (payload_.get() != NULL); + } + + const Orthanc::IDynamicObject& GetPayload() const + { + if (HasPayload()) + { + return *payload_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + }; + + + + typedef std::map<std::string, std::string> HttpHeaders; + + class OrthancApiOracleCommand : public OracleCommandWithPayload + { + public: + class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess, // TODO + OrthancApiOracleCommand> + { + private: + HttpHeaders headers_; + std::string answer_; + + public: + SuccessMessage(const OrthancApiOracleCommand& command, + const HttpHeaders& answerHeaders, + std::string& answer /* will be swapped to avoid a memcpy() */) : + OriginMessage(command), + headers_(answerHeaders), + answer_(answer) + { + } + + const std::string& GetAnswer() const + { + return answer_; + } + + void ParseJsonBody(Json::Value& target) const + { + Json::Reader reader; + if (!reader.parse(answer_, target)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + } + + const HttpHeaders& GetAnswerHeaders() const + { + return headers_; + } + }; + + + class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError, // TODO + OrthancApiOracleCommand> + { + private: + Orthanc::HttpStatus status_; + + public: + FailureMessage(const OrthancApiOracleCommand& command, + Orthanc::HttpStatus status) : + OriginMessage(command), + status_(status) + { + } + + Orthanc::HttpStatus GetHttpStatus() const + { + return status_; + } + }; + + + private: + Orthanc::HttpMethod method_; + std::string uri_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + + std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; + std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> > failureCallback_; + + public: + OrthancApiOracleCommand() : + method_(Orthanc::HttpMethod_Get), + uri_("/"), + timeout_(10) + { + } + + virtual Type GetType() const + { + return Type_OrthancApi; + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + void SetUri(const std::string& uri) + { + uri_ = uri; + } + + void SetBody(const std::string& body) + { + body_ = body; + } + + void SetBody(const Json::Value& json) + { + Json::FastWriter writer; + body_ = writer.write(json); + } + + void SetHttpHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + Orthanc::HttpMethod GetMethod() const + { + return method_; + } + + const std::string& GetUri() const + { + return uri_; + } + + const std::string& GetBody() const + { + if (method_ == Orthanc::HttpMethod_Post || + method_ == Orthanc::HttpMethod_Put) + { + return body_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int seconds) + { + timeout_ = seconds; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + }; + + + + class NativeOracle : public IOracle + { + private: + class Item : public Orthanc::IDynamicObject + { + private: + const OrthancStone::IObserver& receiver_; + std::auto_ptr<IOracleCommand> command_; + + public: + Item(const OrthancStone::IObserver& receiver, + IOracleCommand* command) : + receiver_(receiver), + command_(command) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const OrthancStone::IObserver& GetReceiver() const + { + return receiver_; + } + + const IOracleCommand& GetCommand() const + { + assert(command_.get() != NULL); + return *command_; + } + }; + + + enum State + { + State_Setup, + State_Running, + State_Stopped + }; + + + IMessageEmitter& emitter_; + Orthanc::WebServiceParameters orthanc_; + Orthanc::SharedMessageQueue queue_; + State state_; + boost::mutex mutex_; + std::vector<boost::thread*> workers_; + + + void Execute(const OrthancStone::IObserver& receiver, + const OrthancApiOracleCommand& command) + { + Orthanc::HttpClient client(orthanc_, command.GetUri()); + client.SetMethod(command.GetMethod()); + client.SetTimeout(command.GetTimeout()); + + if (command.GetMethod() == Orthanc::HttpMethod_Post || + command.GetMethod() == Orthanc::HttpMethod_Put) + { + client.SetBody(command.GetBody()); + } + + { + const HttpHeaders& headers = command.GetHttpHeaders(); + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) + { + client.AddHeader(it->first, it->second); + } + } + + std::string answer; + HttpHeaders answerHeaders; + + bool success; + try + { + success = client.Apply(answer, answerHeaders); + } + catch (Orthanc::OrthancException& e) + { + success = false; + } + + if (success) + { + OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer); + emitter_.EmitMessage(receiver, message); + } + else + { + OrthancApiOracleCommand::FailureMessage message(command, client.GetLastStatus()); + emitter_.EmitMessage(receiver, message); + } + } + + + + void Step() + { + std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100)); + + if (object.get() != NULL) + { + const Item& item = dynamic_cast<Item&>(*object); + + try + { + switch (item.GetCommand().GetType()) + { + case IOracleCommand::Type_OrthancApi: + Execute(item.GetReceiver(), + dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Exception within the oracle: " << e.What(); + } + catch (...) + { + LOG(ERROR) << "Native exception within the oracle"; + } + } + } + + + static void Worker(NativeOracle* that) + { + assert(that != NULL); + + for (;;) + { + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->state_ != State_Running) + { + return; + } + } + + that->Step(); + } + } + + + void StopInternal() + { + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ == State_Setup || + state_ == State_Stopped) + { + return; + } + else + { + state_ = State_Stopped; + } + } + + for (size_t i = 0; i < workers_.size(); i++) + { + if (workers_[i] != NULL) + { + if (workers_[i]->joinable()) + { + workers_[i]->join(); + } + + delete workers_[i]; + } + } + } + + + public: + NativeOracle(IMessageEmitter& emitter) : + emitter_(emitter), + state_(State_Setup), + workers_(4) + { + } + + virtual ~NativeOracle() + { + StopInternal(); + } + + void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + orthanc_ = orthanc; + } + } + + void SetWorkersCount(unsigned int count) + { + boost::mutex::scoped_lock lock(mutex_); + + if (count <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + workers_.resize(count); + } + } + + void Start() + { + boost::mutex::scoped_lock lock(mutex_); + + if (state_ != State_Setup) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + state_ = State_Running; + + for (unsigned int i = 0; i < workers_.size(); i++) + { + workers_[i] = new boost::thread(Worker, this); + } + } + } + + void Stop() + { + StopInternal(); + } + + virtual void Schedule(const OrthancStone::IObserver& receiver, + IOracleCommand* command) + { + queue_.Enqueue(new Item(receiver, command)); + } + }; + + + + class NativeApplicationContext : public IMessageEmitter + { + private: + boost::shared_mutex mutex_; + OrthancStone::MessageBroker broker_; + OrthancStone::IObservable oracleObservable_; + + public: + NativeApplicationContext() : + oracleObservable_(broker_) + { + } + + + virtual void EmitMessage(const OrthancStone::IObserver& observer, + const OrthancStone::IMessage& message) + { + boost::unique_lock<boost::shared_mutex> lock(mutex_); + oracleObservable_.EmitMessage(observer, message); + } + + + class ReaderLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::shared_lock<boost::shared_mutex> lock_; + + public: + ReaderLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + }; + + + class WriterLock : public boost::noncopyable + { + private: + NativeApplicationContext& that_; + boost::unique_lock<boost::shared_mutex> lock_; + + public: + WriterLock(NativeApplicationContext& that) : + that_(that), + lock_(that.mutex_) + { + } + + OrthancStone::MessageBroker& GetBroker() + { + return that_.broker_; + } + + OrthancStone::IObservable& GetOracleObservable() + { + return that_.oracleObservable_; + } + }; + }; + + + + class DicomInstanceParameters : public boost::noncopyable + { + private: + Orthanc::DicomImageInformation imageInformation_; + OrthancStone::SopClassUid sopClassUid_; + double thickness_; + double pixelSpacingX_; + double pixelSpacingY_; + OrthancStone::CoordinateSystem3D geometry_; + OrthancStone::Vector frameOffsets_; + bool isColor_; + bool hasRescale_; + double rescaleOffset_; + double rescaleSlope_; + bool hasDefaultWindowing_; + float defaultWindowingCenter_; + float defaultWindowingWidth_; + Orthanc::PixelFormat expectedPixelFormat_; + + void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) + { + // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html + + { + std::string increment; + + if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) + { + Orthanc::Toolbox::ToUpperCase(increment); + if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag + { + LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; + return; + } + } + } + + if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || + frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) + { + LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; + frameOffsets_.clear(); + } + else + { + if (frameOffsets_.size() >= 2) + { + thickness_ = frameOffsets_[1] - frameOffsets_[0]; + + if (thickness_ < 0) + { + thickness_ = -thickness_; + } + } + } + } + + public: + DicomInstanceParameters(const Orthanc::DicomMap& dicom) : + imageInformation_(dicom) + { + if (imageInformation_.GetNumberOfFrames() <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + std::string s; + if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + sopClassUid_ = OrthancStone::StringToSopClassUid(s); + } + + if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) + { + thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); + } + + OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); + + std::string position, orientation; + if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && + dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) + { + geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); + } + + if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) + { + ComputeDoseOffsets(dicom); + } + + isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && + imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); + + double doseGridScaling; + + if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + { + hasRescale_ = true; + } + else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + hasRescale_ = true; + rescaleOffset_ = 0; + rescaleSlope_ = doseGridScaling; + } + else + { + hasRescale_ = false; + } + + OrthancStone::Vector c, w; + if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && + OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && + c.size() > 0 && + w.size() > 0) + { + hasDefaultWindowing_ = true; + defaultWindowingCenter_ = static_cast<float>(c[0]); + defaultWindowingWidth_ = static_cast<float>(w[0]); + } + else + { + hasDefaultWindowing_ = false; + } + + if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) + { + switch (imageInformation_.GetBitsStored()) + { + case 16: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + break; + + case 32: + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + else if (isColor_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; + } + else if (imageInformation_.IsSigned()) + { + expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; + } + else + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } + } + + const Orthanc::DicomImageInformation& GetImageInformation() const + { + return imageInformation_; + } + + OrthancStone::SopClassUid GetSopClassUid() const + { + return sopClassUid_; + } + + double GetThickness() const + { + return thickness_; + } + + double GetPixelSpacingX() const + { + return pixelSpacingX_; + } + + double GetPixelSpacingY() const + { + return pixelSpacingY_; + } + + const OrthancStone::CoordinateSystem3D& GetGeometry() const + { + return geometry_; + } + + OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const + { + if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) + { + if (frame >= frameOffsets_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + return OrthancStone::CoordinateSystem3D( + geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), + geometry_.GetAxisX(), + geometry_.GetAxisY()); + } + } + + bool FrameContainsPlane(unsigned int frame, + const OrthancStone::CoordinateSystem3D& plane) const + { + if (frame >= imageInformation_.GetNumberOfFrames()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + OrthancStone::CoordinateSystem3D tmp = geometry_; + + if (frame != 0) + { + tmp = GetFrameGeometry(frame); + } + + bool opposite; // Ignored + return (OrthancStone::GeometryToolbox::IsParallelOrOpposite( + opposite, tmp.GetNormal(), plane.GetNormal()) && + OrthancStone::LinearAlgebra::IsNear( + tmp.ProjectAlongNormal(tmp.GetOrigin()), + tmp.ProjectAlongNormal(plane.GetOrigin()), + thickness_ / 2.0)); + } + + bool IsColor() const + { + return isColor_; + } + + bool HasRescale() const + { + return hasRescale_; + } + + double GetRescaleOffset() const + { + if (hasRescale_) + { + return rescaleOffset_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + double GetRescaleSlope() const + { + if (hasRescale_) + { + return rescaleSlope_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool HasDefaultWindowing() const + { + return hasDefaultWindowing_; + } + + float GetDefaultWindowingCenter() const + { + if (hasDefaultWindowing_) + { + return defaultWindowingCenter_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + float GetDefaultWindowingWidth() const + { + if (hasDefaultWindowing_) + { + return defaultWindowingWidth_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return expectedPixelFormat_; + } + }; + + + class AxialVolumeOrthancLoader : public OrthancStone::IObserver + { + private: + class MessageHandler : public Orthanc::IDynamicObject + { + public: + virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0; + }; + + void Handle(const OrthancApiOracleCommand::SuccessMessage& message) + { + dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message); + } + + + class LoadSeriesGeometryHandler : public MessageHandler + { + private: + AxialVolumeOrthancLoader& that_; + + public: + LoadSeriesGeometryHandler(AxialVolumeOrthancLoader& that) : + that_(that) + { + } + + virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const + { + Json::Value value; + message.ParseJsonBody(value); + + if (value.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + Json::Value::Members instances = value.getMemberNames(); + + for (size_t i = 0; i < instances.size(); i++) + { + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(value[instances[i]]); + + DicomInstanceParameters instance(dicom); + } + } + }; + + + class LoadInstanceGeometryHandler : public MessageHandler + { + private: + AxialVolumeOrthancLoader& that_; + + public: + LoadInstanceGeometryHandler(AxialVolumeOrthancLoader& that) : + that_(that) + { + } + + virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const + { + Json::Value value; + message.ParseJsonBody(value); + + if (value.type() != Json::objectValue) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } + + Orthanc::DicomMap dicom; + dicom.FromDicomAsJson(value); + + DicomInstanceParameters instance(dicom); + } + }; + + + bool active_; + std::auto_ptr<OrthancStone::ImageBuffer3D> image_; + + + public: + AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) : + IObserver(oracle.GetBroker()), + active_(false) + { + oracle.RegisterObserverCallback( + new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage> + (*this, &AxialVolumeOrthancLoader::Handle)); + } + + void LoadSeries(IOracle& oracle, + const std::string& seriesId) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); + command->SetUri("/series/" + seriesId + "/instances-tags"); + command->SetPayload(new LoadSeriesGeometryHandler(*this)); + + oracle.Schedule(*this, command.release()); + } + + void LoadInstance(IOracle& oracle, + const std::string& instanceId) + { + if (active_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + active_ = true; + + // Tag "3004-000c" is "Grid Frame Offset Vector", which is + // mandatory to read RT DOSE, but is too long to be returned by default + + // TODO => Should be part of a second call if needed + + std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); + command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c"); + command->SetPayload(new LoadInstanceGeometryHandler(*this)); + + oracle.Schedule(*this, command.release()); + } + }; + +} + + + +class Toto : public OrthancStone::IObserver +{ +private: + void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message) + { + Json::Value v; + message.ParseJsonBody(v); + + printf("ICI [%s]\n", v.toStyledString().c_str()); + } + + void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message) + { + printf("ERROR %d\n", message.GetHttpStatus()); + } + +public: + Toto(OrthancStone::IObservable& oracle) : + IObserver(oracle.GetBroker()) + { + oracle.RegisterObserverCallback + (new OrthancStone::Callable + <Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle)); + } +}; + + +void Run(Refactoring::NativeApplicationContext& context) +{ + std::auto_ptr<Toto> toto; + std::auto_ptr<Refactoring::AxialVolumeOrthancLoader> loader1, loader2; + + { + Refactoring::NativeApplicationContext::WriterLock lock(context); + toto.reset(new Toto(lock.GetOracleObservable())); + loader1.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable())); + loader2.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable())); + } + + Refactoring::NativeOracle oracle(context); + + { + Orthanc::WebServiceParameters p; + //p.SetUrl("http://localhost:8043/"); + p.SetCredentials("orthanc", "orthanc"); + oracle.SetOrthancParameters(p); + } + + oracle.Start(); + + { + Json::Value v = Json::objectValue; + v["Level"] = "Series"; + v["Query"] = Json::objectValue; + + std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand); + command->SetMethod(Orthanc::HttpMethod_Post); + command->SetUri("/tools/find"); + command->SetBody(v); + + oracle.Schedule(*toto, command.release()); + } + + // 2017-11-17-Anonymized + loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT + loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE + + boost::this_thread::sleep(boost::posix_time::seconds(1)); + + oracle.Stop(); +} + + + +/** + * IMPORTANT: The full arguments to "main()" are needed for SDL on + * Windows. Otherwise, one gets the linking error "undefined reference + * to `SDL_main'". https://wiki.libsdl.org/FAQWindows + **/ +int main(int argc, char* argv[]) +{ + OrthancStone::StoneInitialize(); + Orthanc::Logging::EnableInfoLevel(true); + + try + { + Refactoring::NativeApplicationContext context; + Run(context); + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "EXCEPTION: " << e.What(); + } + + OrthancStone::StoneFinalize(); + + return 0; +}
--- a/Samples/WebAssembly/BasicScene.cpp Thu May 09 10:41:31 2019 +0200 +++ b/Samples/WebAssembly/BasicScene.cpp Thu May 09 14:38:27 2019 +0200 @@ -343,10 +343,16 @@ void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas) { - //emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent); - 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); + if (0) + { + emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent); + } + else + { + 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); + } }
--- a/Samples/WebAssembly/CMakeLists.txt Thu May 09 10:41:31 2019 +0200 +++ b/Samples/WebAssembly/CMakeLists.txt Thu May 09 14:38:27 2019 +0200 @@ -1,10 +1,3 @@ - -# 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 - - cmake_minimum_required(VERSION 2.8.3)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/WebAssembly/NOTES.txt Thu May 09 14:38:27 2019 +0200 @@ -0,0 +1,4 @@ +$ 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
--- a/UnitTestsSources/TestMessageBroker.cpp Thu May 09 10:41:31 2019 +0200 +++ b/UnitTestsSources/TestMessageBroker.cpp Thu May 09 14:38:27 2019 +0200 @@ -46,7 +46,7 @@ class MyObservable : public IObservable { public: - struct MyCustomMessage: public BaseMessage<CustomMessageType_Completed> + struct MyCustomMessage: public BaseMessage<(MessageType) CustomMessageType_Completed> { int payload_; @@ -160,18 +160,18 @@ observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(20, testCounter); // Unregister the observer; make sure it's not called anymore observable.Unregister(&observer); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } @@ -186,12 +186,12 @@ intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(20, testCounter); } @@ -205,7 +205,7 @@ observable.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(*observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // delete the observer and check that the callback is not called anymore @@ -213,7 +213,7 @@ // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } @@ -228,12 +228,12 @@ intermediate->RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); delete intermediate; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(12, testCounter); } @@ -248,12 +248,12 @@ intermediate.RegisterObserverCallback(new Callable<MyObserver, MyObservable::MyCustomMessage>(observer, &MyObserver::HandleCompletedMessage)); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(12, testCounter); // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(20, testCounter); } @@ -324,7 +324,7 @@ observable.RegisterObserverCallback(new LambdaCallable<MyObservable::MyCustomMessage>(*observer, [&](const MyObservable::MyCustomMessage& message) {testCounter += 2 * message.payload_;})); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(24, testCounter); // delete the observer and check that the callback is not called anymore @@ -332,7 +332,7 @@ // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); } @@ -362,7 +362,7 @@ MyObserverWithLambda* observer = new MyObserverWithLambda(broker, 3, observable); testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(12)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(12)); ASSERT_EQ(36, testCounter); // delete the observer and check that the callback is not called anymore @@ -370,7 +370,7 @@ // the connection is permanent; if we emit the same message again, the observer will be notified again testCounter = 0; - observable.EmitMessage(MyObservable::MyCustomMessage(20)); + observable.BroadcastMessage(MyObservable::MyCustomMessage(20)); ASSERT_EQ(0, testCounter); }