Mercurial > hg > orthanc-stone
changeset 1151:48befc2bf66d broker
ParseDicomFromWadoCommand
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 15 Nov 2019 17:34:19 +0100 |
parents | 7aad0984d38a |
children | 78b8bfe154bc |
files | Framework/Oracle/GenericOracleRunner.cpp Framework/Oracle/IOracleCommand.h Framework/Oracle/ParseDicomFileCommand.cpp Framework/Oracle/ParseDicomFileCommand.h Framework/Oracle/ParseDicomFromFileCommand.cpp Framework/Oracle/ParseDicomFromFileCommand.h Framework/Oracle/ParseDicomFromWadoCommand.cpp Framework/Oracle/ParseDicomFromWadoCommand.h Framework/Oracle/ParseDicomSuccessMessage.cpp Framework/Oracle/ParseDicomSuccessMessage.h Framework/Toolbox/ParsedDicomCache.cpp Framework/Toolbox/ParsedDicomCache.h Resources/CMake/OrthancStoneConfiguration.cmake |
diffstat | 13 files changed, 656 insertions(+), 393 deletions(-) [+] |
line wrap: on
line diff
--- 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 <dcmtk/dcmdata/dcdeftag.h> +static unsigned int BUCKET_DICOMDIR = 0; +static unsigned int BUCKET_SOP = 1; #endif #include <Core/Compression/GzipCompressor.h> @@ -45,6 +49,8 @@ #include <boost/filesystem.hpp> +#include <dcmtk/dcmdata/dcfilefo.h> + namespace OrthancStone { @@ -100,9 +106,9 @@ } - static void RunInternal(boost::weak_ptr<IObserver> 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<IObserver> 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<IObserver> 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<uint64_t>(static_cast<size_t>(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<uint64_t>(static_cast<size_t>(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<IObserver> receiver_; - IMessageEmitter& emitter_; - - public: - DicomHandlerWithoutCache(boost::weak_ptr<IObserver> 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<Orthanc::ParsedDicomFile> parsed(dicom); - - ParseDicomFileCommand::SuccessMessage message - (command, *parsed, static_cast<size_t>(fileSize), command.IsPixelDataIncluded()); - emitter_.EmitMessage(receiver_, message); - } - }; - - - class DicomHandlerWithCache : public IDicomHandler + if (ok) { - private: - boost::weak_ptr<IObserver> receiver_; - IMessageEmitter& emitter_; - boost::shared_ptr<ParsedDicomCache> cache_; + std::auto_ptr<Orthanc::ParsedDicomFile> 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<IObserver> receiver, - IMessageEmitter& emitter, - boost::shared_ptr<ParsedDicomCache> 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<Orthanc::ParsedDicomFile> parsed(dicom); - - { - ParseDicomFileCommand::SuccessMessage message - (command, *parsed, static_cast<size_t>(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<size_t>(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<ParsedDicomCache> 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<Orthanc::ParsedDicomFile> parsed(ParseDicom(fileSize, path, command.IsPixelDataIncluded())); + + if (fileSize != static_cast<size_t>(fileSize)) + { + // Cannot load such a large file on 32-bit architecture + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + { + ParseDicomSuccessMessage message + (command, *parsed, static_cast<size_t>(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<size_t>(fileSize), command.IsPixelDataIncluded()); } - else + } + + + static void RunInternal(boost::weak_ptr<IObserver> receiver, + IMessageEmitter& emitter, + boost::shared_ptr<ParsedDicomCache> 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<const HttpCommand&>(command.GetRestCommand())); + break; + + case IOracleCommand::Type_OrthancRestApi: + RunOrthancRestApiCommand(answer, answerHeaders, orthanc, + dynamic_cast<const OrthancRestApiCommand&>(command.GetRestCommand())); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + size_t fileSize; + std::auto_ptr<Orthanc::ParsedDicomFile> 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<const ReadFileCommand&>(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<const ParseDicomFileCommand&>(command)); + switch (command.GetType()) + { + case IOracleCommand::Type_ParseDicomFromFile: + RunInternal(receiver, emitter, dicomCache_, rootDirectory_, + dynamic_cast<const ParseDicomFromFileCommand&>(command)); + break; + + case IOracleCommand::Type_ParseDicomFromWado: + RunInternal(receiver, emitter, dicomCache_, orthanc_, + dynamic_cast<const ParseDicomFromWadoCommand&>(command)); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } break; #else throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
--- 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 };
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#include "ParseDicomFileCommand.h" - -#include <Core/OrthancException.h> - -#include <boost/filesystem/path.hpp> - -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(); - } -}
--- 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 <http://www.gnu.org/licenses/>. - **/ - - -#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 <Core/DicomParsing/ParsedDicomFile.h> - -#include <dcmtk/dcmdata/dcfilefo.h> - -namespace OrthancStone -{ - class ParseDicomFileCommand : public OracleCommandBase - { - public: - class SuccessMessage : public OriginMessage<ParseDicomFileCommand> - { - 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; - } - }; -}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "ParseDicomFromFileCommand.h" + +#include <Core/OrthancException.h> + +#include <boost/filesystem/path.hpp> + +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(); + } +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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; + } + }; +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "ParseDicomFromWadoCommand.h" + +#include <Core/OrthancException.h> + +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_; + } +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "OracleCommandBase.h" + +namespace OrthancStone +{ + class ParseDicomFromWadoCommand : public OracleCommandBase + { + private: + std::string sopInstanceUid_; + std::auto_ptr<IOracleCommand> 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; + }; +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#include "ParseDicomSuccessMessage.h" + +#include <Core/DicomParsing/ParsedDicomFile.h> +#include <Core/HttpServer/MultipartStreamReader.h> +#include <Core/OrthancException.h> + +namespace OrthancStone +{ + class MultipartHandler : public Orthanc::MultipartStreamReader::IHandler + { + private: + std::auto_ptr<Orthanc::ParsedDicomFile> dicom_; + size_t size_; + + public: + MultipartHandler() : + size_(0) + { + } + + virtual void HandlePart(const std::map<std::string, std::string>& 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<std::string, std::string>& 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"); + } + } +}
--- /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 <http://www.gnu.org/licenses/>. + **/ + + +#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 <map> + +namespace Orthanc +{ + class ParsedDicomFile; +} + +namespace OrthancStone +{ + class ParseDicomSuccessMessage : public OriginMessage<OracleCommandBase> + { + 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<std::string, std::string>& headers); + }; +}
--- 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<std::string>(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<Item&>(accessor_.GetValue()); } else { + LOG(TRACE) << "missing item within cache: bucket " << bucket << ", key " << bucketKey; item_ = NULL; } }
--- 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 {
--- 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()