# HG changeset patch # User am@osimis.io # Date 1535028304 -7200 # Node ID 2d64f4d39610e456a7a8067efc2052b40310954b # Parent 0dfa83535cd795b2bef49bad1cbea5394fa4ceb2 backup (work in progress) diff -r 0dfa83535cd7 -r 2d64f4d39610 Applications/Samples/SimpleViewerApplication.h --- a/Applications/Samples/SimpleViewerApplication.h Wed Aug 22 15:22:33 2018 +0200 +++ b/Applications/Samples/SimpleViewerApplication.h Thu Aug 23 14:45:04 2018 +0200 @@ -122,82 +122,21 @@ }; - // void OffsetSlice(int offset) - // { - // if (source_ != NULL) - // { - // int slice = static_cast(slice_) + offset; - - // if (slice < 0) - // { - // slice = 0; - // } - - // if (slice >= static_cast(source_->GetSliceCount())) - // { - // slice = source_->GetSliceCount() - 1; - // } - - // if (slice != static_cast(slice_)) - // { - // SetSlice(slice); - // } - // } - // } - - - // void SetSlice(size_t index) - // { - // if (source_ != NULL && - // index < source_->GetSliceCount()) - // { - // slice_ = index; - - //#if 1 - // widget_->SetSlice(source_->GetSlice(slice_).GetGeometry()); - //#else - // // TEST for scene extents - Rotate the axes - // double a = 15.0 / 180.0 * M_PI; - - //#if 1 - // Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); - // Vector y; GeometryToolbox::AssignVector(y, -sin(a), cos(a), 0); - //#else - // // Flip the normal - // Vector x; GeometryToolbox::AssignVector(x, cos(a), sin(a), 0); - // Vector y; GeometryToolbox::AssignVector(y, sin(a), -cos(a), 0); - //#endif - - // SliceGeometry s(source_->GetSlice(slice_).GetGeometry().GetOrigin(), x, y); - // widget_->SetSlice(s); - //#endif - // } - // } - - - virtual void HandleMessage(IObservable& from, const IMessage& message) { - switch (message.GetType()) { - case MessageType_Widget_GeometryChanged: - dynamic_cast(from).SetDefaultView(); - break; - default: - VLOG("unhandled message type" << message.GetType()); - } - } - std::unique_ptr interactor_; LayoutWidget* mainLayout_; LayoutWidget* thumbnailsLayout_; LayerWidget* mainViewport_; std::vector thumbnails_; std::vector instances_; + unsigned int currentInstanceIndex_; - OrthancStone::WidgetViewport* wasmViewport1_; - OrthancStone::WidgetViewport* wasmViewport2_; + OrthancStone::WidgetViewport* wasmViewport1_; + OrthancStone::WidgetViewport* wasmViewport2_; IStatusBar* statusBar_; unsigned int slice_; std::unique_ptr smartLoader_; + std::unique_ptr orthancApiClient_; public: SimpleViewerApplication(MessageBroker& broker) : @@ -210,6 +149,10 @@ { DeclareIgnoredMessage(MessageType_Widget_ContentChanged); DeclareHandledMessage(MessageType_Widget_GeometryChanged); + + DeclareHandledMessage(MessageType_OrthancApi_GetStudyIds_Ready); + DeclareHandledMessage(MessageType_OrthancApi_GetStudy_Ready); + DeclareHandledMessage(MessageType_OrthancApi_GetSeries_Ready); } virtual void Finalize() {} @@ -221,10 +164,8 @@ generic.add_options() // ("study", boost::program_options::value(), // "Orthanc ID of the study") - ("instance1", boost::program_options::value(), - "Orthanc ID of the instances") - ("instance2", boost::program_options::value(), - "Orthanc ID of the instances") + ("studyId", boost::program_options::value(), + "Orthanc ID of the study") ; options.add(generic); @@ -238,58 +179,123 @@ context_ = context; statusBar_ = &statusBar; + + {// initialize viewports and layout + mainLayout_ = new LayoutWidget(); + mainLayout_->SetPadding(10); + mainLayout_->SetBackgroundCleared(true); + mainLayout_->SetBackgroundColor(0, 0, 0); + mainLayout_->SetHorizontal(); + + thumbnailsLayout_ = new LayoutWidget(); + thumbnailsLayout_->SetPadding(10); + thumbnailsLayout_->SetBackgroundCleared(true); + thumbnailsLayout_->SetBackgroundColor(50, 50, 50); + thumbnailsLayout_->SetVertical(); + + mainViewport_ = new LayerWidget(broker_); + mainViewport_->RegisterObserver(*this); + + // hierarchy + mainLayout_->AddWidget(thumbnailsLayout_); + mainLayout_->AddWidget(mainViewport_); + + // sources + smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService())); + smartLoader_->SetImageQuality(SliceImageQuality_FullPam); + +// mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0)); +// thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0)); +// thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0)); + + mainLayout_->SetTransmitMouseOver(true); + interactor_.reset(new Interactor(*this)); + mainViewport_->SetInteractor(*interactor_); + } + statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport"); - if (parameters.count("instance1") < 1) + orthancApiClient_.reset(new OrthancApiClient(broker_, context_->GetWebService())); + + if (parameters.count("studyId") < 1) { - LOG(ERROR) << "The instance ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc"; + orthancApiClient_->ScheduleGetStudyIds(*this); } - if (parameters.count("instance2") < 1) + else { - LOG(ERROR) << "The instance ID is missing"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + SelectStudy(parameters["studyId"].as()); } - instances_.push_back(parameters["instance1"].as()); - instances_.push_back(parameters["instance2"].as()); + } - mainLayout_ = new LayoutWidget(); - mainLayout_->SetPadding(10); - mainLayout_->SetBackgroundCleared(true); - mainLayout_->SetBackgroundColor(0, 0, 0); - mainLayout_->SetHorizontal(); - - thumbnailsLayout_ = new LayoutWidget(); - thumbnailsLayout_->SetPadding(10); - thumbnailsLayout_->SetBackgroundCleared(true); - thumbnailsLayout_->SetBackgroundColor(50, 50, 50); - thumbnailsLayout_->SetVertical(); + void OnStudyListReceived(const Json::Value& response) + { + if (response.isArray() && response.size() > 1) + { + SelectStudy(response[0].asString()); + } + } + void OnStudyReceived(const Json::Value& response) + { + if (response.isObject() && response["Series"].isArray()) + { + for (size_t i=0; i < response["Series"].size(); i++) + { + orthancApiClient_->ScheduleGetSeries(*this, response["Series"][(int)i].asString()); + } + } + } - mainViewport_ = new LayerWidget(broker_); - thumbnails_.push_back(new LayerWidget(broker_)); - thumbnails_.push_back(new LayerWidget(broker_)); - mainViewport_->RegisterObserver(*this); - thumbnails_[0]->RegisterObserver(*this); - thumbnails_[1]->RegisterObserver(*this); + void OnSeriesReceived(const Json::Value& response) + { + if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0) + { + LoadThumbnailForSeries(response["ID"].asString(), response["Instances"][0].asString()); + } + //TODO: create layout and start loading frames + //mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0)); + //thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0)); + //thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0)); + } + + void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId) + { + LayerWidget* thumbnailWidget = new LayerWidget(broker_); + thumbnailWidget->RegisterObserver(*this); + thumbnailWidget->AddLayer(smartLoader_->GetFrame(instanceId, 0)); + thumbnailsLayout_->AddWidget(thumbnailWidget); + thumbnails_.push_back(thumbnailWidget); + } - // hierarchy - mainLayout_->AddWidget(thumbnailsLayout_); - mainLayout_->AddWidget(mainViewport_); - thumbnailsLayout_->AddWidget(thumbnails_[0]); - thumbnailsLayout_->AddWidget(thumbnails_[1]); + void SelectStudy(const std::string& studyId) + { + orthancApiClient_->ScheduleGetStudy(*this, studyId); + } + + void LoadSeries(const std::vector& seriesIds) + { +// instances_.push_back(parameters["studyId"].as()); +// instances_.push_back(parameters["instance2"].as()); + } - // sources - smartLoader_.reset(new SmartLoader(broker_, context_->GetWebService())); - smartLoader_->SetImageQuality(SliceImageQuality_FullPam); - - mainViewport_->AddLayer(smartLoader_->GetFrame(instances_[currentInstanceIndex_], 0)); - thumbnails_[0]->AddLayer(smartLoader_->GetFrame(instances_[0], 0)); - thumbnails_[1]->AddLayer(smartLoader_->GetFrame(instances_[1], 0)); - - mainLayout_->SetTransmitMouseOver(true); - interactor_.reset(new Interactor(*this)); - mainViewport_->SetInteractor(*interactor_); + virtual void HandleMessage(IObservable& from, const IMessage& message) { + switch (message.GetType()) { + case MessageType_Widget_GeometryChanged: + dynamic_cast(from).SetDefaultView(); + break; + case MessageType_OrthancApi_GetStudyIds_Ready: + OnStudyListReceived(dynamic_cast(message).response_); + break; + case MessageType_OrthancApi_GetSeries_Ready: + OnSeriesReceived(dynamic_cast(message).response_); + break; + case MessageType_OrthancApi_GetStudy_Ready: + OnStudyReceived(dynamic_cast(message).response_); + break; + default: + VLOG("unhandled message type" << message.GetType()); + } } #if ORTHANC_ENABLE_SDL==0 diff -r 0dfa83535cd7 -r 2d64f4d39610 Framework/Messages/MessageType.h --- a/Framework/Messages/MessageType.h Wed Aug 22 15:22:33 2018 +0200 +++ b/Framework/Messages/MessageType.h Thu Aug 23 14:45:04 2018 +0200 @@ -41,6 +41,12 @@ MessageType_HttpRequestSuccess, MessageType_HttpRequestError, + MessageType_OrthancApi_InternalGetJsonResponseReady, + MessageType_OrthancApi_InternalGetJsonResponseError, + + MessageType_OrthancApi_GetStudyIds_Ready, + MessageType_OrthancApi_GetStudy_Ready, + MessageType_OrthancApi_GetSeries_Ready, // used in unit tests only MessageType_Test1, diff -r 0dfa83535cd7 -r 2d64f4d39610 Framework/SmartLoader.cpp --- a/Framework/SmartLoader.cpp Wed Aug 22 15:22:33 2018 +0200 +++ b/Framework/SmartLoader.cpp Thu Aug 23 14:45:04 2018 +0200 @@ -29,13 +29,17 @@ IObservable(broker), IObserver(broker), imageQuality_(SliceImageQuality_FullPam), - webService_(webService) + webService_(webService), + orthancApiClient_(broker, webService) { DeclareHandledMessage(MessageType_LayerSource_GeometryReady); DeclareHandledMessage(MessageType_LayerSource_LayerReady); DeclareIgnoredMessage(MessageType_LayerSource_GeometryError); DeclareIgnoredMessage(MessageType_LayerSource_ContentChanged); DeclareIgnoredMessage(MessageType_LayerSource_SliceChanged); + +// DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); +// DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); } void SmartLoader::HandleMessage(IObservable& from, const IMessage& message) @@ -51,6 +55,12 @@ const OrthancFrameLayerSource* layerSource=dynamic_cast(&from); // TODO keep track of objects that have been loaded already }; break; +// case MessageType_OrthancApi_GetStudyIds_Ready: +// { + +// const OrthancApiClient::GetJsonResponseReadyMessage& msg = dynamic_cast(message); + +// }; break; default: VLOG("unhandled message type" << message.GetType()); } @@ -75,6 +85,10 @@ return layerSource.release(); } + void SmartLoader::LoadStudyList() + { +// orthancApiClient_.ScheduleGetJsonRequest("/studies"); + } void PreloadStudy(const std::string studyId) { diff -r 0dfa83535cd7 -r 2d64f4d39610 Framework/SmartLoader.h --- a/Framework/SmartLoader.h Wed Aug 22 15:22:33 2018 +0200 +++ b/Framework/SmartLoader.h Thu Aug 23 14:45:04 2018 +0200 @@ -24,15 +24,17 @@ #include "Layers/ILayerSource.h" #include "Messages/IObservable.h" #include "../Platforms/Generic/OracleWebService.h" +#include "Toolbox/OrthancApiClient.h" namespace OrthancStone { class SmartLoader : public IObservable, IObserver { - SliceImageQuality imageQuality_; - IWebService& webService_; + SliceImageQuality imageQuality_; + IWebService& webService_; + OrthancApiClient orthancApiClient_; - + int studyListRequest_; public: SmartLoader(MessageBroker& broker, IWebService& webService); // TODO: add maxPreloadStorageSizeInBytes @@ -40,6 +42,7 @@ void PreloadStudy(const std::string studyId); void PreloadSeries(const std::string seriesId); + void LoadStudyList(); void SetImageQuality(SliceImageQuality imageQuality) { imageQuality_ = imageQuality; } diff -r 0dfa83535cd7 -r 2d64f4d39610 Framework/Toolbox/OrthancApiClient.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/OrthancApiClient.cpp Thu Aug 23 14:45:04 2018 +0200 @@ -0,0 +1,204 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + +#include "OrthancApiClient.h" + +#include "MessagingToolbox.h" +#include + +namespace OrthancStone { + + struct OrthancApiClient::InternalGetJsonResponseReadyMessage : + public IMessage + { + OrthancApiClient::BaseRequest* request_; + Json::Value response_; + + InternalGetJsonResponseReadyMessage(OrthancApiClient::BaseRequest* request, + const Json::Value& response) + : IMessage(MessageType_OrthancApi_InternalGetJsonResponseReady), + request_(request), + response_(response) + { + } + + }; + + struct OrthancApiClient::InternalGetJsonResponseErrorMessage : + public IMessage + { + OrthancApiClient::BaseRequest* request_; + + InternalGetJsonResponseErrorMessage(OrthancApiClient::BaseRequest* request) + : IMessage(MessageType_OrthancApi_InternalGetJsonResponseError), + request_(request) + { + } + }; + + + // this class handles a single request to the OrthancApiClient. + // Once the response is ready, it will emit a message to the responseObserver + // the responseObserver must handle only that message (and not all messages from the OrthancApiClient) + class OrthancApiClient::BaseRequest: + public IObserver, + public IObservable, + public Orthanc::IDynamicObject + { + public: + std::string uri_; + OrthancApiClient& orthanc_; + MessageType messageToEmitWhenResponseReady_; + OrthancApiClient::Mode mode_; + + public: + BaseRequest( + OrthancApiClient& orthanc, + IObserver& responseObserver, + const std::string& uri, + MessageType messageToEmitWhenResponseReady, + OrthancApiClient::Mode mode) + : IObserver(orthanc.broker_), + IObservable(orthanc.broker_), + uri_(uri), + orthanc_(orthanc), + messageToEmitWhenResponseReady_(messageToEmitWhenResponseReady), + mode_(mode) + { + // this object will emit only a single message, the one the final responseObserver is expecting + DeclareEmittableMessage(messageToEmitWhenResponseReady); + + // this object is observing the OrthancApi so it must handle all messages + DeclareHandledMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); + DeclareIgnoredMessage(MessageType_OrthancApi_InternalGetJsonResponseError); + + orthanc_.RegisterObserver(*this); + this->RegisterObserver(responseObserver); + } + virtual ~BaseRequest() {} + + // mainly maps OrthancApi internal messages to a message that is expected by the responseObserver + virtual void HandleMessage(IObservable& from, const IMessage& message) + { + switch (message.GetType()) + { + case MessageType_OrthancApi_InternalGetJsonResponseReady: + { + const OrthancApiClient::InternalGetJsonResponseReadyMessage& messageReceived = dynamic_cast(message); + EmitMessage(OrthancApiClient::GetJsonResponseReadyMessage(messageToEmitWhenResponseReady_, messageReceived.request_->uri_, messageReceived.response_)); + orthanc_.ReleaseRequest(messageReceived.request_); + }; break; + default: + throw MessageNotDeclaredException(message.GetType()); + } + } + + }; + + + class OrthancApiClient::WebCallback : public IWebService::ICallback + { + private: + OrthancApiClient& that_; + + public: + WebCallback(MessageBroker& broker, OrthancApiClient& that) : + IWebService::ICallback(broker), + that_(that) + { + } + + virtual void OnHttpRequestSuccess(const std::string& uri, + const void* answer, + size_t answerSize, + Orthanc::IDynamicObject* payload) + { + OrthancApiClient::BaseRequest* request = dynamic_cast(payload); // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed" + + switch (request->mode_) + { + case OrthancApiClient::Mode_GetJson: + { + Json::Value response; + if (MessagingToolbox::ParseJson(response, answer, answerSize)) + { + OrthancApiClient::InternalGetJsonResponseReadyMessage msg(request, response); + that_.EmitMessage(msg); + } + else + { + OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); + that_.EmitMessage(msg); + } + }; break; + + default: + that_.ReleaseRequest(request); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + virtual void OnHttpRequestError(const std::string& uri, + Orthanc::IDynamicObject* payload) + { + OrthancApiClient::BaseRequest* request = dynamic_cast(payload); // the BaseRequests objects belongs to the OrthancApiClient and is deleted in ReleaseRequest when it has been "consumed" + + switch (request->mode_) + { + case OrthancApiClient::Mode_GetJson: + { + OrthancApiClient::InternalGetJsonResponseErrorMessage msg(request); + that_.EmitMessage(msg); + }; break; + + default: + that_.ReleaseRequest(request); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + }; + + OrthancApiClient::OrthancApiClient(MessageBroker &broker, IWebService &orthanc) + : IObservable(broker), + orthanc_(orthanc), + webCallback_(new OrthancApiClient::WebCallback(broker, *this)) + { + DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseReady); + DeclareEmittableMessage(MessageType_OrthancApi_InternalGetJsonResponseError); + } + + void OrthancApiClient::ScheduleGetJsonRequest(IObserver &responseObserver, const std::string &uri, MessageType messageToEmitWhenResponseReady) + { + OrthancApiClient::BaseRequest* request = new OrthancApiClient::BaseRequest(*this, + responseObserver, + uri, + messageToEmitWhenResponseReady, + OrthancApiClient::Mode_GetJson); + orthanc_.ScheduleGetRequest(*webCallback_, uri, IWebService::Headers(), request); + requestsInProgress_.insert(request); + } + + void OrthancApiClient::ReleaseRequest(BaseRequest* request) + { + requestsInProgress_.erase(request); + delete request; + } + +} diff -r 0dfa83535cd7 -r 2d64f4d39610 Framework/Toolbox/OrthancApiClient.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Toolbox/OrthancApiClient.h Thu Aug 23 14:45:04 2018 +0200 @@ -0,0 +1,90 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2018 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +#include "IWebService.h" +#include "../Messages/IObservable.h" + + +namespace OrthancStone +{ + class OrthancApiClient: + public IObservable + { + protected: + class BaseRequest; + class GetJsonRequest; + + struct InternalGetJsonResponseReadyMessage; + struct InternalGetJsonResponseErrorMessage; + + public: + struct GetJsonResponseReadyMessage : public IMessage + { + Json::Value response_; + std::string uri_; + + GetJsonResponseReadyMessage(MessageType messageType, + const std::string& uri, + const Json::Value& response) + : IMessage(messageType), + uri_(uri), + response_(response) + { + } + }; + + public: + + enum Mode + { + Mode_GetJson + }; + + protected: + IWebService& orthanc_; + class WebCallback; + boost::shared_ptr webCallback_; // This is a PImpl pattern + std::set requestsInProgress_; + +// int ScheduleGetJsonRequest(const std::string& uri); + + void ReleaseRequest(BaseRequest* request); + + public: + OrthancApiClient(MessageBroker& broker, + IWebService& orthanc); + virtual ~OrthancApiClient() {} + + // schedule a GET request expecting a JSON request. + // once the response is ready, it will emit a OrthancApiClient::GetJsonResponseReadyMessage message whose messageType is specified in the call + void ScheduleGetJsonRequest(IObserver& responseObserver, const std::string& uri, MessageType messageToEmitWhenResponseReady); + + void ScheduleGetStudyIds(IObserver& responseObserver) {ScheduleGetJsonRequest(responseObserver, "/studies", MessageType_OrthancApi_GetStudyIds_Ready);} + void ScheduleGetStudy(IObserver& responseObserver, const std::string& studyId) {ScheduleGetJsonRequest(responseObserver, "/studies/" + studyId, MessageType_OrthancApi_GetStudy_Ready);} + void ScheduleGetSeries(IObserver& responseObserver, const std::string& seriesId) {ScheduleGetJsonRequest(responseObserver, "/series/" + seriesId, MessageType_OrthancApi_GetSeries_Ready);} + + }; +} diff -r 0dfa83535cd7 -r 2d64f4d39610 Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Wed Aug 22 15:22:33 2018 +0200 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Thu Aug 23 14:45:04 2018 +0200 @@ -209,6 +209,7 @@ ${ORTHANC_STONE_ROOT}/Framework/Toolbox/MessagingToolbox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrientedBoundingBox.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancSlicesLoader.cpp + ${ORTHANC_STONE_ROOT}/Framework/Toolbox/OrthancApiClient.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlices.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParallelSlicesCursor.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp