# HG changeset patch # User Sebastien Jodogne # Date 1573835659 -3600 # Node ID 48befc2bf66da75b2d184b4c85cc272f532518d9 # Parent 7aad0984d38a3e793f8cfb73186350da2a1aacc1 ParseDicomFromWadoCommand diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/GenericOracleRunner.cpp --- a/Framework/Oracle/GenericOracleRunner.cpp Fri Nov 15 12:38:15 2019 +0100 +++ b/Framework/Oracle/GenericOracleRunner.cpp Fri Nov 15 17:34:19 2019 +0100 @@ -30,11 +30,15 @@ #include "HttpCommand.h" #include "OracleCommandExceptionMessage.h" #include "OrthancRestApiCommand.h" +#include "ParseDicomFromFileCommand.h" +#include "ParseDicomFromWadoCommand.h" #include "ReadFileCommand.h" #if ORTHANC_ENABLE_DCMTK == 1 -# include "ParseDicomFileCommand.h" +# include "ParseDicomSuccessMessage.h" # include +static unsigned int BUCKET_DICOMDIR = 0; +static unsigned int BUCKET_SOP = 1; #endif #include @@ -45,6 +49,8 @@ #include +#include + namespace OrthancStone { @@ -100,9 +106,9 @@ } - static void RunInternal(boost::weak_ptr receiver, - IMessageEmitter& emitter, - const HttpCommand& command) + static void RunHttpCommand(std::string& answer, + Orthanc::HttpClient::HttpHeaders& answerHeaders, + const HttpCommand& command) { Orthanc::HttpClient client; client.SetUrl(command.GetUrl()); @@ -122,21 +128,28 @@ client.SetBody(command.GetBody()); } - std::string answer; - Orthanc::HttpClient::HttpHeaders answerHeaders; client.ApplyAndThrowException(answer, answerHeaders); - DecodeAnswer(answer, answerHeaders); - - HttpCommand::SuccessMessage message(command, answerHeaders, answer); - emitter.EmitMessage(receiver, message); } static void RunInternal(boost::weak_ptr receiver, IMessageEmitter& emitter, - const Orthanc::WebServiceParameters& orthanc, - const OrthancRestApiCommand& command) + const HttpCommand& command) + { + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + RunHttpCommand(answer, answerHeaders, command); + + HttpCommand::SuccessMessage message(command, answerHeaders, answer); + emitter.EmitMessage(receiver, message); + } + + + static void RunOrthancRestApiCommand(std::string& answer, + Orthanc::HttpClient::HttpHeaders& answerHeaders, + const Orthanc::WebServiceParameters& orthanc, + const OrthancRestApiCommand& command) { Orthanc::HttpClient client(orthanc, command.GetUri()); client.SetMethod(command.GetMethod()); @@ -150,11 +163,19 @@ client.SetBody(command.GetBody()); } + client.ApplyAndThrowException(answer, answerHeaders); + DecodeAnswer(answer, answerHeaders); + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + const Orthanc::WebServiceParameters& orthanc, + const OrthancRestApiCommand& command) + { std::string answer; Orthanc::HttpClient::HttpHeaders answerHeaders; - client.ApplyAndThrowException(answer, answerHeaders); - - DecodeAnswer(answer, answerHeaders); + RunOrthancRestApiCommand(answer, answerHeaders, orthanc, command); OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer); emitter.EmitMessage(receiver, message); @@ -238,160 +259,70 @@ #if ORTHANC_ENABLE_DCMTK == 1 - namespace + static Orthanc::ParsedDicomFile* ParseDicom(uint64_t& fileSize, /* OUT */ + const std::string& path, + bool isPixelData) { - class IDicomHandler : public boost::noncopyable + if (!Orthanc::SystemToolbox::IsRegularFile(path)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); + } + + LOG(TRACE) << "Parsing DICOM file, " << (isPixelData ? "with" : "without") + << " pixel data: " << path; + + boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); + + fileSize = Orthanc::SystemToolbox::GetFileSize(path); + + // Check for 32bit systems + if (fileSize != static_cast(static_cast(fileSize))) { - public: - virtual ~IDicomHandler() - { - } - - virtual void Handle(Orthanc::ParsedDicomFile* dicom, - const ParseDicomFileCommand& command, - const std::string& path, - uint64_t fileSize) = 0; - - static void Apply(IDicomHandler& handler, - const std::string& path, - const ParseDicomFileCommand& command) - { - if (!Orthanc::SystemToolbox::IsRegularFile(path)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentFile); - } - - LOG(TRACE) << "Parsing DICOM file, " - << (command.IsPixelDataIncluded() ? "with" : "without") - << " pixel data: " << path; - - boost::posix_time::ptime start = boost::posix_time::microsec_clock::local_time(); - - uint64_t fileSize = Orthanc::SystemToolbox::GetFileSize(path); - - // Check for 32bit systems - if (fileSize != static_cast(static_cast(fileSize))) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); - } - - DcmFileFormat dicom; - bool ok; - - if (command.IsPixelDataIncluded()) - { - ok = dicom.loadFile(path.c_str()).good(); - } - else - { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + DcmFileFormat dicom; + bool ok; + + if (isPixelData) + { + ok = dicom.loadFile(path.c_str()).good(); + } + else + { #if DCMTK_VERSION_NUMBER >= 362 - /** - * NB : We could stop at (0x3007, 0x0000) instead of - * DCM_PixelData as the Stone framework does not use further - * tags (cf. the Orthanc::DICOM_TAG_* constants), but we - * still use "PixelData" as this does not change the runtime - * much, and as it is more explicit. - **/ - static const DcmTagKey STOP = DCM_PixelData; - //static const DcmTagKey STOP(0x3007, 0x0000); - - ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange, - DCM_MaxReadLength, ERM_autoDetect, STOP).good(); -#else - // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2 - ok = dicom.loadFile(path.c_str()).good(); -#endif - } - - if (ok) - { - handler.Handle(new Orthanc::ParsedDicomFile(dicom), command, path, fileSize); + /** + * NB : We could stop at (0x3007, 0x0000) instead of + * DCM_PixelData as the Stone framework does not use further + * tags (cf. the Orthanc::DICOM_TAG_* constants), but we still + * use "PixelData" as this does not change the runtime much, and + * as it is more explicit. + **/ + static const DcmTagKey STOP = DCM_PixelData; + //static const DcmTagKey STOP(0x3007, 0x0000); - boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); - LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms"; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "Cannot parse file: " + path); - } - } - }; - + ok = dicom.loadFileUntilTag(path.c_str(), EXS_Unknown, EGL_noChange, + DCM_MaxReadLength, ERM_autoDetect, STOP).good(); +#else + // The primitive "loadFileUntilTag" was introduced in DCMTK 3.6.2 + ok = dicom.loadFile(path.c_str()).good(); +#endif + } - class DicomHandlerWithoutCache : public IDicomHandler - { - private: - boost::weak_ptr receiver_; - IMessageEmitter& emitter_; - - public: - DicomHandlerWithoutCache(boost::weak_ptr receiver, - IMessageEmitter& emitter) : - receiver_(receiver), - emitter_(emitter) - { - } - - virtual void Handle(Orthanc::ParsedDicomFile* dicom, - const ParseDicomFileCommand& command, - const std::string& path, - uint64_t fileSize) - { - std::auto_ptr parsed(dicom); - - ParseDicomFileCommand::SuccessMessage message - (command, *parsed, static_cast(fileSize), command.IsPixelDataIncluded()); - emitter_.EmitMessage(receiver_, message); - } - }; - - - class DicomHandlerWithCache : public IDicomHandler + if (ok) { - private: - boost::weak_ptr receiver_; - IMessageEmitter& emitter_; - boost::shared_ptr cache_; + std::auto_ptr result(new Orthanc::ParsedDicomFile(dicom)); + + boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time(); + LOG(TRACE) << path << ": parsed in " << (end-start).total_milliseconds() << " ms"; - public: - DicomHandlerWithCache(boost::weak_ptr receiver, - IMessageEmitter& emitter, - boost::shared_ptr cache) : - receiver_(receiver), - emitter_(emitter), - cache_(cache) - { - if (!cache) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - virtual void Handle(Orthanc::ParsedDicomFile* dicom, - const ParseDicomFileCommand& command, - const std::string& path, - uint64_t fileSize) - { - std::auto_ptr parsed(dicom); - - { - ParseDicomFileCommand::SuccessMessage message - (command, *parsed, static_cast(fileSize), command.IsPixelDataIncluded()); - emitter_.EmitMessage(receiver_, message); - } - - // Store it into the cache for future use - assert(cache_); - - // Invalidate to overwrite DICOM instance that would already - // be stored without pixel data - cache_->Invalidate(path); - - cache_->Acquire(path, parsed.release(), - static_cast(fileSize), command.IsPixelDataIncluded()); - } - }; + return result.release(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, + "Cannot parse file: " + path); + } } @@ -399,42 +330,105 @@ IMessageEmitter& emitter, boost::shared_ptr cache, const std::string& root, - const ParseDicomFileCommand& command) + const ParseDicomFromFileCommand& command) { const std::string path = GetPath(root, command.GetPath()); -#if 1 - if (cache.get()) + if (cache) { + ParsedDicomCache::Reader reader(*cache, BUCKET_DICOMDIR, path); + if (reader.IsValid() && + (!command.IsPixelDataIncluded() || + reader.HasPixelData())) { - ParsedDicomCache::Reader reader(*cache, path); - if (reader.IsValid() && - (!command.IsPixelDataIncluded() || - reader.HasPixelData())) - { - // Reuse the DICOM file from the cache - ParseDicomFileCommand::SuccessMessage message( - command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData()); - emitter.EmitMessage(receiver, message); - return; - } + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData()); + emitter.EmitMessage(receiver, message); + return; } + } + + uint64_t fileSize; + std::auto_ptr parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded())); + + if (fileSize != static_cast(fileSize)) + { + // Cannot load such a large file on 32-bit architecture + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + { + ParseDicomSuccessMessage message + (command, *parsed, static_cast(fileSize), command.IsPixelDataIncluded()); + emitter.EmitMessage(receiver, message); + } + + if (cache) + { + // Store it into the cache for future use - DicomHandlerWithCache handler(receiver, emitter, cache); - IDicomHandler::Apply(handler, path, command); + // Invalidate to overwrite DICOM instance that would already + // be stored without pixel data + cache->Invalidate(BUCKET_DICOMDIR, path); + + cache->Acquire(BUCKET_DICOMDIR, path, parsed.release(), + static_cast(fileSize), command.IsPixelDataIncluded()); } - else + } + + + static void RunInternal(boost::weak_ptr receiver, + IMessageEmitter& emitter, + boost::shared_ptr cache, + const Orthanc::WebServiceParameters& orthanc, + const ParseDicomFromWadoCommand& command) + { + if (cache) { - // No cache available - DicomHandlerWithoutCache handler(receiver, emitter); - IDicomHandler::Apply(handler, path, command); + ParsedDicomCache::Reader reader(*cache, BUCKET_SOP, command.GetSopInstanceUid()); + if (reader.IsValid() && + reader.HasPixelData()) + { + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(command, reader.GetDicom(), reader.GetFileSize(), reader.HasPixelData()); + emitter.EmitMessage(receiver, message); + return; + } } -#else - DicomHandlerWithoutCache handler(receiver, emitter); - IDicomHandler::Apply(handler, path, command); -#endif + + std::string answer; + Orthanc::HttpClient::HttpHeaders answerHeaders; + + switch (command.GetRestCommand().GetType()) + { + case IOracleCommand::Type_Http: + RunHttpCommand(answer, answerHeaders, dynamic_cast(command.GetRestCommand())); + break; + + case IOracleCommand::Type_OrthancRestApi: + RunOrthancRestApiCommand(answer, answerHeaders, orthanc, + dynamic_cast(command.GetRestCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + size_t fileSize; + std::auto_ptr parsed(ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, answerHeaders)); + + { + ParseDicomSuccessMessage message(command, *parsed, fileSize, + true /* pixel data always is included in WADO-RS */); + emitter.EmitMessage(receiver, message); + } + + if (cache) + { + // Store it into the cache for future use + cache->Acquire(BUCKET_SOP, command.GetSopInstanceUid(), parsed.release(), fileSize, true); + } } - #endif @@ -476,10 +470,24 @@ dynamic_cast(command)); break; - case IOracleCommand::Type_ParseDicomFile: + case IOracleCommand::Type_ParseDicomFromFile: + case IOracleCommand::Type_ParseDicomFromWado: #if ORTHANC_ENABLE_DCMTK == 1 - RunInternal(receiver, emitter, dicomCache_, rootDirectory_, - dynamic_cast(command)); + switch (command.GetType()) + { + case IOracleCommand::Type_ParseDicomFromFile: + RunInternal(receiver, emitter, dicomCache_, rootDirectory_, + dynamic_cast(command)); + break; + + case IOracleCommand::Type_ParseDicomFromWado: + RunInternal(receiver, emitter, dicomCache_, orthanc_, + dynamic_cast(command)); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } break; #else throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/IOracleCommand.h --- a/Framework/Oracle/IOracleCommand.h Fri Nov 15 12:38:15 2019 +0100 +++ b/Framework/Oracle/IOracleCommand.h Fri Nov 15 17:34:19 2019 +0100 @@ -34,7 +34,8 @@ Type_GetOrthancWebViewerJpeg, Type_Http, Type_OrthancRestApi, - Type_ParseDicomFile, + Type_ParseDicomFromFile, + Type_ParseDicomFromWado, Type_ReadFile, Type_Sleep }; diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomFileCommand.cpp --- a/Framework/Oracle/ParseDicomFileCommand.cpp Fri Nov 15 12:38:15 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -/** - * 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 . - **/ - - -#include "ParseDicomFileCommand.h" - -#include - -#include - -namespace OrthancStone -{ - ParseDicomFileCommand::SuccessMessage::SuccessMessage(const ParseDicomFileCommand& command, - Orthanc::ParsedDicomFile& dicom, - size_t fileSize, - bool hasPixelData) : - OriginMessage(command), - dicom_(dicom), - fileSize_(fileSize), - hasPixelData_(hasPixelData) - { - if (!dicom.GetTagValue(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, - "DICOM instance missing tag SOPInstanceUID"); - } - } - - - std::string ParseDicomFileCommand::GetDicomDirPath(const std::string& dicomDirPath, - const std::string& file) - { - std::string tmp = file; - -#if !defined(_WIN32) - std::replace(tmp.begin(), tmp.end(), '\\', '/'); -#endif - - boost::filesystem::path base = boost::filesystem::path(dicomDirPath).parent_path(); - - return (base / tmp).string(); - } -} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomFileCommand.h --- a/Framework/Oracle/ParseDicomFileCommand.h Fri Nov 15 12:38:15 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,133 +0,0 @@ -/** - * 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 . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_DCMTK) -# error The macro ORTHANC_ENABLE_DCMTK must be defined -#endif - -#if ORTHANC_ENABLE_DCMTK != 1 -# error Support for DCMTK must be enabled to use ParseDicomFileCommand -#endif - -#include "../Messages/IMessage.h" -#include "OracleCommandBase.h" - -#include - -#include - -namespace OrthancStone -{ - class ParseDicomFileCommand : public OracleCommandBase - { - public: - class SuccessMessage : public OriginMessage - { - ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); - - private: - Orthanc::ParsedDicomFile& dicom_; - size_t fileSize_; - bool hasPixelData_; - std::string sopInstanceUid_; - - public: - SuccessMessage(const ParseDicomFileCommand& command, - Orthanc::ParsedDicomFile& dicom, - size_t fileSize, - bool hasPixelData); - - Orthanc::ParsedDicomFile& GetDicom() const - { - return dicom_; - } - - size_t GetFileSize() const - { - return fileSize_; - } - - bool HasPixelData() const - { - return hasPixelData_; - } - - const std::string& GetSopInstanceUid() const - { - return sopInstanceUid_; - } - }; - - private: - std::string path_; - bool pixelDataIncluded_; - - ParseDicomFileCommand(const ParseDicomFileCommand& other) : - path_(other.path_), - pixelDataIncluded_(other.pixelDataIncluded_) - { - } - - public: - ParseDicomFileCommand(const std::string& path) : - path_(path), - pixelDataIncluded_(true) - { - } - - ParseDicomFileCommand(const std::string& dicomDirPath, - const std::string& file) : - path_(GetDicomDirPath(dicomDirPath, file)), - pixelDataIncluded_(true) - { - } - - static std::string GetDicomDirPath(const std::string& dicomDirPath, - const std::string& file); - - virtual Type GetType() const - { - return Type_ParseDicomFile; - } - - virtual IOracleCommand* Clone() const - { - return new ParseDicomFileCommand(*this); - } - - const std::string& GetPath() const - { - return path_; - } - - bool IsPixelDataIncluded() const - { - return pixelDataIncluded_; - } - - void SetPixelDataIncluded(bool included) - { - pixelDataIncluded_ = included; - } - }; -} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomFromFileCommand.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromFileCommand.cpp Fri Nov 15 17:34:19 2019 +0100 @@ -0,0 +1,43 @@ +/** + * 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 . + **/ + + +#include "ParseDicomFromFileCommand.h" + +#include + +#include + +namespace OrthancStone +{ + std::string ParseDicomFromFileCommand::GetDicomDirPath(const std::string& dicomDirPath, + const std::string& file) + { + std::string tmp = file; + +#if !defined(_WIN32) + std::replace(tmp.begin(), tmp.end(), '\\', '/'); +#endif + + boost::filesystem::path base = boost::filesystem::path(dicomDirPath).parent_path(); + + return (base / tmp).string(); + } +} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomFromFileCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromFileCommand.h Fri Nov 15 17:34:19 2019 +0100 @@ -0,0 +1,82 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +namespace OrthancStone +{ + class ParseDicomFromFileCommand : public OracleCommandBase + { + private: + std::string path_; + bool pixelDataIncluded_; + + ParseDicomFromFileCommand(const ParseDicomFromFileCommand& other) : + path_(other.path_), + pixelDataIncluded_(other.pixelDataIncluded_) + { + } + + public: + ParseDicomFromFileCommand(const std::string& path) : + path_(path), + pixelDataIncluded_(true) + { + } + + ParseDicomFromFileCommand(const std::string& dicomDirPath, + const std::string& file) : + path_(GetDicomDirPath(dicomDirPath, file)), + pixelDataIncluded_(true) + { + } + + static std::string GetDicomDirPath(const std::string& dicomDirPath, + const std::string& file); + + virtual Type GetType() const + { + return Type_ParseDicomFromFile; + } + + virtual IOracleCommand* Clone() const + { + return new ParseDicomFromFileCommand(*this); + } + + const std::string& GetPath() const + { + return path_; + } + + bool IsPixelDataIncluded() const + { + return pixelDataIncluded_; + } + + void SetPixelDataIncluded(bool included) + { + pixelDataIncluded_ = included; + } + }; +} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomFromWadoCommand.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromWadoCommand.cpp Fri Nov 15 17:34:19 2019 +0100 @@ -0,0 +1,58 @@ +/** + * 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 . + **/ + + +#include "ParseDicomFromWadoCommand.h" + +#include + +namespace OrthancStone +{ + ParseDicomFromWadoCommand::ParseDicomFromWadoCommand(const std::string& sopInstanceUid, + IOracleCommand* restCommand) : + sopInstanceUid_(sopInstanceUid), + restCommand_(restCommand) + { + if (restCommand == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (restCommand_->GetType() != Type_Http && + restCommand_->GetType() != Type_OrthancRestApi) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType); + } + } + + + IOracleCommand* ParseDicomFromWadoCommand::Clone() const + { + assert(restCommand_.get() != NULL); + return new ParseDicomFromWadoCommand(sopInstanceUid_, restCommand_->Clone()); + } + + + const IOracleCommand& ParseDicomFromWadoCommand::GetRestCommand() const + { + assert(restCommand_.get() != NULL); + return *restCommand_; + } +} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomFromWadoCommand.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomFromWadoCommand.h Fri Nov 15 17:34:19 2019 +0100 @@ -0,0 +1,52 @@ +/** + * 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 . + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +namespace OrthancStone +{ + class ParseDicomFromWadoCommand : public OracleCommandBase + { + private: + std::string sopInstanceUid_; + std::auto_ptr restCommand_; + + public: + ParseDicomFromWadoCommand(const std::string& sopInstanceUid, + IOracleCommand* restCommand); + + virtual Type GetType() const + { + return Type_ParseDicomFromWado; + } + + virtual IOracleCommand* Clone() const; + + const std::string& GetSopInstanceUid() const + { + return sopInstanceUid_; + } + + const IOracleCommand& GetRestCommand() const; + }; +} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomSuccessMessage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomSuccessMessage.cpp Fri Nov 15 17:34:19 2019 +0100 @@ -0,0 +1,107 @@ +/** + * 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 . + **/ + + +#include "ParseDicomSuccessMessage.h" + +#include +#include +#include + +namespace OrthancStone +{ + class MultipartHandler : public Orthanc::MultipartStreamReader::IHandler + { + private: + std::auto_ptr dicom_; + size_t size_; + + public: + MultipartHandler() : + size_(0) + { + } + + virtual void HandlePart(const std::map& headers, + const void* part, + size_t size) + { + if (dicom_.get()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Multiple DICOM instances were contained in a WADO-RS request"); + } + else + { + dicom_.reset(new Orthanc::ParsedDicomFile(part, size)); + size_ = size; + } + } + + Orthanc::ParsedDicomFile* ReleaseDicom() + { + if (dicom_.get()) + { + return dicom_.release(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "WADO-RS request didn't contain any DICOM instance"); + } + } + + size_t GetSize() const + { + return size_; + } + }; + + + Orthanc::ParsedDicomFile* ParseDicomSuccessMessage::ParseWadoAnswer( + size_t& fileSize /* OUT */, + const std::string& answer, + const std::map& headers) + { + std::string contentType, subType, boundary, header; + if (Orthanc::MultipartStreamReader::GetMainContentType(header, headers) && + Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, header) && + contentType == "multipart/related" && + subType == "application/dicom") + { + MultipartHandler handler; + + { + Orthanc::MultipartStreamReader reader(boundary); + reader.SetHandler(handler); + reader.AddChunk(answer); + reader.CloseStream(); + } + + fileSize = handler.GetSize(); + return handler.ReleaseDicom(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, + "Multipart/related answer of application/dicom was expected from DICOMweb server"); + } + } +} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Oracle/ParseDicomSuccessMessage.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Oracle/ParseDicomSuccessMessage.h Fri Nov 15 17:34:19 2019 +0100 @@ -0,0 +1,85 @@ +/** + * 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 . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_DCMTK) +# error The macro ORTHANC_ENABLE_DCMTK must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK != 1 +# error Support for DCMTK must be enabled to use ParseDicomFromFileCommand +#endif + +#include "OracleCommandBase.h" +#include "../Messages/IMessageEmitter.h" +#include "../Messages/IObserver.h" + +#include + +namespace Orthanc +{ + class ParsedDicomFile; +} + +namespace OrthancStone +{ + class ParseDicomSuccessMessage : public OriginMessage + { + ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); + + private: + Orthanc::ParsedDicomFile& dicom_; + size_t fileSize_; + bool hasPixelData_; + + public: + ParseDicomSuccessMessage(const OracleCommandBase& command, + Orthanc::ParsedDicomFile& dicom, + size_t fileSize, + bool hasPixelData) : + OriginMessage(command), + dicom_(dicom), + fileSize_(fileSize), + hasPixelData_(hasPixelData) + { + } + + Orthanc::ParsedDicomFile& GetDicom() const + { + return dicom_; + } + + size_t GetFileSize() const + { + return fileSize_; + } + + bool HasPixelData() const + { + return hasPixelData_; + } + + static Orthanc::ParsedDicomFile* ParseWadoAnswer(size_t& fileSize /* OUT */, + const std::string& answer, + const std::map& headers); + }; +} diff -r 7aad0984d38a -r 48befc2bf66d Framework/Toolbox/ParsedDicomCache.cpp --- a/Framework/Toolbox/ParsedDicomCache.cpp Fri Nov 15 12:38:15 2019 +0100 +++ b/Framework/Toolbox/ParsedDicomCache.cpp Fri Nov 15 17:34:19 2019 +0100 @@ -68,30 +68,42 @@ }; - void ParsedDicomCache::Acquire(const std::string& key, + std::string ParsedDicomCache::GetIndex(unsigned int bucket, + const std::string& bucketKey) + { + return boost::lexical_cast(bucket) + "|" + bucketKey; + } + + + void ParsedDicomCache::Acquire(unsigned int bucket, + const std::string& bucketKey, Orthanc::ParsedDicomFile* dicom, size_t fileSize, bool hasPixelData) { - cache_.Acquire(key, new Item(dicom, fileSize, hasPixelData)); + LOG(TRACE) << "new item stored in cache: bucket " << bucket << ", key " << bucketKey; + cache_.Acquire(GetIndex(bucket, bucketKey), new Item(dicom, fileSize, hasPixelData)); } ParsedDicomCache::Reader::Reader(ParsedDicomCache& cache, - const std::string& key) : + unsigned int bucket, + const std::string& bucketKey) : /** * The "DcmFileFormat" object cannot be accessed from multiple * threads, even if using only getters. An unique lock (mutex) is * mandatory. **/ - accessor_(cache.cache_, key, true /* unique */) + accessor_(cache.cache_, GetIndex(bucket, bucketKey), true /* unique */) { if (accessor_.IsValid()) { + LOG(TRACE) << "accessing item within cache: bucket " << bucket << ", key " << bucketKey; item_ = &dynamic_cast(accessor_.GetValue()); } else { + LOG(TRACE) << "missing item within cache: bucket " << bucket << ", key " << bucketKey; item_ = NULL; } } diff -r 7aad0984d38a -r 48befc2bf66d Framework/Toolbox/ParsedDicomCache.h --- a/Framework/Toolbox/ParsedDicomCache.h Fri Nov 15 12:38:15 2019 +0100 +++ b/Framework/Toolbox/ParsedDicomCache.h Fri Nov 15 17:34:19 2019 +0100 @@ -30,6 +30,9 @@ { private: class Item; + + static std::string GetIndex(unsigned int bucket, + const std::string& bucketKey); Orthanc::MemoryObjectCache cache_; @@ -39,12 +42,14 @@ cache_.SetMaximumSize(size); } - void Invalidate(const std::string& key) + void Invalidate(unsigned int bucket, + const std::string& bucketKey) { - cache_.Invalidate(key); + cache_.Invalidate(GetIndex(bucket, bucketKey)); } - void Acquire(const std::string& key, + void Acquire(unsigned int bucket, + const std::string& bucketKey, Orthanc::ParsedDicomFile* dicom, size_t fileSize, bool hasPixelData); @@ -57,7 +62,8 @@ public: Reader(ParsedDicomCache& cache, - const std::string& key); + unsigned int bucket, + const std::string& bucketKey); bool IsValid() const { diff -r 7aad0984d38a -r 48befc2bf66d Resources/CMake/OrthancStoneConfiguration.cmake --- a/Resources/CMake/OrthancStoneConfiguration.cmake Fri Nov 15 12:38:15 2019 +0100 +++ b/Resources/CMake/OrthancStoneConfiguration.cmake Fri Nov 15 17:34:19 2019 +0100 @@ -401,7 +401,9 @@ if (ENABLE_DCMTK) list(APPEND ORTHANC_STONE_SOURCES - ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFileCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromFileCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomFromWadoCommand.cpp + ${ORTHANC_STONE_ROOT}/Framework/Oracle/ParseDicomSuccessMessage.cpp ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ParsedDicomCache.cpp ) endif()