Mercurial > hg > orthanc
changeset 2382:7284093111b0
big reorganization to cleanly separate framework vs. server
line wrap: on
line diff
--- a/CMakeLists.txt Tue Aug 29 19:59:01 2017 +0200 +++ b/CMakeLists.txt Tue Aug 29 21:17:35 2017 +0200 @@ -166,23 +166,27 @@ set(ORTHANC_SERVER_SOURCES + Core/DicomParsing/DicomDirWriter.cpp + Core/DicomParsing/DicomModification.cpp + Core/DicomParsing/FromDcmtkBridge.cpp + Core/DicomParsing/Internals/DicomFrameIndex.cpp + Core/DicomParsing/Internals/DicomImageDecoder.cpp + Core/DicomParsing/ParsedDicomFile.cpp + Core/DicomParsing/ToDcmtkBridge.cpp + + Core/DicomNetworking/DicomFindAnswers.cpp + Core/DicomNetworking/DicomServer.cpp + Core/DicomNetworking/DicomUserConnection.cpp + Core/DicomNetworking/RemoteModalityParameters.cpp + Core/DicomNetworking/ReusableDicomUserConnection.cpp + Core/DicomNetworking/Internals/CommandDispatcher.cpp + Core/DicomNetworking/Internals/FindScp.cpp + Core/DicomNetworking/Internals/MoveScp.cpp + Core/DicomNetworking/Internals/StoreScp.cpp + OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapperBase.cpp - OrthancServer/DicomDirWriter.cpp - OrthancServer/DicomModification.cpp - OrthancServer/DicomProtocol/DicomFindAnswers.cpp - OrthancServer/DicomProtocol/DicomServer.cpp - OrthancServer/DicomProtocol/DicomUserConnection.cpp - OrthancServer/DicomProtocol/RemoteModalityParameters.cpp - OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp OrthancServer/ExportedResource.cpp - OrthancServer/FromDcmtkBridge.cpp - OrthancServer/Internals/CommandDispatcher.cpp - OrthancServer/Internals/DicomFrameIndex.cpp - OrthancServer/Internals/DicomImageDecoder.cpp - OrthancServer/Internals/FindScp.cpp - OrthancServer/Internals/MoveScp.cpp - OrthancServer/Internals/StoreScp.cpp OrthancServer/LuaScripting.cpp OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancHttpHandler.cpp @@ -195,7 +199,6 @@ OrthancServer/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/OrthancRestApi/OrthancRestSystem.cpp - OrthancServer/ParsedDicomFile.cpp OrthancServer/QueryRetrieveHandler.cpp OrthancServer/Search/HierarchicalMatcher.cpp OrthancServer/Search/IFindConstraint.cpp @@ -211,7 +214,6 @@ OrthancServer/ServerIndex.cpp OrthancServer/ServerToolbox.cpp OrthancServer/SliceOrdering.cpp - OrthancServer/ToDcmtkBridge.cpp # From "lua-scripting" branch OrthancServer/DicomInstanceToStore.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomFindAnswers.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,178 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomFindAnswers.h" + +#include "../DicomParsing/FromDcmtkBridge.h" +#include "../OrthancException.h" + +#include <memory> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <boost/noncopyable.hpp> + + +namespace Orthanc +{ + void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer) + { + std::auto_ptr<ParsedDicomFile> protection(answer); + + if (isWorklist_) + { + // These lines are necessary when serving worklists, otherwise + // Orthanc does not behave as "wlmscpfs" + protection->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID); + protection->Remove(DICOM_TAG_SOP_INSTANCE_UID); + } + + protection->ChangeEncoding(encoding_); + + answers_.push_back(protection.release()); + } + + + DicomFindAnswers::DicomFindAnswers(bool isWorklist) : + encoding_(GetDefaultDicomEncoding()), + isWorklist_(isWorklist), + complete_(true) + { + } + + + void DicomFindAnswers::SetEncoding(Encoding encoding) + { + for (size_t i = 0; i < answers_.size(); i++) + { + assert(answers_[i] != NULL); + answers_[i]->ChangeEncoding(encoding); + } + + encoding_ = encoding; + } + + + void DicomFindAnswers::SetWorklist(bool isWorklist) + { + if (answers_.empty()) + { + isWorklist_ = isWorklist; + } + else + { + // This set of answers is not empty anymore, cannot change its type + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomFindAnswers::Clear() + { + for (size_t i = 0; i < answers_.size(); i++) + { + assert(answers_[i] != NULL); + delete answers_[i]; + } + + answers_.clear(); + } + + + void DicomFindAnswers::Reserve(size_t size) + { + if (size > answers_.size()) + { + answers_.reserve(size); + } + } + + + void DicomFindAnswers::Add(const DicomMap& map) + { + AddAnswerInternal(new ParsedDicomFile(map, encoding_)); + } + + + void DicomFindAnswers::Add(ParsedDicomFile& dicom) + { + AddAnswerInternal(dicom.Clone()); + } + + void DicomFindAnswers::Add(const void* dicom, + size_t size) + { + AddAnswerInternal(new ParsedDicomFile(dicom, size)); + } + + + ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const + { + if (index < answers_.size()) + { + return *answers_[index]; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const + { + return new DcmDataset(*GetAnswer(index).GetDcmtkObject().getDataset()); + } + + + void DicomFindAnswers::ToJson(Json::Value& target, + size_t index, + bool simplify) const + { + DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full); + GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0); + } + + + void DicomFindAnswers::ToJson(Json::Value& target, + bool simplify) const + { + target = Json::arrayValue; + + for (size_t i = 0; i < GetSize(); i++) + { + Json::Value answer; + ToJson(answer, i, simplify); + target.append(answer); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomFindAnswers.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,109 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../DicomParsing/ParsedDicomFile.h" + +namespace Orthanc +{ + class DicomFindAnswers : public boost::noncopyable + { + private: + Encoding encoding_; + bool isWorklist_; + std::vector<ParsedDicomFile*> answers_; + bool complete_; + + void AddAnswerInternal(ParsedDicomFile* answer); + + public: + DicomFindAnswers(bool isWorklist); + + ~DicomFindAnswers() + { + Clear(); + } + + Encoding GetEncoding() const + { + return encoding_; + } + + void SetEncoding(Encoding encoding); + + void SetWorklist(bool isWorklist); + + bool IsWorklist() const + { + return isWorklist_; + } + + void Clear(); + + void Reserve(size_t index); + + void Add(const DicomMap& map); + + void Add(ParsedDicomFile& dicom); + + void Add(const void* dicom, + size_t size); + + size_t GetSize() const + { + return answers_.size(); + } + + ParsedDicomFile& GetAnswer(size_t index) const; + + DcmDataset* ExtractDcmDataset(size_t index) const; + + void ToJson(Json::Value& target, + bool simplify) const; + + void ToJson(Json::Value& target, + size_t index, + bool simplify) const; + + bool IsComplete() const + { + return complete_; + } + + void SetComplete(bool isComplete) + { + complete_ = isComplete; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomServer.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,382 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomServer.h" + +#include "../../Core/Logging.h" +#include "../../Core/MultiThreading/RunnableWorkersPool.h" +#include "../../Core/OrthancException.h" +#include "../../Core/Toolbox.h" +#include "Internals/CommandDispatcher.h" + +#include <boost/thread.hpp> + +#if defined(__linux__) +#include <cstdlib> +#endif + + +namespace Orthanc +{ + struct DicomServer::PImpl + { + boost::thread thread_; + T_ASC_Network *network_; + std::auto_ptr<RunnableWorkersPool> workers_; + }; + + + void DicomServer::ServerThread(DicomServer* server) + { + LOG(INFO) << "DICOM server started"; + + while (server->continue_) + { + /* receive an association and acknowledge or reject it. If the association was */ + /* acknowledged, offer corresponding services and invoke one or more if required. */ + std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); + + try + { + if (dispatcher.get() != NULL) + { + server->pimpl_->workers_->Add(dispatcher.release()); + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception in the DICOM server thread: " << e.What(); + } + } + + LOG(INFO) << "DICOM server stopping"; + } + + + DicomServer::DicomServer() : + pimpl_(new PImpl), + aet_("ANY-SCP") + { + port_ = 104; + modalities_ = NULL; + findRequestHandlerFactory_ = NULL; + moveRequestHandlerFactory_ = NULL; + storeRequestHandlerFactory_ = NULL; + worklistRequestHandlerFactory_ = NULL; + applicationEntityFilter_ = NULL; + checkCalledAet_ = true; + associationTimeout_ = 30; + continue_ = false; + } + + DicomServer::~DicomServer() + { + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!"; + Stop(); + } + } + + void DicomServer::SetPortNumber(uint16_t port) + { + Stop(); + port_ = port; + } + + uint16_t DicomServer::GetPortNumber() const + { + return port_; + } + + void DicomServer::SetAssociationTimeout(uint32_t seconds) + { + LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): " + << seconds << " seconds (0 = no timeout)"; + + Stop(); + associationTimeout_ = seconds; + } + + uint32_t DicomServer::GetAssociationTimeout() const + { + return associationTimeout_; + } + + + void DicomServer::SetCalledApplicationEntityTitleCheck(bool check) + { + Stop(); + checkCalledAet_ = check; + } + + bool DicomServer::HasCalledApplicationEntityTitleCheck() const + { + return checkCalledAet_; + } + + void DicomServer::SetApplicationEntityTitle(const std::string& aet) + { + if (aet.size() == 0) + { + throw OrthancException(ErrorCode_BadApplicationEntityTitle); + } + + if (aet.size() > 16) + { + throw OrthancException(ErrorCode_BadApplicationEntityTitle); + } + + for (size_t i = 0; i < aet.size(); i++) + { + if (!(aet[i] == '-' || + aet[i] == '_' || + isdigit(aet[i]) || + (aet[i] >= 'A' && aet[i] <= 'Z'))) + { + LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\""; + break; + } + } + + Stop(); + aet_ = aet; + } + + const std::string& DicomServer::GetApplicationEntityTitle() const + { + return aet_; + } + + void DicomServer::SetRemoteModalities(IRemoteModalities& modalities) + { + Stop(); + modalities_ = &modalities; + } + + DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const + { + if (modalities_ == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + return *modalities_; + } + } + + void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory) + { + Stop(); + findRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasFindRequestHandlerFactory() const + { + return (findRequestHandlerFactory_ != NULL); + } + + IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const + { + if (HasFindRequestHandlerFactory()) + { + return *findRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoCFindHandler); + } + } + + void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory) + { + Stop(); + moveRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasMoveRequestHandlerFactory() const + { + return (moveRequestHandlerFactory_ != NULL); + } + + IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const + { + if (HasMoveRequestHandlerFactory()) + { + return *moveRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoCMoveHandler); + } + } + + void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory) + { + Stop(); + storeRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasStoreRequestHandlerFactory() const + { + return (storeRequestHandlerFactory_ != NULL); + } + + IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const + { + if (HasStoreRequestHandlerFactory()) + { + return *storeRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoCStoreHandler); + } + } + + void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory) + { + Stop(); + worklistRequestHandlerFactory_ = &factory; + } + + bool DicomServer::HasWorklistRequestHandlerFactory() const + { + return (worklistRequestHandlerFactory_ != NULL); + } + + IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const + { + if (HasWorklistRequestHandlerFactory()) + { + return *worklistRequestHandlerFactory_; + } + else + { + throw OrthancException(ErrorCode_NoWorklistHandler); + } + } + + void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory) + { + Stop(); + applicationEntityFilter_ = &factory; + } + + bool DicomServer::HasApplicationEntityFilter() const + { + return (applicationEntityFilter_ != NULL); + } + + IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const + { + if (HasApplicationEntityFilter()) + { + return *applicationEntityFilter_; + } + else + { + throw OrthancException(ErrorCode_NoApplicationEntityFilter); + } + } + + void DicomServer::Start() + { + if (modalities_ == NULL) + { + LOG(ERROR) << "No list of modalities was provided to the DICOM server"; + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + Stop(); + + /* initialize network, i.e. create an instance of T_ASC_Network*. */ + OFCondition cond = ASC_initializeNetwork + (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_); + if (cond.bad()) + { + LOG(ERROR) << "cannot create network: " << cond.text(); + throw OrthancException(ErrorCode_DicomPortInUse); + } + + continue_ = true; + pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? + pimpl_->thread_ = boost::thread(ServerThread, this); + } + + + void DicomServer::Stop() + { + if (continue_) + { + continue_ = false; + + if (pimpl_->thread_.joinable()) + { + pimpl_->thread_.join(); + } + + pimpl_->workers_.reset(NULL); + + /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ + /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ + OFCondition cond = ASC_dropNetwork(&pimpl_->network_); + if (cond.bad()) + { + LOG(ERROR) << "Error while dropping the network: " << cond.text(); + } + } + } + + + bool DicomServer::IsMyAETitle(const std::string& aet) const + { + if (modalities_ == NULL) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + + if (!HasCalledApplicationEntityTitleCheck()) + { + // OK, no check on the AET. + return true; + } + else + { + return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle()); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomServer.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,136 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 +#endif + +#include "IFindRequestHandlerFactory.h" +#include "IMoveRequestHandlerFactory.h" +#include "IStoreRequestHandlerFactory.h" +#include "IWorklistRequestHandlerFactory.h" +#include "IApplicationEntityFilter.h" +#include "RemoteModalityParameters.h" + +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> + + +namespace Orthanc +{ + class DicomServer : public boost::noncopyable + { + public: + // WARNING: The methods of this class must be thread-safe + class IRemoteModalities : public boost::noncopyable + { + public: + virtual ~IRemoteModalities() + { + } + + virtual bool IsSameAETitle(const std::string& aet1, + const std::string& aet2) = 0; + + virtual bool LookupAETitle(RemoteModalityParameters& modality, + const std::string& aet) = 0; + }; + + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + bool checkCalledAet_; + std::string aet_; + uint16_t port_; + bool continue_; + uint32_t associationTimeout_; + IRemoteModalities* modalities_; + IFindRequestHandlerFactory* findRequestHandlerFactory_; + IMoveRequestHandlerFactory* moveRequestHandlerFactory_; + IStoreRequestHandlerFactory* storeRequestHandlerFactory_; + IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_; + IApplicationEntityFilter* applicationEntityFilter_; + + static void ServerThread(DicomServer* server); + + public: + DicomServer(); + + ~DicomServer(); + + void SetPortNumber(uint16_t port); + uint16_t GetPortNumber() const; + + void SetAssociationTimeout(uint32_t seconds); + uint32_t GetAssociationTimeout() const; + + void SetCalledApplicationEntityTitleCheck(bool check); + bool HasCalledApplicationEntityTitleCheck() const; + + void SetApplicationEntityTitle(const std::string& aet); + const std::string& GetApplicationEntityTitle() const; + + void SetRemoteModalities(IRemoteModalities& modalities); + IRemoteModalities& GetRemoteModalities() const; + + void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler); + bool HasFindRequestHandlerFactory() const; + IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const; + + void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler); + bool HasMoveRequestHandlerFactory() const; + IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const; + + void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler); + bool HasStoreRequestHandlerFactory() const; + IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const; + + void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler); + bool HasWorklistRequestHandlerFactory() const; + IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const; + + void SetApplicationEntityFilter(IApplicationEntityFilter& handler); + bool HasApplicationEntityFilter() const; + IApplicationEntityFilter& GetApplicationEntityFilter() const; + + void Start(); + + void Stop(); + + bool IsMyAETitle(const std::string& aet) const; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,1220 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../PrecompiledHeaders.h" +#include "DicomUserConnection.h" + +#include "../DicomFormat/DicomArray.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../DicomParsing/FromDcmtkBridge.h" +#include "../DicomParsing/ToDcmtkBridge.h" + +#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmdata/dcistrmf.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmnet/diutil.h> + +#include <set> + + +#ifdef _WIN32 +/** + * "The maximum length, in bytes, of the string returned in the buffer + * pointed to by the name parameter is dependent on the namespace provider, + * but this string must be 256 bytes or less. + * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx + **/ +# define HOST_NAME_MAX 256 +# include <winsock.h> +#endif + + +#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) +/** + * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that + * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an + * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect + * that the result will fit." + * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html + **/ +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif + + +static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; + +/** + * "If we have more than 64 storage SOP classes, tools such as + * storescu will fail because they attempt to negotiate two + * presentation contexts for each SOP class, and there is a total + * limit of 128 contexts for one association." + **/ +static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; + + +namespace Orthanc +{ + // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds + static uint32_t defaultTimeout_ = 10; + + struct DicomUserConnection::PImpl + { + // Connection state + uint32_t dimseTimeout_; + uint32_t acseTimeout_; + T_ASC_Network* net_; + T_ASC_Parameters* params_; + T_ASC_Association* assoc_; + + bool IsOpen() const + { + return assoc_ != NULL; + } + + void CheckIsOpen() const; + + void Store(DcmInputStream& is, + DicomUserConnection& connection, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + }; + + + static void Check(const OFCondition& cond) + { + if (cond.bad()) + { + LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text()); + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + void DicomUserConnection::PImpl::CheckIsOpen() const + { + if (!IsOpen()) + { + LOG(ERROR) << "DicomUserConnection: First open the connection"; + throw OrthancException(ErrorCode_NetworkProtocol); + } + } + + + void DicomUserConnection::CheckIsOpen() const + { + pimpl_->CheckIsOpen(); + } + + + static void RegisterStorageSOPClass(T_ASC_Parameters* params, + unsigned int& presentationContextId, + const std::string& sopClass, + const char* asPreferred[], + std::vector<const char*>& asFallback) + { + Check(ASC_addPresentationContext(params, presentationContextId, + sopClass.c_str(), asPreferred, 1)); + presentationContextId += 2; + + if (asFallback.size() > 0) + { + Check(ASC_addPresentationContext(params, presentationContextId, + sopClass.c_str(), &asFallback[0], asFallback.size())); + presentationContextId += 2; + } + } + + + void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) + { + // Flatten an array with the preferred transfer syntax + const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; + + // Setup the fallback transfer syntaxes + std::set<std::string> fallbackSyntaxes; + fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); + fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); + fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); + fallbackSyntaxes.erase(preferredTransferSyntax); + + // Flatten an array with the fallback transfer syntaxes + std::vector<const char*> asFallback; + asFallback.reserve(fallbackSyntaxes.size()); + for (std::set<std::string>::const_iterator + it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) + { + asFallback.push_back(it->c_str()); + } + + CheckStorageSOPClassesInvariant(); + unsigned int presentationContextId = 1; + + for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); + it != reservedStorageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } + + for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); + it != storageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } + + for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); + it != defaultStorageSOPClasses_.end(); ++it) + { + RegisterStorageSOPClass(pimpl_->params_, presentationContextId, + *it, asPreferred, asFallback); + } + } + + + static bool IsGenericTransferSyntax(const std::string& syntax) + { + return (syntax == UID_LittleEndianExplicitTransferSyntax || + syntax == UID_BigEndianExplicitTransferSyntax || + syntax == UID_LittleEndianImplicitTransferSyntax); + } + + + void DicomUserConnection::PImpl::Store(DcmInputStream& is, + DicomUserConnection& connection, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + CheckIsOpen(); + + DcmFileFormat dcmff; + Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); + + // Determine the storage SOP class UID for this instance + static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016); + OFString sopClassUid; + if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good()) + { + connection.AddStorageSOPClass(sopClassUid.c_str()); + } + + // Determine whether a new presentation context must be + // negotiated, depending on the transfer syntax of this instance + DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); + const std::string syntax(xfer.getXferID()); + bool isGeneric = IsGenericTransferSyntax(syntax); + + bool renegotiate; + if (isGeneric) + { + // Are we making a generic-to-specific or specific-to-generic change of + // the transfer syntax? If this is the case, renegotiate the connection. + renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); + } + else + { + // We are using a specific transfer syntax. Renegotiate if the + // current connection does not match this transfer syntax. + renegotiate = (syntax != connection.GetPreferredTransferSyntax()); + } + + if (renegotiate) + { + LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; + + if (isGeneric) + { + connection.ResetPreferredTransferSyntax(); + } + else + { + connection.SetPreferredTransferSyntax(syntax); + } + } + + if (!connection.IsOpen()) + { + LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; + connection.Open(); + } + + // Figure out which SOP class and SOP instance is encapsulated in the file + DIC_UI sopClass; + DIC_UI sopInstance; + if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) + { + throw OrthancException(ErrorCode_NoSopClassOrInstance); + } + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); + if (presID == 0) + { + const char *modalityName = dcmSOPClassUIDToModality(sopClass); + if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); + if (!modalityName) modalityName = "unknown SOP class"; + throw OrthancException(ErrorCode_NoPresentationContext); + } + + // Prepare the transmission of data + T_DIMSE_C_StoreRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = assoc_->nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN); + + if (!moveOriginatorAET.empty()) + { + strncpy(request.MoveOriginatorApplicationEntityTitle, + moveOriginatorAET.c_str(), DIC_AE_LEN); + request.opts = O_STORE_MOVEORIGINATORAETITLE; + + request.MoveOriginatorID = moveOriginatorID; // The type DIC_US is an alias for uint16_t + request.opts |= O_STORE_MOVEORIGINATORID; + } + + // Finally conduct transmission of data + T_DIMSE_C_StoreRSP rsp; + DcmDataset* statusDetail = NULL; + Check(DIMSE_storeUser(assoc_, presID, &request, + NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, + /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_, + &rsp, &statusDetail, NULL)); + + if (statusDetail != NULL) + { + delete statusDetail; + } + } + + + namespace + { + struct FindPayload + { + DicomFindAnswers* answers; + const char* level; + bool isWorklist; + }; + } + + + static void FindCallback( + /* in */ + void *callbackData, + T_DIMSE_C_FindRQ *request, /* original find request */ + int responseCount, + T_DIMSE_C_FindRSP *response, /* pending response received */ + DcmDataset *responseIdentifiers /* pending response identifiers */ + ) + { + FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData); + + if (responseIdentifiers != NULL) + { + if (payload.isWorklist) + { + ParsedDicomFile answer(*responseIdentifiers); + payload.answers->Add(answer); + } + else + { + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); + + if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); + } + + payload.answers->Add(m); + } + } + } + + + static void FixFindQuery(DicomMap& fixedQuery, + ResourceType level, + const DicomMap& fields) + { + std::set<DicomTag> allowedTags; + + // WARNING: Do not add "break" or reorder items in this switch-case! + switch (level) + { + case ResourceType_Instance: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); + + case ResourceType_Series: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); + + case ResourceType_Study: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); + + case ResourceType_Patient: + DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + switch (level) + { + case ResourceType_Patient: + allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); + allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); + allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); + break; + + case ResourceType_Study: + allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); + allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); + allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); + allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); + break; + + case ResourceType_Series: + allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); + break; + + default: + break; + } + + allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); + + DicomArray query(fields); + for (size_t i = 0; i < query.GetSize(); i++) + { + const DicomTag& tag = query.GetElement(i).GetTag(); + if (allowedTags.find(tag) == allowedTags.end()) + { + LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; + } + else + { + fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); + } + } + } + + + static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, + ModalityManufacturer manufacturer) + { + // Fix outgoing C-Find requests issue for Syngo.Via and its + // solution was reported by Emsy Chan by private mail on + // 2015-06-17. According to Robert van Ommen (2015-11-30), the + // same fix is required for Agfa Impax. This was generalized for + // generic manufacturer since it seems to affect PhilipsADW, + // GEWAServer as well: + // https://bitbucket.org/sjodogne/orthanc/issues/31/ + + switch (manufacturer) + { + case ModalityManufacturer_GenericNoWildcardInDates: + case ModalityManufacturer_GenericNoUniversalWildcard: + { + std::auto_ptr<DicomMap> fix(fields.Clone()); + + std::set<DicomTag> tags; + fix->GetTags(tags); + + for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) + { + // Replace a "*" wildcard query by an empty query ("") for + // "date" or "all" value representations depending on the + // type of manufacturer. + if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || + (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && + FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) + { + const DicomValue* value = fix->TestAndGetValue(*it); + + if (value != NULL && + !value->IsNull() && + value->GetContent() == "*") + { + fix->SetValue(*it, "", false); + } + } + } + + return new ParsedDicomFile(*fix); + } + + default: + return new ParsedDicomFile(fields); + } + } + + + static void ExecuteFind(DicomFindAnswers& answers, + T_ASC_Association* association, + DcmDataset* dataset, + const char* sopClass, + bool isWorklist, + const char* level, + uint32_t dimseTimeout) + { + assert(isWorklist ^ (level != NULL)); + + FindPayload payload; + payload.answers = &answers; + payload.level = level; + payload.isWorklist = isWorklist; + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(association, sopClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_DicomFindUnavailable); + } + + T_DIMSE_C_FindRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = association->nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + + T_DIMSE_C_FindRSP response; + DcmDataset* statusDetail = NULL; + OFCondition cond = DIMSE_findUser(association, presID, &request, dataset, + FindCallback, &payload, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ dimseTimeout, + &response, &statusDetail); + + if (statusDetail) + { + delete statusDetail; + } + + Check(cond); + } + + + void DicomUserConnection::Find(DicomFindAnswers& result, + ResourceType level, + const DicomMap& originalFields) + { + DicomMap fields; + FixFindQuery(fields, level, originalFields); + + CheckIsOpen(); + + std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); + DcmDataset* dataset = query->GetDcmtkObject().getDataset(); + + const char* clevel = NULL; + const char* sopClass = NULL; + + switch (level) + { + case ResourceType_Patient: + clevel = "PATIENT"; + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT"); + sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; + break; + + case ResourceType_Study: + clevel = "STUDY"; + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + + case ResourceType_Series: + clevel = "SERIES"; + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES"); + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + + case ResourceType_Instance: + clevel = "INSTANCE"; + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) + { + // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. + // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J + // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + + sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Add the expected tags for this query level. + // WARNING: Do not reorder or add "break" in this switch-case! + switch (level) + { + case ResourceType_Instance: + // SOP Instance UID + if (!fields.HasTag(0x0008, 0x0018)) + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), ""); + + case ResourceType_Series: + // Series instance UID + if (!fields.HasTag(0x0020, 0x000e)) + DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), ""); + + case ResourceType_Study: + // Accession number + if (!fields.HasTag(0x0008, 0x0050)) + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), ""); + + // Study instance UID + if (!fields.HasTag(0x0020, 0x000d)) + DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), ""); + + case ResourceType_Patient: + // Patient ID + if (!fields.HasTag(0x0010, 0x0020)) + DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), ""); + + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + assert(clevel != NULL && sopClass != NULL); + ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, pimpl_->dimseTimeout_); + } + + + void DicomUserConnection::MoveInternal(const std::string& targetAet, + ResourceType level, + const DicomMap& fields) + { + CheckIsOpen(); + + std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); + DcmDataset* dataset = query->GetDcmtkObject().getDataset(); + + const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; + switch (level) + { + case ResourceType_Patient: + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT"); + break; + + case ResourceType_Study: + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY"); + break; + + case ResourceType_Series: + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES"); + break; + + case ResourceType_Instance: + if (manufacturer_ == ModalityManufacturer_ClearCanvas || + manufacturer_ == ModalityManufacturer_Dcm4Chee) + { + // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. + // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J + // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE"); + } + else + { + DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE"); + } + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Figure out which of the accepted presentation contexts should be used + int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); + if (presID == 0) + { + throw OrthancException(ErrorCode_DicomMoveUnavailable); + } + + T_DIMSE_C_MoveRQ request; + memset(&request, 0, sizeof(request)); + request.MessageID = pimpl_->assoc_->nextMsgID++; + strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); + request.Priority = DIMSE_PRIORITY_MEDIUM; + request.DataSetType = DIMSE_DATASET_PRESENT; + strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); + + T_DIMSE_C_MoveRSP response; + DcmDataset* statusDetail = NULL; + DcmDataset* responseIdentifiers = NULL; + OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset, + NULL, NULL, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, + pimpl_->net_, NULL, NULL, + &response, &statusDetail, &responseIdentifiers); + + if (statusDetail) + { + delete statusDetail; + } + + if (responseIdentifiers) + { + delete responseIdentifiers; + } + + Check(cond); + } + + + void DicomUserConnection::ResetStorageSOPClasses() + { + CheckStorageSOPClassesInvariant(); + + storageSOPClasses_.clear(); + defaultStorageSOPClasses_.clear(); + + // Copy the short list of storage SOP classes from DCMTK, making + // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**). + + std::set<std::string> uncommon; + uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); + uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); + uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); + uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); + uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage); + + // Add the storage syntaxes for C-STORE + for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) + { + if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) + { + defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); + } + } + + CheckStorageSOPClassesInvariant(); + } + + + DicomUserConnection::DicomUserConnection() : + pimpl_(new PImpl), + preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), + localAet_("STORESCU"), + remoteAet_("ANY-SCP"), + remoteHost_("127.0.0.1") + { + remotePort_ = 104; + manufacturer_ = ModalityManufacturer_Generic; + + SetTimeout(defaultTimeout_); + pimpl_->net_ = NULL; + pimpl_->params_ = NULL; + pimpl_->assoc_ = NULL; + + // SOP classes for C-ECHO, C-FIND and C-MOVE (**) + reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); + reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel); + + ResetStorageSOPClasses(); + } + + DicomUserConnection::~DicomUserConnection() + { + Close(); + } + + + void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) + { + SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); + SetRemoteHost(parameters.GetHost()); + SetRemotePort(parameters.GetPort()); + SetRemoteManufacturer(parameters.GetManufacturer()); + } + + + void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) + { + if (localAet_ != aet) + { + Close(); + localAet_ = aet; + } + } + + void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet) + { + if (remoteAet_ != aet) + { + Close(); + remoteAet_ = aet; + } + } + + void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer) + { + if (manufacturer_ != manufacturer) + { + Close(); + manufacturer_ = manufacturer; + } + } + + void DicomUserConnection::ResetPreferredTransferSyntax() + { + SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); + } + + void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) + { + if (preferredTransferSyntax_ != preferredTransferSyntax) + { + Close(); + preferredTransferSyntax_ = preferredTransferSyntax; + } + } + + + void DicomUserConnection::SetRemoteHost(const std::string& host) + { + if (remoteHost_ != host) + { + if (host.size() > HOST_NAME_MAX - 10) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + Close(); + remoteHost_ = host; + } + } + + void DicomUserConnection::SetRemotePort(uint16_t port) + { + if (remotePort_ != port) + { + Close(); + remotePort_ = port; + } + } + + void DicomUserConnection::Open() + { + if (IsOpen()) + { + // Don't reopen the connection + return; + } + + LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() + << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host " + << GetRemoteHost() << ":" << GetRemotePort() + << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")"; + + Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_)); + Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); + + // Set this application's title and the called application's title in the params + Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL)); + + // Set the network addresses of the local and remote entities + char localHost[HOST_NAME_MAX]; + gethostname(localHost, HOST_NAME_MAX - 1); + + char remoteHostAndPort[HOST_NAME_MAX]; + +#ifdef _MSC_VER + _snprintf +#else + snprintf +#endif + (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_); + + Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort)); + + // Set various options + Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); + + SetupPresentationContexts(preferredTransferSyntax_); + + // Do the association + Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_)); + + if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) + { + throw OrthancException(ErrorCode_NoPresentationContext); + } + } + + void DicomUserConnection::Close() + { + if (pimpl_->assoc_ != NULL) + { + ASC_releaseAssociation(pimpl_->assoc_); + ASC_destroyAssociation(&pimpl_->assoc_); + pimpl_->assoc_ = NULL; + pimpl_->params_ = NULL; + } + else + { + if (pimpl_->params_ != NULL) + { + ASC_destroyAssociationParameters(&pimpl_->params_); + pimpl_->params_ = NULL; + } + } + + if (pimpl_->net_ != NULL) + { + ASC_dropNetwork(&pimpl_->net_); + pimpl_->net_ = NULL; + } + } + + bool DicomUserConnection::IsOpen() const + { + return pimpl_->IsOpen(); + } + + void DicomUserConnection::Store(const char* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + // Prepare an input stream for the memory buffer + DcmInputBufferStream is; + if (size > 0) + is.setBuffer(buffer, size); + is.setEos(); + + pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); + } + + void DicomUserConnection::Store(const std::string& buffer, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + if (buffer.size() > 0) + Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveOriginatorAET, moveOriginatorID); + else + Store(NULL, 0, moveOriginatorAET, moveOriginatorID); + } + + void DicomUserConnection::StoreFile(const std::string& path, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID) + { + // Prepare an input stream for the file + DcmInputFileStream is(path.c_str()); + pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); + } + + bool DicomUserConnection::Echo() + { + CheckIsOpen(); + DIC_US status; + Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, + &status, NULL)); + return status == STATUS_Success; + } + + + static void TestAndCopyTag(DicomMap& result, + const DicomMap& source, + const DicomTag& tag) + { + if (!source.HasTag(tag)) + { + throw OrthancException(ErrorCode_BadRequest); + } + else + { + result.SetValue(tag, source.GetValue(tag)); + } + } + + + void DicomUserConnection::Move(const std::string& targetAet, + ResourceType level, + const DicomMap& findResult) + { + DicomMap move; + switch (level) + { + case ResourceType_Patient: + TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); + break; + + case ResourceType_Study: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + break; + + case ResourceType_Series: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); + break; + + case ResourceType_Instance: + TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); + TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + MoveInternal(targetAet, level, move); + } + + + void DicomUserConnection::Move(const std::string& targetAet, + const DicomMap& findResult) + { + if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) + { + throw OrthancException(ErrorCode_InternalError); + } + + const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); + ResourceType level = StringToResourceType(tmp.c_str()); + + Move(targetAet, level, findResult); + } + + + void DicomUserConnection::MovePatient(const std::string& targetAet, + const std::string& patientId) + { + DicomMap query; + query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); + MoveInternal(targetAet, ResourceType_Patient, query); + } + + void DicomUserConnection::MoveStudy(const std::string& targetAet, + const std::string& studyUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); + MoveInternal(targetAet, ResourceType_Study, query); + } + + void DicomUserConnection::MoveSeries(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); + query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); + MoveInternal(targetAet, ResourceType_Series, query); + } + + void DicomUserConnection::MoveInstance(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid, + const std::string& instanceUid) + { + DicomMap query; + query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); + query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); + query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); + MoveInternal(targetAet, ResourceType_Instance, query); + } + + + void DicomUserConnection::SetTimeout(uint32_t seconds) + { + if (seconds == 0) + { + DisableTimeout(); + } + else + { + dcmConnectionTimeout.set(seconds); + pimpl_->dimseTimeout_ = seconds; + pimpl_->acseTimeout_ = 10; // Timeout used during association negociation + } + } + + + void DicomUserConnection::DisableTimeout() + { + /** + * Global timeout (seconds) for connecting to remote hosts. + * Default value is -1 which selects infinite timeout, i.e. blocking connect(). + */ + dcmConnectionTimeout.set(-1); + pimpl_->dimseTimeout_ = 0; + pimpl_->acseTimeout_ = 10; // Timeout used during association negociation + } + + + void DicomUserConnection::CheckStorageSOPClassesInvariant() const + { + assert(storageSOPClasses_.size() + + defaultStorageSOPClasses_.size() + + reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); + } + + void DicomUserConnection::AddStorageSOPClass(const char* sop) + { + CheckStorageSOPClassesInvariant(); + + if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) + { + // This storage SOP class is already explicitly registered. Do + // nothing. + return; + } + + if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) + { + // This storage SOP class is not explicitly registered, but is + // used by default. Just register it explicitly. + defaultStorageSOPClasses_.erase(sop); + storageSOPClasses_.insert(sop); + + CheckStorageSOPClassesInvariant(); + return; + } + + // This storage SOP class is neither explicitly, nor implicitly + // registered. Close the connection and register it explicitly. + + Close(); + + if (reservedStorageSOPClasses_.size() + + storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) + { + // The maximum number of SOP classes is reached + ResetStorageSOPClasses(); + defaultStorageSOPClasses_.erase(sop); + } + else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + + defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) + { + // Make room in the default storage syntaxes + assert(!defaultStorageSOPClasses_.empty()); // Necessarily true because condition (*) is false + defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); + } + + // Explicitly register the new storage syntax + storageSOPClasses_.insert(sop); + + CheckStorageSOPClassesInvariant(); + } + + + void DicomUserConnection::FindWorklist(DicomFindAnswers& result, + ParsedDicomFile& query) + { + CheckIsOpen(); + + DcmDataset* dataset = query.GetDcmtkObject().getDataset(); + const char* sopClass = UID_FINDModalityWorklistInformationModel; + + ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_); + } + + + void DicomUserConnection::SetDefaultTimeout(uint32_t seconds) + { + LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " + << seconds << " seconds (0 = no timeout)"; + defaultTimeout_ = seconds; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/DicomUserConnection.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,205 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 +#endif + +#include "DicomFindAnswers.h" +#include "../Enumerations.h" +#include "RemoteModalityParameters.h" + +#include <stdint.h> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <list> + +namespace Orthanc +{ + class DicomUserConnection : public boost::noncopyable + { + private: + struct PImpl; + boost::shared_ptr<PImpl> pimpl_; + + // Connection parameters + std::string preferredTransferSyntax_; + std::string localAet_; + std::string remoteAet_; + std::string remoteHost_; + uint16_t remotePort_; + ModalityManufacturer manufacturer_; + std::set<std::string> storageSOPClasses_; + std::list<std::string> reservedStorageSOPClasses_; + std::set<std::string> defaultStorageSOPClasses_; + + void CheckIsOpen() const; + + void SetupPresentationContexts(const std::string& preferredTransferSyntax); + + void MoveInternal(const std::string& targetAet, + ResourceType level, + const DicomMap& fields); + + void ResetStorageSOPClasses(); + + void CheckStorageSOPClassesInvariant() const; + + public: + DicomUserConnection(); + + ~DicomUserConnection(); + + void SetRemoteModality(const RemoteModalityParameters& parameters); + + void SetLocalApplicationEntityTitle(const std::string& aet); + + const std::string& GetLocalApplicationEntityTitle() const + { + return localAet_; + } + + void SetRemoteApplicationEntityTitle(const std::string& aet); + + const std::string& GetRemoteApplicationEntityTitle() const + { + return remoteAet_; + } + + void SetRemoteHost(const std::string& host); + + const std::string& GetRemoteHost() const + { + return remoteHost_; + } + + void SetRemotePort(uint16_t port); + + uint16_t GetRemotePort() const + { + return remotePort_; + } + + void SetRemoteManufacturer(ModalityManufacturer manufacturer); + + ModalityManufacturer GetRemoteManufacturer() const + { + return manufacturer_; + } + + void ResetPreferredTransferSyntax(); + + void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); + + const std::string& GetPreferredTransferSyntax() const + { + return preferredTransferSyntax_; + } + + void AddStorageSOPClass(const char* sop); + + void Open(); + + void Close(); + + bool IsOpen() const; + + bool Echo(); + + void Store(const char* buffer, + size_t size, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Store(const char* buffer, + size_t size) + { + Store(buffer, size, "", 0); // Not a C-Move + } + + void Store(const std::string& buffer, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void Store(const std::string& buffer) + { + Store(buffer, "", 0); // Not a C-Move + } + + void StoreFile(const std::string& path, + const std::string& moveOriginatorAET, + uint16_t moveOriginatorID); + + void StoreFile(const std::string& path) + { + StoreFile(path, "", 0); // Not a C-Move + } + + void Find(DicomFindAnswers& result, + ResourceType level, + const DicomMap& fields); + + void Move(const std::string& targetAet, + ResourceType level, + const DicomMap& findResult); + + void Move(const std::string& targetAet, + const DicomMap& findResult); + + void MovePatient(const std::string& targetAet, + const std::string& patientId); + + void MoveStudy(const std::string& targetAet, + const std::string& studyUid); + + void MoveSeries(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid); + + void MoveInstance(const std::string& targetAet, + const std::string& studyUid, + const std::string& seriesUid, + const std::string& instanceUid); + + void SetTimeout(uint32_t seconds); + + void DisableTimeout(); + + void FindWorklist(DicomFindAnswers& result, + ParsedDicomFile& query); + + static void SetDefaultTimeout(uint32_t seconds); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IApplicationEntityFilter.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,67 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <string> + +namespace Orthanc +{ + class IApplicationEntityFilter : public boost::noncopyable + { + public: + virtual ~IApplicationEntityFilter() + { + } + + virtual bool IsAllowedConnection(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + + virtual bool IsAllowedRequest(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + DicomRequestType type) = 0; + + virtual bool IsAllowedTransferSyntax(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + TransferSyntax syntax) = 0; + + virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IFindRequestHandler.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DicomFindAnswers.h" + +namespace Orthanc +{ + class IFindRequestHandler : public boost::noncopyable + { + public: + virtual ~IFindRequestHandler() + { + } + + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input, + const std::list<DicomTag>& sequencesToReturn, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IFindRequestHandlerFactory.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IFindRequestHandler.h" + +namespace Orthanc +{ + class IFindRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IFindRequestHandlerFactory() + { + } + + virtual IFindRequestHandler* ConstructFindRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IMoveRequestHandler.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,79 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/DicomFormat/DicomMap.h" + +#include <vector> +#include <string> + + +namespace Orthanc +{ + class IMoveRequestIterator : public boost::noncopyable + { + public: + enum Status + { + Status_Success, + Status_Failure, + Status_Warning + }; + + virtual ~IMoveRequestIterator() + { + } + + virtual unsigned int GetSubOperationCount() const = 0; + + virtual Status DoNext() = 0; + }; + + + class IMoveRequestHandler + { + public: + virtual ~IMoveRequestHandler() + { + } + + virtual IMoveRequestIterator* Handle(const std::string& targetAet, + const DicomMap& input, + const std::string& originatorIp, + const std::string& originatorAet, + const std::string& calledAet, + uint16_t originatorId) = 0; + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IMoveRequestHandlerFactory.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IMoveRequestHandler.h" + +namespace Orthanc +{ + class IMoveRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IMoveRequestHandlerFactory() + { + } + + virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IStoreRequestHandler.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,58 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Core/DicomFormat/DicomMap.h" + +#include <vector> +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + class IStoreRequestHandler : public boost::noncopyable + { + public: + virtual ~IStoreRequestHandler() + { + } + + virtual void Handle(const std::string& dicomFile, + const DicomMap& dicomSummary, + const Json::Value& dicomJson, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IStoreRequestHandlerFactory.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IStoreRequestHandler.h" + +namespace Orthanc +{ + class IStoreRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IStoreRequestHandlerFactory() + { + } + + virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IWorklistRequestHandler.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DicomFindAnswers.h" + +namespace Orthanc +{ + class IWorklistRequestHandler : public boost::noncopyable + { + public: + virtual ~IWorklistRequestHandler() + { + } + + virtual void Handle(DicomFindAnswers& answers, + ParsedDicomFile& query, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + ModalityManufacturer manufacturer) = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/IWorklistRequestHandlerFactory.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,49 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "IWorklistRequestHandler.h" + +namespace Orthanc +{ + class IWorklistRequestHandlerFactory : public boost::noncopyable + { + public: + virtual ~IWorklistRequestHandlerFactory() + { + } + + virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,934 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../../PrecompiledHeaders.h" +#include "CommandDispatcher.h" + +#include "FindScp.h" +#include "StoreScp.h" +#include "MoveScp.h" +#include "../../Toolbox.h" +#include "../../Logging.h" + +#include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ +#include <boost/lexical_cast.hpp> + +static OFBool opt_rejectWithoutImplementationUID = OFFalse; + + + +static DUL_PRESENTATIONCONTEXT * +findPresentationContextID(LST_HEAD * head, + T_ASC_PresentationContextID presentationContextID) +{ + DUL_PRESENTATIONCONTEXT *pc; + LST_HEAD **l; + OFBool found = OFFalse; + + if (head == NULL) + return NULL; + + l = &head; + if (*l == NULL) + return NULL; + + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); + (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); + + while (pc && !found) { + if (pc->presentationContextID == presentationContextID) { + found = OFTrue; + } else { + pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); + } + } + return pc; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithTransferSyntax( + T_ASC_Parameters * params, + const char* transferSyntax, + T_ASC_SC_ROLE acceptedRole) +{ + OFCondition cond = EC_Normal; + int n, i, k; + DUL_PRESENTATIONCONTEXT *dpc; + T_ASC_PresentationContext pc; + OFBool accepted = OFFalse; + OFBool abstractOK = OFFalse; + + n = ASC_countPresentationContexts(params); + for (i = 0; i < n; i++) + { + cond = ASC_getPresentationContext(params, i, &pc); + if (cond.bad()) return cond; + abstractOK = OFFalse; + accepted = OFFalse; + + if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) + { + abstractOK = OFTrue; + + /* check the transfer syntax */ + for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) + { + if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) + { + accepted = OFTrue; + } + } + } + + if (accepted) + { + cond = ASC_acceptPresentationContext( + params, pc.presentationContextID, + transferSyntax, acceptedRole); + if (cond.bad()) return cond; + } else { + T_ASC_P_ResultReason reason; + + /* do not refuse if already accepted */ + dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, + pc.presentationContextID); + if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) + { + + if (abstractOK) { + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + } else { + reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; + } + /* + * If previously this presentation context was refused + * because of bad transfer syntax let it stay that way. + */ + if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) + reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; + + cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); + if (cond.bad()) return cond; + } + } + } + return EC_Normal; +} + + +/** accept all presenstation contexts for unknown SOP classes, + * i.e. UIDs appearing in the list of abstract syntaxes + * where no corresponding name is defined in the UID dictionary. + * This method is passed a list of "preferred" transfer syntaxes. + * @param params pointer to association parameters structure + * @param transferSyntax transfer syntax to accept + * @param acceptedRole SCU/SCP role to accept + */ +static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( + T_ASC_Parameters * params, + const char* transferSyntaxes[], int transferSyntaxCount, + T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT) +{ + OFCondition cond = EC_Normal; + /* + ** Accept in the order "least wanted" to "most wanted" transfer + ** syntax. Accepting a transfer syntax will override previously + ** accepted transfer syntaxes. + */ + for (int i = transferSyntaxCount - 1; i >= 0; i--) + { + cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); + if (cond.bad()) return cond; + } + return cond; +} + + + +namespace Orthanc +{ + namespace Internals + { + /** + * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0 + * (dcmAllStorageSOPClassUIDs). + * + * an array of const strings containing all known Storage SOP + * Classes that fit into the conventional + * PATIENT-STUDY-SERIES-INSTANCE information model, + * i.e. everything a Storage SCP might want to store in a PACS. + * Special cases such as hanging protocol storage or the Storage + * SOP Class are not included in this list. + * + * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED + * ONES AND IS LARGER THAN 64 ENTRIES. + */ + + const char* orthancStorageSOPClassUIDs[] = + { + UID_AmbulatoryECGWaveformStorage, + UID_ArterialPulseWaveformStorage, + UID_AutorefractionMeasurementsStorage, + UID_BasicStructuredDisplayStorage, + UID_BasicTextSRStorage, + UID_BasicVoiceAudioWaveformStorage, + UID_BlendingSoftcopyPresentationStateStorage, + UID_BreastTomosynthesisImageStorage, + UID_CardiacElectrophysiologyWaveformStorage, + UID_ChestCADSRStorage, + UID_ColonCADSRStorage, + UID_ColorSoftcopyPresentationStateStorage, + UID_ComprehensiveSRStorage, + UID_ComputedRadiographyImageStorage, + UID_CTImageStorage, + UID_DeformableSpatialRegistrationStorage, + UID_DigitalIntraOralXRayImageStorageForPresentation, + UID_DigitalIntraOralXRayImageStorageForProcessing, + UID_DigitalMammographyXRayImageStorageForPresentation, + UID_DigitalMammographyXRayImageStorageForProcessing, + UID_DigitalXRayImageStorageForPresentation, + UID_DigitalXRayImageStorageForProcessing, + UID_EncapsulatedCDAStorage, + UID_EncapsulatedPDFStorage, + UID_EnhancedCTImageStorage, + UID_EnhancedMRColorImageStorage, + UID_EnhancedMRImageStorage, + UID_EnhancedPETImageStorage, + UID_EnhancedSRStorage, + UID_EnhancedUSVolumeStorage, + UID_EnhancedXAImageStorage, + UID_EnhancedXRFImageStorage, + UID_GeneralAudioWaveformStorage, + UID_GeneralECGWaveformStorage, + UID_GenericImplantTemplateStorage, + UID_GrayscaleSoftcopyPresentationStateStorage, + UID_HemodynamicWaveformStorage, + UID_ImplantAssemblyTemplateStorage, + UID_ImplantationPlanSRDocumentStorage, + UID_ImplantTemplateGroupStorage, + UID_IntraocularLensCalculationsStorage, + UID_KeratometryMeasurementsStorage, + UID_KeyObjectSelectionDocumentStorage, + UID_LensometryMeasurementsStorage, + UID_MacularGridThicknessAndVolumeReportStorage, + UID_MammographyCADSRStorage, + UID_MRImageStorage, + UID_MRSpectroscopyStorage, + UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage, + UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage, + UID_MultiframeSingleBitSecondaryCaptureImageStorage, + UID_MultiframeTrueColorSecondaryCaptureImageStorage, + UID_NuclearMedicineImageStorage, + UID_OphthalmicAxialMeasurementsStorage, + UID_OphthalmicPhotography16BitImageStorage, + UID_OphthalmicPhotography8BitImageStorage, + UID_OphthalmicTomographyImageStorage, + UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage, + UID_PositronEmissionTomographyImageStorage, + UID_ProcedureLogStorage, + UID_PseudoColorSoftcopyPresentationStateStorage, + UID_RawDataStorage, + UID_RealWorldValueMappingStorage, + UID_RespiratoryWaveformStorage, + UID_RTBeamsTreatmentRecordStorage, + UID_RTBrachyTreatmentRecordStorage, + UID_RTDoseStorage, + UID_RTImageStorage, + UID_RTIonBeamsTreatmentRecordStorage, + UID_RTIonPlanStorage, + UID_RTPlanStorage, + UID_RTStructureSetStorage, + UID_RTTreatmentSummaryRecordStorage, + UID_SecondaryCaptureImageStorage, + UID_SegmentationStorage, + UID_SpatialFiducialsStorage, + UID_SpatialRegistrationStorage, + UID_SpectaclePrescriptionReportStorage, + UID_StereometricRelationshipStorage, + UID_SubjectiveRefractionMeasurementsStorage, + UID_SurfaceSegmentationStorage, + UID_TwelveLeadECGWaveformStorage, + UID_UltrasoundImageStorage, + UID_UltrasoundMultiframeImageStorage, + UID_VideoEndoscopicImageStorage, + UID_VideoMicroscopicImageStorage, + UID_VideoPhotographicImageStorage, + UID_VisualAcuityMeasurementsStorage, + UID_VLEndoscopicImageStorage, + UID_VLMicroscopicImageStorage, + UID_VLPhotographicImageStorage, + UID_VLSlideCoordinatesMicroscopicImageStorage, + UID_VLWholeSlideMicroscopyImageStorage, + UID_XAXRFGrayscaleSoftcopyPresentationStateStorage, + UID_XRay3DAngiographicImageStorage, + UID_XRay3DCraniofacialImageStorage, + UID_XRayAngiographicImageStorage, + UID_XRayRadiationDoseSRStorage, + UID_XRayRadiofluoroscopicImageStorage, + // retired + UID_RETIRED_HardcopyColorImageStorage, + UID_RETIRED_HardcopyGrayscaleImageStorage, + UID_RETIRED_NuclearMedicineImageStorage, + UID_RETIRED_StandaloneCurveStorage, + UID_RETIRED_StandaloneModalityLUTStorage, + UID_RETIRED_StandaloneOverlayStorage, + UID_RETIRED_StandalonePETCurveStorage, + UID_RETIRED_StandaloneVOILUTStorage, + UID_RETIRED_StoredPrintStorage, + UID_RETIRED_UltrasoundImageStorage, + UID_RETIRED_UltrasoundMultiframeImageStorage, + UID_RETIRED_VLImageStorage, + UID_RETIRED_VLMultiFrameImageStorage, + UID_RETIRED_XRayAngiographicBiPlaneImageStorage, + // draft + UID_DRAFT_SRAudioStorage, + UID_DRAFT_SRComprehensiveStorage, + UID_DRAFT_SRDetailStorage, + UID_DRAFT_SRTextStorage, + UID_DRAFT_WaveformStorage, + UID_DRAFT_RTBeamsDeliveryInstructionStorage, + NULL + }; + + const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1; + + + + OFCondition AssociationCleanup(T_ASC_Association *assoc) + { + OFCondition cond = ASC_dropSCPAssociation(assoc); + if (cond.bad()) + { + LOG(FATAL) << cond.text(); + return cond; + } + + cond = ASC_destroyAssociation(&assoc); + if (cond.bad()) + { + LOG(FATAL) << cond.text(); + return cond; + } + + return cond; + } + + + + CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) + { + DcmAssociationConfiguration asccfg; + char buf[BUFSIZ]; + T_ASC_Association *assoc; + OFCondition cond; + OFString sprofile; + OFString temp_str; + + std::vector<const char*> knownAbstractSyntaxes; + + // For C-STORE + if (server.HasStoreRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); + } + + // For C-FIND + if (server.HasFindRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); + knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); + } + + if (server.HasWorklistRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); + } + + // For C-MOVE + if (server.HasMoveRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); + } + + cond = ASC_receiveAssociation(net, &assoc, + /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, + NULL, NULL, + /*opt_secureConnection*/ OFFalse, + DUL_NOBLOCK, 1); + + if (cond == DUL_NOASSOCIATIONREQUEST) + { + // Timeout + AssociationCleanup(assoc); + return NULL; + } + + // if some kind of error occured, take care of it + if (cond.bad()) + { + LOG(ERROR) << "Receiving Association failed: " << cond.text(); + // no matter what kind of error occurred, we need to do a cleanup + AssociationCleanup(assoc); + return NULL; + } + + // Retrieve the AET and the IP address of the remote modality + std::string remoteAet; + std::string remoteIp; + std::string calledAet; + + { + DIC_AE remoteAet_C; + DIC_AE calledAet_C; + DIC_AE remoteIp_C; + DIC_AE calledIP_C; + if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() || + ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad()) + { + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C)); + remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C)); + calledAet = (/*OFSTRING_GUARD*/(calledAet_C)); + } + + LOG(INFO) << "Association Received from AET " << remoteAet + << " on IP " << remoteIp; + + + std::vector<const char*> transferSyntaxes; + + // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 + transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); + transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); + transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); + + // New transfer syntaxes supported since Orthanc 0.7.2 + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) + { + transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) + { + transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) + { + transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) + { + transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) + { + transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); + transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) + { + transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); + transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) + { + transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); + } + + /* accept the Verification SOP Class if presented */ + cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size()); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + + /* the array of Storage SOP Class UIDs comes from dcuid.h */ + cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size()); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) + { + /* + * Promiscous mode is enabled: Accept everything not known not + * to be a storage SOP class. + **/ + cond = acceptUnknownContextsWithPreferredTransferSyntaxes( + assoc->params, &transferSyntaxes[0], transferSyntaxes.size()); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + } + + /* set our app title */ + ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); + + /* acknowledge or reject this association */ + cond = ASC_getApplicationContextName(assoc->params, buf); + if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0) + { + /* reject: the application context name is not supported */ + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED + }; + + LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf; + cond = ASC_rejectAssociation(assoc, &rej); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + } + AssociationCleanup(assoc); + return NULL; + } + + /* check the AETs */ + if (!server.IsMyAETitle(calledAet)) + { + LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")"; + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + if (server.HasApplicationEntityFilter() && + !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet)) + { + LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp; + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + if (opt_rejectWithoutImplementationUID && + strlen(assoc->params->theirImplementationClassUID) == 0) + { + /* reject: the no implementation Class UID provided */ + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + + LOG(INFO) << "Association Rejected: No Implementation Class UID provided"; + cond = ASC_rejectAssociation(assoc, &rej); + if (cond.bad()) + { + LOG(INFO) << cond.text(); + } + AssociationCleanup(assoc); + return NULL; + } + + { + cond = ASC_acknowledgeAssociation(assoc); + if (cond.bad()) + { + LOG(ERROR) << cond.text(); + AssociationCleanup(assoc); + return NULL; + } + LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"; + if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) + LOG(INFO) << " (but no valid presentation contexts)"; + } + + IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; + return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter); + } + + + CommandDispatcher::CommandDispatcher(const DicomServer& server, + T_ASC_Association* assoc, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + IApplicationEntityFilter* filter) : + server_(server), + assoc_(assoc), + remoteIp_(remoteIp), + remoteAet_(remoteAet), + calledAet_(calledAet), + filter_(filter) + { + associationTimeout_ = server.GetAssociationTimeout(); + elapsedTimeSinceLastCommand_ = 0; + } + + + CommandDispatcher::~CommandDispatcher() + { + try + { + AssociationCleanup(assoc_); + } + catch (...) + { + LOG(ERROR) << "Some association was not cleanly aborted"; + } + } + + + bool CommandDispatcher::Step() + /* + * This function receives DIMSE commmands over the network connection + * and handles these commands correspondingly. Note that in case of + * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed. + */ + { + bool finished = false; + + // receive a DIMSE command over the network, with a timeout of 1 second + DcmDataset *statusDetail = NULL; + T_ASC_PresentationContextID presID = 0; + T_DIMSE_Message msg; + + OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail); + elapsedTimeSinceLastCommand_++; + + // if the command which was received has extra status + // detail information, dump this information + if (statusDetail != NULL) + { + //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail)); + delete statusDetail; + } + + if (cond == DIMSE_OUTOFRESOURCES) + { + finished = true; + } + else if (cond == DIMSE_NODATAAVAILABLE) + { + // Timeout due to DIMSE_NONBLOCKING + if (associationTimeout_ != 0 && + elapsedTimeSinceLastCommand_ >= associationTimeout_) + { + // This timeout is actually a association timeout + finished = true; + } + } + else if (cond == EC_Normal) + { + // Reset the association timeout counter + elapsedTimeSinceLastCommand_ = 0; + + // Convert the type of request to Orthanc's internal type + bool supported = false; + DicomRequestType request; + switch (msg.CommandField) + { + case DIMSE_C_ECHO_RQ: + request = DicomRequestType_Echo; + supported = true; + break; + + case DIMSE_C_STORE_RQ: + request = DicomRequestType_Store; + supported = true; + break; + + case DIMSE_C_MOVE_RQ: + request = DicomRequestType_Move; + supported = true; + break; + + case DIMSE_C_FIND_RQ: + request = DicomRequestType_Find; + supported = true; + break; + + default: + // we cannot handle this kind of message + cond = DIMSE_BADCOMMANDTYPE; + LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; + break; + } + + + // Check whether this request is allowed by the security filter + if (supported && + filter_ != NULL && + !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request)) + { + LOG(WARNING) << "Rejected " << EnumerationToString(request) + << " request from remote DICOM modality with AET \"" + << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\""; + cond = DIMSE_ILLEGALASSOCIATION; + supported = false; + finished = true; + } + + // in case we received a supported message, process this command + if (supported) + { + // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer + cond = DIMSE_BADCOMMANDTYPE; + + switch (request) + { + case DicomRequestType_Echo: + cond = EchoScp(assoc_, &msg, presID); + break; + + case DicomRequestType_Store: + if (server_.HasStoreRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IStoreRequestHandler> handler + (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); + + if (handler.get() != NULL) + { + cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_); + } + } + break; + + case DicomRequestType_Move: + if (server_.HasMoveRequestHandlerFactory()) // Should always be true + { + std::auto_ptr<IMoveRequestHandler> handler + (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); + + if (handler.get() != NULL) + { + cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_); + } + } + break; + + case DicomRequestType_Find: + if (server_.HasFindRequestHandlerFactory() || // Should always be true + server_.HasWorklistRequestHandlerFactory()) + { + std::auto_ptr<IFindRequestHandler> findHandler; + if (server_.HasFindRequestHandlerFactory()) + { + findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); + } + + std::auto_ptr<IWorklistRequestHandler> worklistHandler; + if (server_.HasWorklistRequestHandlerFactory()) + { + worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler()); + } + + cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(), + findHandler.get(), worklistHandler.get(), + remoteIp_, remoteAet_, calledAet_); + } + break; + + default: + // Should never happen + break; + } + } + } + else + { + // Bad status, which indicates the closing of the connection by + // the peer or a network error + finished = true; + + LOG(INFO) << cond.text(); + } + + if (finished) + { + if (cond == DUL_PEERREQUESTEDRELEASE) + { + LOG(INFO) << "Association Release"; + ASC_acknowledgeRelease(assoc_); + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + LOG(INFO) << "Association Aborted"; + } + else + { + OFString temp_str; + LOG(INFO) << "DIMSE failure (aborting association): " << cond.text(); + /* some kind of error so abort the association */ + ASC_abortAssociation(assoc_); + } + } + + return !finished; + } + + + OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) + { + OFString temp_str; + LOG(INFO) << "Received Echo Request"; + //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); + + /* the echo succeeded !! */ + OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); + if (cond.bad()) + { + LOG(ERROR) << "Echo SCP Failed: " << cond.text(); + } + return cond; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,79 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../DicomServer.h" +#include "../../MultiThreading/IRunnableBySteps.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition AssociationCleanup(T_ASC_Association *assoc); + + class CommandDispatcher : public IRunnableBySteps + { + private: + uint32_t associationTimeout_; + uint32_t elapsedTimeSinceLastCommand_; + const DicomServer& server_; + T_ASC_Association* assoc_; + std::string remoteIp_; + std::string remoteAet_; + std::string calledAet_; + IApplicationEntityFilter* filter_; + + public: + CommandDispatcher(const DicomServer& server, + T_ASC_Association* assoc, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + IApplicationEntityFilter* filter); + + virtual ~CommandDispatcher(); + + virtual bool Step(); + }; + + OFCondition EchoScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID); + + CommandDispatcher* AcceptAssociation(const DicomServer& server, + T_ASC_Network *net); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/FindScp.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,348 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + + +#include "../../PrecompiledHeaders.h" +#include "FindScp.h" + +#include "../../DicomParsing/FromDcmtkBridge.h" +#include "../../DicomParsing/ToDcmtkBridge.h" +#include "../../Logging.h" +#include "../../OrthancException.h" + +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcdeftag.h> + + + +/** + * The function below is extracted from DCMTK 3.6.0, cf. file + * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc". + **/ + +static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, + const DcmTagKey &sequenceTagKey) +// Date : May 3, 2005 +// Author : Thomas Wilkens +// Task : This function performs a check on a sequence attribute in the given dataset. At two different places +// in the definition of the DICOM worklist management service, a sequence attribute with a return type +// of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes +// specifies that in case a sequence item is present, then these two attributes must be existent and +// must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.) +// In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass +// and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what +// this function does. +// Parameters : dataset - [in] Dataset in which the consistency of the sequence attribute shall be checked. +// sequenceTagKey - [in] DcmTagKey of the sequence attribute which shall be checked. +// Return Value : none. +{ + DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL; + + // in case the sequence attribute contains exactly one item with an empty + // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item + if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() && + ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 && + ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() && + referencedSOPClassUIDAttribute->getLength() == 0 && + ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() && + referencedSOPInstanceUIDAttribute->getLength() == 0 ) + { + DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) ); + delete item; + } +} + + + +namespace Orthanc +{ + namespace + { + struct FindScpData + { + DicomServer::IRemoteModalities* modalities_; + IFindRequestHandler* findHandler_; + IWorklistRequestHandler* worklistHandler_; + DicomFindAnswers answers_; + DcmDataset* lastRequest_; + const std::string* remoteIp_; + const std::string* remoteAet_; + const std::string* calledAet_; + + FindScpData() : answers_(false) + { + } + }; + + + + static void FixWorklistQuery(ParsedDicomFile& query) + { + // TODO: Check out + // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()" + // in DCMTK 3.6.0 + + DcmDataset* dataset = query.GetDcmtkObject().getDataset(); + HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence); + HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence); + } + + + + void FindScpCallback( + /* in */ + void *callbackData, + OFBool cancelled, + T_DIMSE_C_FindRQ *request, + DcmDataset *requestIdentifiers, + int responseCount, + /* out */ + T_DIMSE_C_FindRSP *response, + DcmDataset **responseIdentifiers, + DcmDataset **statusDetail) + { + bzero(response, sizeof(T_DIMSE_C_FindRSP)); + *statusDetail = NULL; + + std::string sopClassUid(request->AffectedSOPClassUID); + + FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData); + if (data.lastRequest_ == NULL) + { + bool ok = false; + + try + { + RemoteModalityParameters modality; + + /** + * Ensure that the remote modality is known to Orthanc for C-FIND requests. + **/ + + assert(data.modalities_ != NULL); + if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_)) + { + LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_ + << "\" is not defined in the \"DicomModalities\" configuration option"; + throw OrthancException(ErrorCode_UnknownModality); + } + + + if (sopClassUid == UID_FINDModalityWorklistInformationModel) + { + data.answers_.SetWorklist(true); + + if (data.worklistHandler_ != NULL) + { + ParsedDicomFile query(*requestIdentifiers); + FixWorklistQuery(query); + + data.worklistHandler_->Handle(data.answers_, query, + *data.remoteIp_, *data.remoteAet_, + *data.calledAet_, modality.GetManufacturer()); + ok = true; + } + else + { + LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request"; + } + } + else + { + data.answers_.SetWorklist(false); + + if (data.findHandler_ != NULL) + { + std::list<DicomTag> sequencesToReturn; + + for (unsigned long i = 0; i < requestIdentifiers->card(); i++) + { + DcmElement* element = requestIdentifiers->getElement(i); + if (element && !element->isLeaf()) + { + const DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + if (sequence.card() != 0) + { + LOG(WARNING) << "Orthanc only supports sequence matching on worklists, " + << "ignoring C-FIND SCU constraint on tag (" << tag.Format() + << ") " << FromDcmtkBridge::GetTagName(*element); + } + + sequencesToReturn.push_back(tag); + } + } + + DicomMap input; + FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); + + data.findHandler_->Handle(data.answers_, input, sequencesToReturn, + *data.remoteIp_, *data.remoteAet_, + *data.calledAet_, modality.GetManufacturer()); + ok = true; + } + else + { + LOG(ERROR) << "No C-Find handler is installed, cannot handle this request"; + } + } + } + catch (OrthancException& e) + { + // Internal error! + LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); + } + + if (!ok) + { + response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; + *responseIdentifiers = NULL; + return; + } + + data.lastRequest_ = requestIdentifiers; + } + else if (data.lastRequest_ != requestIdentifiers) + { + // Internal error! + response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; + *responseIdentifiers = NULL; + return; + } + + if (responseCount <= static_cast<int>(data.answers_.GetSize())) + { + // There are pending results that are still to be sent + response->DimseStatus = STATUS_Pending; + *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1); + } + else if (data.answers_.IsComplete()) + { + // Success: All the results have been sent + response->DimseStatus = STATUS_Success; + *responseIdentifiers = NULL; + } + else + { + // Success, but the results were too numerous and had to be cropped + LOG(WARNING) << "Too many results for an incoming C-FIND query"; + response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest; + *responseIdentifiers = NULL; + } + } + } + + + OFCondition Internals::findScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + DicomServer::IRemoteModalities& modalities, + IFindRequestHandler* findHandler, + IWorklistRequestHandler* worklistHandler, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + FindScpData data; + data.modalities_ = &modalities; + data.findHandler_ = findHandler; + data.worklistHandler_ = worklistHandler; + data.lastRequest_ = NULL; + data.remoteIp_ = &remoteIp; + data.remoteAet_ = &remoteAet; + data.calledAet_ = &calledAet; + + OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, + FindScpCallback, &data, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0); + + // if some error occured, dump corresponding information and remove the outfile if necessary + if (cond.bad()) + { + OFString temp_str; + LOG(ERROR) << "Find SCP Failed: " << cond.text(); + } + + return cond; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/FindScp.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../DicomServer.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition findScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + DicomServer::IRemoteModalities& modalities, + IFindRequestHandler* findHandler, // can be NULL + IWorklistRequestHandler* worklistHandler, // can be NULL + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/MoveScp.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,283 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../../PrecompiledHeaders.h" +#include "MoveScp.h" + +#include <memory> + +#include "../../DicomParsing/FromDcmtkBridge.h" +#include "../../DicomParsing/ToDcmtkBridge.h" +#include "../../Logging.h" +#include "../../OrthancException.h" + +#include <boost/lexical_cast.hpp> + + +namespace Orthanc +{ + namespace + { + struct MoveScpData + { + std::string target_; + IMoveRequestHandler* handler_; + DcmDataset* lastRequest_; + unsigned int subOperationCount_; + unsigned int failureCount_; + unsigned int warningCount_; + std::auto_ptr<IMoveRequestIterator> iterator_; + const std::string* remoteIp_; + const std::string* remoteAet_; + const std::string* calledAet_; + }; + + + + static uint16_t GetMessageId(const DicomMap& message) + { + /** + * Retrieve the Message ID (0000,0110) for this C-MOVE request, if + * any. If present, this Message ID will be stored in the Move + * Originator Message ID (0000,1031) field of the C-MOVE response. + * http://dicom.nema.org/dicom/2013/output/chtml/part07/chapter_E.html + **/ + + const DicomValue* value = message.TestAndGetValue(DICOM_TAG_MESSAGE_ID); + + if (value != NULL && + !value->IsNull() && + !value->IsBinary()) + { + try + { + int tmp = boost::lexical_cast<int>(value->GetContent()); + if (tmp >= 0 && tmp <= 0xffff) + { + return static_cast<uint16_t>(tmp); + } + } + catch (boost::bad_lexical_cast&) + { + LOG(WARNING) << "Cannot convert the Message ID (\"" << value->GetContent() + << "\") of an incoming C-MOVE request to an integer, assuming zero"; + } + } + + return 0; + } + + + + void MoveScpCallback( + /* in */ + void *callbackData, + OFBool cancelled, + T_DIMSE_C_MoveRQ *request, + DcmDataset *requestIdentifiers, + int responseCount, + /* out */ + T_DIMSE_C_MoveRSP *response, + DcmDataset **responseIdentifiers, + DcmDataset **statusDetail) + { + bzero(response, sizeof(T_DIMSE_C_MoveRSP)); + *statusDetail = NULL; + *responseIdentifiers = NULL; + + MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData); + if (data.lastRequest_ == NULL) + { + DicomMap input; + FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); + + try + { + data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_, + *data.calledAet_, GetMessageId(input))); + + if (data.iterator_.get() == NULL) + { + // Internal error! + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + data.subOperationCount_ = data.iterator_->GetSubOperationCount(); + data.failureCount_ = 0; + data.warningCount_ = 0; + } + catch (OrthancException& e) + { + // Internal error! + LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + data.lastRequest_ = requestIdentifiers; + } + else if (data.lastRequest_ != requestIdentifiers) + { + // Internal error! + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + if (data.subOperationCount_ == 0) + { + response->DimseStatus = STATUS_Success; + } + else + { + IMoveRequestIterator::Status status; + + try + { + status = data.iterator_->DoNext(); + } + catch (OrthancException& e) + { + // Internal error! + LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); + response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; + return; + } + + if (status == IMoveRequestIterator::Status_Failure) + { + data.failureCount_++; + } + else if (status == IMoveRequestIterator::Status_Warning) + { + data.warningCount_++; + } + + if (responseCount < static_cast<int>(data.subOperationCount_)) + { + response->DimseStatus = STATUS_Pending; + } + else + { + response->DimseStatus = STATUS_Success; + } + } + + response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount; + response->NumberOfCompletedSubOperations = responseCount; + response->NumberOfFailedSubOperations = data.failureCount_; + response->NumberOfWarningSubOperations = data.warningCount_; + } + } + + + OFCondition Internals::moveScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IMoveRequestHandler& handler, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + MoveScpData data; + data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); + data.lastRequest_ = NULL; + data.handler_ = &handler; + data.remoteIp_ = &remoteIp; + data.remoteAet_ = &remoteAet; + data.calledAet_ = &calledAet; + + OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, + MoveScpCallback, &data, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0); + + // if some error occured, dump corresponding information and remove the outfile if necessary + if (cond.bad()) + { + OFString temp_str; + LOG(ERROR) << "Move SCP Failed: " << cond.text(); + } + + return cond; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/MoveScp.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,52 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IMoveRequestHandler.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition moveScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IMoveRequestHandler& handler, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/StoreScp.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,298 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../../PrecompiledHeaders.h" +#include "StoreScp.h" + +#include "../../DicomParsing/FromDcmtkBridge.h" +#include "../../DicomParsing/ToDcmtkBridge.h" +#include "../../OrthancException.h" +#include "../../Logging.h" + +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmnet/diutil.h> + + +namespace Orthanc +{ + namespace + { + struct StoreCallbackData + { + IStoreRequestHandler* handler; + const std::string* remoteIp; + const char* remoteAET; + const char* calledAET; + const char* modality; + const char* affectedSOPInstanceUID; + uint32_t messageID; + }; + + + static void + storeScpCallback( + void *callbackData, + T_DIMSE_StoreProgress *progress, + T_DIMSE_C_StoreRQ *req, + char * /*imageFileName*/, DcmDataset **imageDataSet, + T_DIMSE_C_StoreRSP *rsp, + DcmDataset **statusDetail) + /* + * This function.is used to indicate progress when storescp receives instance data over the + * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd) + * this function will store the data set which was received over the network to a file. + * Earlier calls to this function will simply cause some information to be dumped to stdout. + * + * Parameters: + * callbackData - [in] data for this callback function + * progress - [in] The state of progress. (identifies if this is the initial or final call + * to this function, or a call in between these two calls. + * req - [in] The original store request message. + * imageFileName - [in] The path to and name of the file the information shall be written to. + * imageDataSet - [in] The data set which shall be stored in the image file + * rsp - [inout] the C-STORE-RSP message (will be sent after the call to this function) + * statusDetail - [inout] This variable can be used to capture detailed information with regard to + * the status information which is captured in the status element (0000,0900). Note + * that this function does specify any such information, the pointer will be set to NULL. + */ + { + StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData); + + DIC_UI sopClass; + DIC_UI sopInstance; + + // if this is the final call of this function, save the data which was received to a file + // (note that we could also save the image somewhere else, put it in database, etc.) + if (progress->state == DIMSE_StoreEnd) + { + OFString tmpStr; + + // do not send status detail information + *statusDetail = NULL; + + // Concerning the following line: an appropriate status code is already set in the resp structure, + // it need not be success. For example, if the caller has already detected an out of resources problem + // then the status will reflect this. The callback function is still called to allow cleanup. + //rsp->DimseStatus = STATUS_Success; + + // we want to write the received information to a file only if this information + // is present and the options opt_bitPreserving and opt_ignore are not set. + if ((imageDataSet != NULL) && (*imageDataSet != NULL)) + { + DicomMap summary; + Json::Value dicomJson; + std::string buffer; + + try + { + FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet); + FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet); + + if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) + { + LOG(ERROR) << "cannot write DICOM file to memory"; + rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; + } + } + catch (...) + { + rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; + } + + // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond + // to those mentioned in the request. If not, set the status in the response message variable. + if (rsp->DimseStatus == STATUS_Success) + { + // which SOP class and SOP instance ? + if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) + { + //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); + rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; + } + else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0) + { + rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; + } + else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0) + { + rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; + } + else + { + try + { + cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); + } + catch (OrthancException& e) + { + rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; + + if (e.GetErrorCode() == ErrorCode_InexistentTag) + { + summary.LogMissingTagsForStore(); + } + else + { + LOG(ERROR) << "Exception while storing DICOM: " << e.What(); + } + } + } + } + } + } + } + } + +/* + * This function processes a DIMSE C-STORE-RQ commmand that was + * received over the network connection. + * + * Parameters: + * assoc - [in] The association (network connection to another DICOM application). + * msg - [in] The DIMSE C-STORE-RQ message that was received. + * presID - [in] The ID of the presentation context which was specified in the PDV which contained + * the DIMSE command. + */ + OFCondition Internals::storeScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IStoreRequestHandler& handler, + const std::string& remoteIp) + { + OFCondition cond = EC_Normal; + T_DIMSE_C_StoreRQ *req; + + // assign the actual information of the C-STORE-RQ command to a local variable + req = &msg->msg.CStoreRQ; + + // intialize some variables + StoreCallbackData data; + data.handler = &handler; + data.remoteIp = &remoteIp; + data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); + if (data.modality == NULL) + data.modality = "UNKNOWN"; + + data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; + data.messageID = req->MessageID; + if (assoc && assoc->params) + { + data.remoteAET = assoc->params->DULparams.callingAPTitle; + data.calledAET = assoc->params->DULparams.calledAPTitle; + } + else + { + data.remoteAET = ""; + data.calledAET = ""; + } + + DcmFileFormat dcmff; + + // store SourceApplicationEntityTitle in metaheader + if (assoc && assoc->params) + { + const char *aet = assoc->params->DULparams.callingAPTitle; + if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet); + } + + // define an address where the information which will be received over the network will be stored + DcmDataset *dset = dcmff.getDataset(); + + cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, + storeScpCallback, &data, + /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0); + + // if some error occured, dump corresponding information and remove the outfile if necessary + if (cond.bad()) + { + OFString temp_str; + LOG(ERROR) << "Store SCP Failed: " << cond.text(); + } + + // return return value + return cond; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/Internals/StoreScp.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,50 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../IStoreRequestHandler.h" + +#include <dcmtk/dcmnet/dimse.h> + +namespace Orthanc +{ + namespace Internals + { + OFCondition storeScp(T_ASC_Association * assoc, + T_DIMSE_Message * msg, + T_ASC_PresentationContextID presID, + IStoreRequestHandler& handler, + const std::string& remoteIp); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,128 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "RemoteModalityParameters.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +#include <boost/lexical_cast.hpp> +#include <stdexcept> + +namespace Orthanc +{ + RemoteModalityParameters::RemoteModalityParameters() : + aet_("ORTHANC"), + host_("127.0.0.1"), + port_(104), + manufacturer_(ModalityManufacturer_Generic) + { + } + + RemoteModalityParameters::RemoteModalityParameters(const std::string& aet, + const std::string& host, + uint16_t port, + ModalityManufacturer manufacturer) + { + SetApplicationEntityTitle(aet); + SetHost(host); + SetPort(port); + SetManufacturer(manufacturer); + } + + + void RemoteModalityParameters::FromJson(const Json::Value& modality) + { + if (!modality.isArray() || + (modality.size() != 3 && modality.size() != 4)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + SetApplicationEntityTitle(modality.get(0u, "").asString()); + SetHost(modality.get(1u, "").asString()); + + const Json::Value& portValue = modality.get(2u, ""); + try + { + int tmp = portValue.asInt(); + + if (tmp <= 0 || tmp >= 65535) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + SetPort(static_cast<uint16_t>(tmp)); + } + catch (std::runtime_error /* error inside JsonCpp */) + { + try + { + SetPort(boost::lexical_cast<uint16_t>(portValue.asString())); + } + catch (boost::bad_lexical_cast) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + if (modality.size() == 4) + { + const std::string& manufacturer = modality.get(3u, "").asString(); + + try + { + SetManufacturer(manufacturer); + } + catch (OrthancException&) + { + LOG(ERROR) << "Unknown modality manufacturer: \"" << manufacturer << "\""; + throw; + } + } + else + { + SetManufacturer(ModalityManufacturer_Generic); + } + } + + void RemoteModalityParameters::ToJson(Json::Value& value) const + { + value = Json::arrayValue; + value.append(GetApplicationEntityTitle()); + value.append(GetHost()); + value.append(GetPort()); + value.append(EnumerationToString(GetManufacturer())); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,109 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Enumerations.h" + +#include <stdint.h> +#include <string> +#include <json/json.h> + +namespace Orthanc +{ + class RemoteModalityParameters + { + private: + std::string aet_; + std::string host_; + uint16_t port_; + ModalityManufacturer manufacturer_; + + public: + RemoteModalityParameters(); + + RemoteModalityParameters(const std::string& aet, + const std::string& host, + uint16_t port, + ModalityManufacturer manufacturer); + + const std::string& GetApplicationEntityTitle() const + { + return aet_; + } + + void SetApplicationEntityTitle(const std::string& aet) + { + aet_ = aet; + } + + const std::string& GetHost() const + { + return host_; + } + + void SetHost(const std::string& host) + { + host_ = host; + } + + uint16_t GetPort() const + { + return port_; + } + + void SetPort(uint16_t port) + { + port_ = port; + } + + ModalityManufacturer GetManufacturer() const + { + return manufacturer_; + } + + void SetManufacturer(ModalityManufacturer manufacturer) + { + manufacturer_ = manufacturer; + } + + void SetManufacturer(const std::string& manufacturer) + { + manufacturer_ = StringToModalityManufacturer(manufacturer); + } + + void FromJson(const Json::Value& modality); + + void ToJson(Json::Value& value) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/ReusableDicomUserConnection.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,188 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ReusableDicomUserConnection.h" + +#include "../Logging.h" +#include "../OrthancException.h" + +namespace Orthanc +{ + static boost::posix_time::ptime Now() + { + return boost::posix_time::microsec_clock::local_time(); + } + + void ReusableDicomUserConnection::Open(const std::string& localAet, + const RemoteModalityParameters& remote) + { + if (connection_ != NULL && + connection_->GetLocalApplicationEntityTitle() == localAet && + connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && + connection_->GetRemoteHost() == remote.GetHost() && + connection_->GetRemotePort() == remote.GetPort() && + connection_->GetRemoteManufacturer() == remote.GetManufacturer()) + { + // The current connection can be reused + LOG(INFO) << "Reusing the previous SCU connection"; + return; + } + + Close(); + + connection_ = new DicomUserConnection(); + connection_->SetLocalApplicationEntityTitle(localAet); + connection_->SetRemoteModality(remote); + connection_->Open(); + } + + void ReusableDicomUserConnection::Close() + { + if (connection_ != NULL) + { + delete connection_; + connection_ = NULL; + } + } + + void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) + { + for (;;) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + if (!that->continue_) + { + //LOG(INFO) << "Finishing the thread watching the global SCU connection"; + return; + } + + { + boost::mutex::scoped_lock lock(that->mutex_); + if (that->connection_ != NULL && + Now() >= that->lastUse_ + that->timeBeforeClose_) + { + LOG(INFO) << "Closing the global SCU connection after timeout"; + that->Close(); + } + } + } + } + + + ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, + const std::string& localAet, + const RemoteModalityParameters& remote) : + ::Orthanc::Locker(that) + { + that.Open(localAet, remote); + connection_ = that.connection_; + } + + + DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() + { + if (connection_ == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + return *connection_; + } + + ReusableDicomUserConnection::ReusableDicomUserConnection() : + connection_(NULL), + timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds + { + lastUse_ = Now(); + continue_ = true; + closeThread_ = boost::thread(CloseThread, this); + } + + ReusableDicomUserConnection::~ReusableDicomUserConnection() + { + if (continue_) + { + LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; + Finalize(); + } + } + + void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) + { + boost::mutex::scoped_lock lock(mutex_); + + if (ms == 0) + { + ms = 1; + } + + timeBeforeClose_ = boost::posix_time::milliseconds(ms); + } + + void ReusableDicomUserConnection::Lock() + { + mutex_.lock(); + } + + void ReusableDicomUserConnection::Unlock() + { + if (connection_ != NULL && + connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) + { + // "storescp" from DCMTK has problems when reusing a + // connection. Always close. + Close(); + } + + lastUse_ = Now(); + mutex_.unlock(); + } + + + void ReusableDicomUserConnection::Finalize() + { + if (continue_) + { + continue_ = false; + + if (closeThread_.joinable()) + { + closeThread_.join(); + } + + Close(); + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomNetworking/ReusableDicomUserConnection.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,89 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DicomUserConnection.h" +#include "../../Core/MultiThreading/Locker.h" + +#include <boost/thread.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +namespace Orthanc +{ + class ReusableDicomUserConnection : public ILockable + { + private: + boost::mutex mutex_; + DicomUserConnection* connection_; + bool continue_; + boost::posix_time::time_duration timeBeforeClose_; + boost::posix_time::ptime lastUse_; + boost::thread closeThread_; + + void Open(const std::string& localAet, + const RemoteModalityParameters& remote); + + void Close(); + + static void CloseThread(ReusableDicomUserConnection* that); + + protected: + virtual void Lock(); + + virtual void Unlock(); + + public: + class Locker : public ::Orthanc::Locker + { + private: + DicomUserConnection* connection_; + + public: + Locker(ReusableDicomUserConnection& that, + const std::string& localAet, + const RemoteModalityParameters& remote); + + DicomUserConnection& GetConnection(); + }; + + ReusableDicomUserConnection(); + + virtual ~ReusableDicomUserConnection(); + + void SetMillisecondsBeforeClose(uint64_t ms); + + void Finalize(); + }; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomDirWriter.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,510 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: DCMTK 3.6.0 + Module: http://dicom.offis.de/dcmtk.php.en + +Copyright (C) 1994-2011, OFFIS e.V. +All rights reserved. + +This software and supporting documentation were developed by + + OFFIS e.V. + R&D Division Health + Escherweg 2 + 26121 Oldenburg, Germany + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +- Neither the name of OFFIS nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + + +/*** + + Validation: + + # sudo apt-get install dicom3tools + # dciodvfy DICOMDIR 2>&1 | less + # dcentvfy DICOMDIR 2>&1 | less + + http://www.dclunie.com/dicom3tools/dciodvfy.html + + DICOMDIR viewer working with Wine under Linux: + http://www.microdicom.com/ + + ***/ + + +#include "../PrecompiledHeaders.h" +#include "DicomDirWriter.h" + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "../TemporaryFile.h" +#include "../Toolbox.h" +#include "../SystemToolbox.h" + +#include <dcmtk/dcmdata/dcdicdir.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcddirif.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcsequen.h> +#include <dcmtk/dcmdata/dcostrmf.h> +#include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ +#include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ + +#include <memory> + +namespace Orthanc +{ + class DicomDirWriter::PImpl + { + private: + std::string fileSetId_; + TemporaryFile file_; + std::auto_ptr<DcmDicomDir> dir_; + + typedef std::pair<ResourceType, std::string> IndexKey; + typedef std::map<IndexKey, DcmDirectoryRecord* > Index; + Index index_; + + + DcmDicomDir& GetDicomDir() + { + if (dir_.get() == NULL) + { + dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), + fileSetId_.c_str())); + //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8)); + } + + return *dir_; + } + + + DcmDirectoryRecord& GetRoot() + { + return GetDicomDir().getRootRecord(); + } + + + static bool GetUtf8TagValue(std::string& result, + DcmItem& source, + Encoding encoding, + const DcmTagKey& key) + { + DcmElement* element = NULL; + + if (source.findAndGetElement(key, element).good()) + { + char* s = NULL; + if (element->isLeaf() && + element->getString(s).good() && + s != NULL) + { + result = Toolbox::ConvertToUtf8(s, encoding); + return true; + } + } + + result.clear(); + return false; + } + + + static void SetTagValue(DcmDirectoryRecord& target, + const DcmTagKey& key, + const std::string& valueUtf8) + { + std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii); + + if (!target.putAndInsertString(key, s.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + + static bool CopyString(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key, + bool optional, + bool copyEmpty) + { + if (optional && + !source.tagExistsWithValue(key) && + !(copyEmpty && source.tagExists(key))) + { + return false; + } + + std::string value; + bool found = GetUtf8TagValue(value, source, encoding, key); + + SetTagValue(target, key, value); + return found; + } + + + static void CopyStringType1(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, false, false); + } + + static void CopyStringType1C(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, true, false); + } + + static void CopyStringType2(DcmDirectoryRecord& target, + DcmDataset& source, + Encoding encoding, + const DcmTagKey& key) + { + CopyString(target, source, encoding, key, false, true); + } + + + public: + PImpl() : fileSetId_("ORTHANC_MEDIA") + { + } + + void FillPatient(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildPatientRecord()" + + CopyStringType1C(record, dicom, encoding, DCM_PatientID); + CopyStringType2(record, dicom, encoding, DCM_PatientName); + } + + void FillStudy(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildStudyRecord()" + + std::string nowDate, nowTime; + SystemToolbox::GetNowDicom(nowDate, nowTime); + + std::string studyDate; + if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate)) + { + studyDate = nowDate; + } + + std::string studyTime; + if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime)) + { + studyTime = nowTime; + } + + /* copy attribute values from dataset to study record */ + SetTagValue(record, DCM_StudyDate, studyDate); + SetTagValue(record, DCM_StudyTime, studyTime); + CopyStringType2(record, dicom, encoding, DCM_StudyDescription); + CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID); + /* use type 1C instead of 1 in order to avoid unwanted overwriting */ + CopyStringType1C(record, dicom, encoding, DCM_StudyID); + CopyStringType2(record, dicom, encoding, DCM_AccessionNumber); + } + + void FillSeries(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding) + { + // cf. "DicomDirInterface::buildSeriesRecord()" + + /* copy attribute values from dataset to series record */ + CopyStringType1(record, dicom, encoding, DCM_Modality); + CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID); + /* use type 1C instead of 1 in order to avoid unwanted overwriting */ + CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber); + } + + void FillInstance(DcmDirectoryRecord& record, + DcmDataset& dicom, + Encoding encoding, + DcmMetaInfo& metaInfo, + const char* path) + { + // cf. "DicomDirInterface::buildImageRecord()" + + /* copy attribute values from dataset to image record */ + CopyStringType1(record, dicom, encoding, DCM_InstanceNumber); + //CopyElementType1C(record, dicom, encoding, DCM_ImageType); + + // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); + + std::string sopClassUid, sopInstanceUid, transferSyntaxUid; + if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) || + !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) || + !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + SetTagValue(record, DCM_ReferencedFileID, path); + SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid); + SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid); + SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid); + } + + + + bool CreateResource(DcmDirectoryRecord*& target, + ResourceType level, + ParsedDicomFile& dicom, + const char* filename, + const char* path) + { + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + Encoding encoding = dicom.GetEncoding(); + + bool found; + std::string id; + E_DirRecType type; + + switch (level) + { + case ResourceType_Patient: + found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID); + type = ERT_Patient; + break; + + case ResourceType_Study: + found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID); + type = ERT_Study; + break; + + case ResourceType_Series: + found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID); + type = ERT_Series; + break; + + case ResourceType_Instance: + found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID); + type = ERT_Image; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + if (!found) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + IndexKey key = std::make_pair(level, std::string(id.c_str())); + Index::iterator it = index_.find(key); + + if (it != index_.end()) + { + target = it->second; + return false; // Already existing + } + + std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); + + switch (level) + { + case ResourceType_Patient: + FillPatient(*record, dataset, encoding); + break; + + case ResourceType_Study: + FillStudy(*record, dataset, encoding); + break; + + case ResourceType_Series: + FillSeries(*record, dataset, encoding); + break; + + case ResourceType_Instance: + FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet); + + target = record.get(); + GetRoot().insertSub(record.release()); + index_[key] = target; + + return true; // Newly created + } + + void Read(std::string& s) + { + if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, + EET_UndefinedLength /*encodingType*/, + EGL_withoutGL /*groupLength*/).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + + file_.Read(s); + } + + void SetFileSetId(const std::string& id) + { + dir_.reset(NULL); + fileSetId_ = id; + } + }; + + + DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) + { + } + + DicomDirWriter::~DicomDirWriter() + { + if (pimpl_) + { + delete pimpl_; + } + } + + void DicomDirWriter::SetFileSetId(const std::string& id) + { + pimpl_->SetFileSetId(id); + } + + void DicomDirWriter::Add(const std::string& directory, + const std::string& filename, + ParsedDicomFile& dicom) + { + std::string path; + if (directory.empty()) + { + path = filename; + } + else + { + if (directory[directory.length() - 1] == '/' || + directory[directory.length() - 1] == '\\') + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + path = directory + '\\' + filename; + } + + DcmDirectoryRecord* instance; + bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str()); + if (isNewInstance) + { + DcmDirectoryRecord* series; + bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL); + series->insertSub(instance); + + if (isNewSeries) + { + DcmDirectoryRecord* study; + bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL); + study->insertSub(series); + + if (isNewStudy) + { + DcmDirectoryRecord* patient; + pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL); + patient->insertSub(study); + } + } + } + } + + void DicomDirWriter::Encode(std::string& target) + { + pimpl_->Read(target); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomDirWriter.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,62 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ParsedDicomFile.h" + +#include <boost/noncopyable.hpp> + +namespace Orthanc +{ + class DicomDirWriter : public boost::noncopyable + { + private: + class PImpl; + PImpl* pimpl_; + + public: + DicomDirWriter(); + + ~DicomDirWriter(); + + void SetFileSetId(const std::string& id); + + void Add(const std::string& directory, + const std::string& filename, + ParsedDicomFile& dicom); + + void Encode(std::string& target); + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomModification.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,1132 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomModification.h" + +#include "../Logging.h" +#include "../OrthancException.h" +#include "FromDcmtkBridge.h" + +#include <memory> // For std::auto_ptr + + +static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = + "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; + +static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = + "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile"; + +namespace Orthanc +{ + bool DicomModification::CancelReplacement(const DicomTag& tag) + { + Replacements::iterator it = replacements_.find(tag); + + if (it != replacements_.end()) + { + delete it->second; + replacements_.erase(it); + return true; + } + else + { + return false; + } + } + + + void DicomModification::ReplaceInternal(const DicomTag& tag, + const Json::Value& value) + { + Replacements::iterator it = replacements_.find(tag); + + if (it != replacements_.end()) + { + delete it->second; + it->second = NULL; // In the case of an exception during the clone + it->second = new Json::Value(value); // Clone + } + else + { + replacements_[tag] = new Json::Value(value); // Clone + } + } + + + void DicomModification::ClearReplacements() + { + for (Replacements::iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + delete it->second; + } + + replacements_.clear(); + } + + + void DicomModification::MarkNotOrthancAnonymization() + { + Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); + + if (it != replacements_.end() && + (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || + it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) + { + delete it->second; + replacements_.erase(it); + } + } + + + void DicomModification::MapDicomIdentifier(ParsedDicomFile& dicom, + ResourceType level) + { + std::auto_ptr<DicomTag> tag; + + switch (level) + { + case ResourceType_Study: + tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + break; + + case ResourceType_Series: + tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + break; + + case ResourceType_Instance: + tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + std::string original; + if (!dicom.GetTagValue(original, *tag)) + { + original = ""; + } + + std::string mapped; + + UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); + if (previous == uidMap_.end()) + { + mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); + uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); + } + else + { + mapped = previous->second; + } + + dicom.Replace(*tag, mapped, false /* don't try and decode data URI scheme for UIDs */, DicomReplaceMode_InsertIfAbsent); + } + + DicomModification::DicomModification() : + removePrivateTags_(false), + level_(ResourceType_Instance), + allowManualIdentifiers_(true), + keepStudyInstanceUid_(false), + keepSeriesInstanceUid_(false) + { + } + + DicomModification::~DicomModification() + { + ClearReplacements(); + } + + void DicomModification::Keep(const DicomTag& tag) + { + bool wasRemoved = IsRemoved(tag); + bool wasCleared = IsCleared(tag); + + removals_.erase(tag); + clearings_.erase(tag); + + bool wasReplaced = CancelReplacement(tag); + + if (tag == DICOM_TAG_STUDY_INSTANCE_UID) + { + keepStudyInstanceUid_ = true; + } + else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) + { + keepSeriesInstanceUid_ = true; + } + else if (tag.IsPrivate()) + { + privateTagsToKeep_.insert(tag); + } + else if (!wasRemoved && + !wasReplaced && + !wasCleared) + { + LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format(); + } + + MarkNotOrthancAnonymization(); + } + + void DicomModification::Remove(const DicomTag& tag) + { + removals_.insert(tag); + clearings_.erase(tag); + CancelReplacement(tag); + privateTagsToKeep_.erase(tag); + + MarkNotOrthancAnonymization(); + } + + void DicomModification::Clear(const DicomTag& tag) + { + removals_.erase(tag); + clearings_.insert(tag); + CancelReplacement(tag); + privateTagsToKeep_.erase(tag); + + MarkNotOrthancAnonymization(); + } + + bool DicomModification::IsRemoved(const DicomTag& tag) const + { + return removals_.find(tag) != removals_.end(); + } + + bool DicomModification::IsCleared(const DicomTag& tag) const + { + return clearings_.find(tag) != clearings_.end(); + } + + void DicomModification::Replace(const DicomTag& tag, + const Json::Value& value, + bool safeForAnonymization) + { + clearings_.erase(tag); + removals_.erase(tag); + privateTagsToKeep_.erase(tag); + ReplaceInternal(tag, value); + + if (!safeForAnonymization) + { + MarkNotOrthancAnonymization(); + } + } + + + bool DicomModification::IsReplaced(const DicomTag& tag) const + { + return replacements_.find(tag) != replacements_.end(); + } + + const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const + { + Replacements::const_iterator it = replacements_.find(tag); + + if (it == replacements_.end()) + { + throw OrthancException(ErrorCode_InexistentItem); + } + else + { + return *it->second; + } + } + + + std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const + { + const Json::Value& json = GetReplacement(tag); + + if (json.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + else + { + return json.asString(); + } + } + + + void DicomModification::SetRemovePrivateTags(bool removed) + { + removePrivateTags_ = removed; + + if (!removed) + { + MarkNotOrthancAnonymization(); + } + } + + void DicomModification::SetLevel(ResourceType level) + { + uidMap_.clear(); + level_ = level; + + if (level != ResourceType_Patient) + { + MarkNotOrthancAnonymization(); + } + } + + + void DicomModification::SetupAnonymization2008() + { + // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles + // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf + + removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() + removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number + removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name + removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name + removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID + removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) + //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) + removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids + removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number + removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() + //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() + removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID + removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals_.insert(DicomTag(0x0040, 0xa124)); // UID + removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID + removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID + removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + + // Some more removals (from the experience of DICOM files at the CHU of Liege) + removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address + removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + + // Set the DeidentificationMethod tag + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008); + } + + +#if 0 + /** + * This is a manual implementation by Alain Mazy. Only kept for reference. + * https://bitbucket.org/sjodogne/orthanc/commits/c6defdc4c611fca2ab528ba2c6937a742e0329a8?at=issue-46-anonymization + **/ + + void DicomModification::SetupAnonymization2011() + { + // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles + // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf + + removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID + removals_.insert(DicomTag(0x0000, 0x1001)); // Requested SOP Instance UID + removals_.insert(DicomTag(0x0002, 0x0003)); // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances + removals_.insert(DicomTag(0x0004, 0x1511)); // Referenced SOP Instance UID in File + removals_.insert(DicomTag(0x0008, 0x0010)); // Irradiation Event UID + removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID + //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() + clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date + clearings_.insert(DicomTag(0x0008, 0x0021)); // Series Date + clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time + clearings_.insert(DicomTag(0x0008, 0x0031)); // Series Time + removals_.insert(DicomTag(0x0008, 0x0022)); // Acquisition Date + removals_.insert(DicomTag(0x0008, 0x0023)); // Content Date + removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date + removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date + removals_.insert(DicomTag(0x0008, 0x002a)); // Acquisition DateTime + removals_.insert(DicomTag(0x0008, 0x0032)); // Acquisition Time + removals_.insert(DicomTag(0x0008, 0x0033)); // Content Time + removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time + removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time + removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number + removals_.insert(DicomTag(0x0008, 0x0058)); // Failed SOP Instance UID List + removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name + removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals_.insert(DicomTag(0x0008, 0x0082)); // Institution Code Sequence + removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician's Identification Sequence + removals_.insert(DicomTag(0x0008, 0x010d)); // Context Group Extension Creator UID + removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + removals_.insert(DicomTag(0x0008, 0x0300)); // Current Patient Location + removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name + removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physicians Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals_.insert(DicomTag(0x0008, 0x1062)); // Physician Reading Study Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name + removals_.insert(DicomTag(0x0008, 0x1072)); // Operators' Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence + removals_.insert(DicomTag(0x0008, 0x1110)); // Referenced Study Sequence + removals_.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence + removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence + removals_.insert(DicomTag(0x0008, 0x1140)); // Referenced Image Sequence + removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID + removals_.insert(DicomTag(0x0008, 0x1195)); // Transaction UID + removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + removals_.insert(DicomTag(0x0008, 0x2112)); // Source Image Sequence + removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments + removals_.insert(DicomTag(0x0008, 0x9123)); // Creator Version UID + //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) + //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) + removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence + removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence + removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence + removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids + removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence + removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name + removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address + removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification + removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name + removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank + removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service + removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies + removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence + removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence + removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers + removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status + removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status + removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date + removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference + removals_.insert(DicomTag(0x0010, 0x2203)); // Patient's Sex Neutered + removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person + removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization + removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals_.insert(DicomTag(0x0018, 0x0010)); // Contrast Bolus Agent + removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number + removals_.insert(DicomTag(0x0018, 0x1002)); // Device UID + removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID + removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID + removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID + removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID + removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name + removals_.insert(DicomTag(0x0018, 0x1400)); // Acquisition Device Processing Description + removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments + removals_.insert(DicomTag(0x0018, 0x700a)); // Detector ID + removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description + removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description + //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() + //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() + removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID + removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID + removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer + removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description + removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments + removals_.insert(DicomTag(0x0020, 0x9161)); // Concatenation UID + removals_.insert(DicomTag(0x0020, 0x9164)); // Dimension Organization UID + //removals_.insert(DicomTag(0x0028, 0x1199)); // Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances + //removals_.insert(DicomTag(0x0028, 0x1214)); // Large Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances + removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments + removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer + removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location + removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title + removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study + removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service + removals_.insert(DicomTag(0x0032, 0x1060)); // Requesting Procedure Description + removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent + removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments + removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID + removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID + removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence + removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date + removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time + removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description + removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs + removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description + removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence + removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State + removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments + removals_.insert(DicomTag(0x0038, 0x1234)); // Referenced Patient Alias Sequence + removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title + removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date + removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time + removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name + removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence + removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name + removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location + removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication + removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title + removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name + removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location + removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0248)); // Performed Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on Performed Procedure Step + removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence + removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID + removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results + removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipient of Results Identification Sequence + removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements + removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location + removals_.insert(DicomTag(0x0040, 0x1101)); // Person Identification Code Sequence + removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address + removals_.insert(DicomTag(0x0040, 0x1103)); // Person Telephone Numbers + removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments + removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By + removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location + removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number + removals_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number of Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number of Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments + removals_.insert(DicomTag(0x0040, 0x4023)); // Referenced General Purpose Scheduled Procedure Step Transaction UID + removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization + removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name + removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization + removals_.insert(DicomTag(0x0040, 0xa073)); // Verifying Observer Sequence + removals_.insert(DicomTag(0x0040, 0xa075)); // Verifying Observer Name + removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence + removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence + removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence + removals_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence + removals_.insert(DicomTag(0x0040, 0xa123)); // Person Name + removals_.insert(DicomTag(0x0040, 0xa124)); // UID + removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description + removals_.insert(DicomTag(0x0040, 0xdb0c)); // Template Extension Organization UID + removals_.insert(DicomTag(0x0040, 0xdb0d)); // Template Extension Creator UID + removals_.insert(DicomTag(0x0070, 0x0001)); // Graphic Annotation Sequence + removals_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name + removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence + removals_.insert(DicomTag(0x0070, 0x031a)); // Fiducial UID + removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID + removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence + removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title + removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject + removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author + removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Key Words + removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID + removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence + removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence + removals_.insert(DicomTag(0x0400, 0x0404)); // MAC + removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence + removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + removals_.insert(DicomTag(0x2030, 0x0020)); // Text String + removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID + removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID + removals_.insert(DicomTag(0x300a, 0x0013)); // Dose Reference UID + removals_.insert(DicomTag(0x300e, 0x0008)); // Reviewer Name + removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary + removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments + removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer + removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder + removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber + removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text + removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author + removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence + removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation + removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description + removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence + removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name + removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address + removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer + removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions + removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments + removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signature Sequence + removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding + //removals_.insert(DicomTag(0x60xx, 0x4000)); // Overlay Comments => TODO + //removals_.insert(DicomTag(0x60xx, 0x3000)); // Overlay Data => TODO + + // Set the DeidentificationMethod tag + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011); + } +#endif + + + + void DicomModification::SetupAnonymization2017c() + { + /** + * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security + * and System Management Profiles), "basic profile" column. It was + * generated automatically with the + * "../Resources/GenerateAnonymizationProfile.py" script. + * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf + **/ + + // TODO: (50xx,xxxx) with rule X // Curve Data + // TODO: (60xx,3000) with rule X // Overlay Data + // TODO: (60xx,4000) with rule X // Overlay Comments + // Tag (0x0008, 0x0018) is set in Apply() // SOP Instance UID + // Tag (0x0010, 0x0010) is set below (*) // Patient's Name + // Tag (0x0010, 0x0020) is set below (*) // Patient ID + // Tag (0x0020, 0x000d) is set in Apply() // Study Instance UID + // Tag (0x0020, 0x000e) is set in Apply() // Series Instance UID + clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date + clearings_.insert(DicomTag(0x0008, 0x0023)); /* Z/D */ // Content Date + clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time + clearings_.insert(DicomTag(0x0008, 0x0033)); /* Z/D */ // Content Time + clearings_.insert(DicomTag(0x0008, 0x0050)); // Accession Number + clearings_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name + clearings_.insert(DicomTag(0x0008, 0x009c)); // Consulting Physician's Name + clearings_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date + clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex + clearings_.insert(DicomTag(0x0018, 0x0010)); /* Z/D */ // Contrast Bolus Agent + clearings_.insert(DicomTag(0x0020, 0x0010)); // Study ID + clearings_.insert(DicomTag(0x0040, 0x1101)); /* D */ // Person Identification Code Sequence + clearings_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number / Imaging Service Request + clearings_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number / Imaging Service Request + clearings_.insert(DicomTag(0x0040, 0xa073)); /* D */ // Verifying Observer Sequence + clearings_.insert(DicomTag(0x0040, 0xa075)); /* D */ // Verifying Observer Name + clearings_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence + clearings_.insert(DicomTag(0x0040, 0xa123)); /* D */ // Person Name + clearings_.insert(DicomTag(0x0070, 0x0001)); /* D */ // Graphic Annotation Sequence + clearings_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name + removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID + removals_.insert(DicomTag(0x0000, 0x1001)); /* TODO UID */ // Requested SOP Instance UID + removals_.insert(DicomTag(0x0002, 0x0003)); /* TODO UID */ // Media Storage SOP Instance UID + removals_.insert(DicomTag(0x0004, 0x1511)); /* TODO UID */ // Referenced SOP Instance UID in File + removals_.insert(DicomTag(0x0008, 0x0014)); /* TODO UID */ // Instance Creator UID + removals_.insert(DicomTag(0x0008, 0x0015)); // Instance Coercion DateTime + removals_.insert(DicomTag(0x0008, 0x0021)); /* X/D */ // Series Date + removals_.insert(DicomTag(0x0008, 0x0022)); /* X/Z */ // Acquisition Date + removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date + removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date + removals_.insert(DicomTag(0x0008, 0x002a)); /* X/D */ // Acquisition DateTime + removals_.insert(DicomTag(0x0008, 0x0031)); /* X/D */ // Series Time + removals_.insert(DicomTag(0x0008, 0x0032)); /* X/Z */ // Acquisition Time + removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time + removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time + removals_.insert(DicomTag(0x0008, 0x0058)); /* TODO UID */ // Failed SOP Instance UID List + removals_.insert(DicomTag(0x0008, 0x0080)); /* X/Z/D */ // Institution Name + removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address + removals_.insert(DicomTag(0x0008, 0x0082)); /* X/Z/D */ // Institution Code Sequence + removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address + removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers + removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician Identification Sequence + removals_.insert(DicomTag(0x0008, 0x009d)); // Consulting Physician Identification Sequence + removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC + removals_.insert(DicomTag(0x0008, 0x1010)); /* X/Z/D */ // Station Name + removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description + removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description + removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name + removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record + removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name + removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study + removals_.insert(DicomTag(0x0008, 0x1062)); // Physician(s) Reading Study Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1070)); /* X/Z/D */ // Operators' Name + removals_.insert(DicomTag(0x0008, 0x1072)); /* X/D */ // Operators' Identification Sequence + removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description + removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence + removals_.insert(DicomTag(0x0008, 0x1110)); /* X/Z */ // Referenced Study Sequence + removals_.insert(DicomTag(0x0008, 0x1111)); /* X/Z/D */ // Referenced Performed Procedure Step Sequence + removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence + removals_.insert(DicomTag(0x0008, 0x1140)); /* X/Z/U* */ // Referenced Image Sequence + removals_.insert(DicomTag(0x0008, 0x1155)); /* TODO UID */ // Referenced SOP Instance UID + removals_.insert(DicomTag(0x0008, 0x1195)); /* TODO UID */ // Transaction UID + removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description + removals_.insert(DicomTag(0x0008, 0x2112)); /* X/Z/U* */ // Source Image Sequence + removals_.insert(DicomTag(0x0008, 0x3010)); /* TODO UID */ // Irradiation Event UID + removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments + removals_.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID + removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time + removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence + removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence + removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence + removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient IDs + removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names + removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence + removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name + removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age + removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size + removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight + removals_.insert(DicomTag(0x0010, 0x1040)); // Patient Address + removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification + removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name + removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank + removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service + removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator + removals_.insert(DicomTag(0x0010, 0x1100)); // Referenced Patient Photo Sequence + removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts + removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies + removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence + removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence + removals_.insert(DicomTag(0x0010, 0x2154)); // Patient's Telephone Numbers + removals_.insert(DicomTag(0x0010, 0x2155)); // Patient's Telecom Information + removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group + removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation + removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status + removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History + removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status + removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date + removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference + removals_.insert(DicomTag(0x0010, 0x2203)); /* X/Z */ // Patient Sex Neutered + removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person + removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization + removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments + removals_.insert(DicomTag(0x0018, 0x1000)); /* X/Z/D */ // Device Serial Number + removals_.insert(DicomTag(0x0018, 0x1002)); /* TODO UID */ // Device UID + removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID + removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID + removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID + removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID + removals_.insert(DicomTag(0x0018, 0x1030)); /* X/D */ // Protocol Name + removals_.insert(DicomTag(0x0018, 0x1400)); /* X/D */ // Acquisition Device Processing Description + removals_.insert(DicomTag(0x0018, 0x2042)); /* TODO UID */ // Target UID + removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments + removals_.insert(DicomTag(0x0018, 0x700a)); /* X/D */ // Detector ID + removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description + removals_.insert(DicomTag(0x0018, 0x9516)); /* X/D */ // Start Acquisition DateTime + removals_.insert(DicomTag(0x0018, 0x9517)); /* X/D */ // End Acquisition DateTime + removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description + removals_.insert(DicomTag(0x0020, 0x0052)); /* TODO UID */ // Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x0200)); /* TODO UID */ // Synchronization Frame of Reference UID + removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID + removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer + removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description + removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments + removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments + removals_.insert(DicomTag(0x0020, 0x9161)); /* TODO UID */ // Concatenation UID + removals_.insert(DicomTag(0x0020, 0x9164)); /* TODO UID */ // Dimension Organization UID + removals_.insert(DicomTag(0x0028, 0x1199)); /* TODO UID */ // Palette Color Lookup Table UID + removals_.insert(DicomTag(0x0028, 0x1214)); /* TODO UID */ // Large Palette Color Lookup Table UID + removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments + removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer + removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location + removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title + removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study + removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician + removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service + removals_.insert(DicomTag(0x0032, 0x1060)); /* X/Z */ // Requested Procedure Description + removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent + removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments + removals_.insert(DicomTag(0x0038, 0x0004)); // Referenced Patient Alias Sequence + removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID + removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID + removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence + removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date + removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time + removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description + removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs + removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID + removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description + removals_.insert(DicomTag(0x0038, 0x0300)); // Current Patient Location + removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence + removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State + removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments + removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title + removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date + removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time + removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name + removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence + removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name + removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location + removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication + removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title + removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name + removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location + removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date + removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time + removals_.insert(DicomTag(0x0040, 0x0250)); // Performed Procedure Step End Date + removals_.insert(DicomTag(0x0040, 0x0251)); // Performed Procedure Step End Time + removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID + removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description + removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence + removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step + removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence + removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID + removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements + removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location + removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results + removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipients of Results Identification Sequence + removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address + removals_.insert(DicomTag(0x0040, 0x1103)); // Person's Telephone Numbers + removals_.insert(DicomTag(0x0040, 0x1104)); // Person's Telecom Information + removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments + removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for the Imaging Service Request + removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By + removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location + removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number + removals_.insert(DicomTag(0x0040, 0x2011)); // Order Callback Telecom Information + removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments + removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description + removals_.insert(DicomTag(0x0040, 0x4005)); // Scheduled Procedure Step Start DateTime + removals_.insert(DicomTag(0x0040, 0x4010)); // Scheduled Procedure Step Modification DateTime + removals_.insert(DicomTag(0x0040, 0x4011)); // Expected Completion DateTime + removals_.insert(DicomTag(0x0040, 0x4023)); /* TODO UID */ // Referenced General Purpose Scheduled Procedure Step Transaction UID + removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4028)); // Performed Station Name Code Sequence + removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence + removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence + removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization + removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name + removals_.insert(DicomTag(0x0040, 0x4050)); // Performed Procedure Step Start DateTime + removals_.insert(DicomTag(0x0040, 0x4051)); // Performed Procedure Step End DateTime + removals_.insert(DicomTag(0x0040, 0x4052)); // Procedure Step Cancellation DateTime + removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization + removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence + removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence + removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence + removals_.insert(DicomTag(0x0040, 0xa124)); /* TODO UID */ // UID + removals_.insert(DicomTag(0x0040, 0xa171)); /* TODO UID */ // Observation UID + removals_.insert(DicomTag(0x0040, 0xa172)); /* TODO UID */ // Referenced Observation UID (Trial) + removals_.insert(DicomTag(0x0040, 0xa192)); // Observation Date (Trial) + removals_.insert(DicomTag(0x0040, 0xa193)); // Observation Time (Trial) + removals_.insert(DicomTag(0x0040, 0xa307)); // Current Observer (Trial) + removals_.insert(DicomTag(0x0040, 0xa352)); // Verbal Source (Trial) + removals_.insert(DicomTag(0x0040, 0xa353)); // Address (Trial) + removals_.insert(DicomTag(0x0040, 0xa354)); // Telephone Number (Trial) + removals_.insert(DicomTag(0x0040, 0xa358)); // Verbal Source Identifier Code Sequence (Trial) + removals_.insert(DicomTag(0x0040, 0xa402)); /* TODO UID */ // Observation Subject UID (Trial) + removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence + removals_.insert(DicomTag(0x0040, 0xdb0c)); /* TODO UID */ // Template Extension Organization UID + removals_.insert(DicomTag(0x0040, 0xdb0d)); /* TODO UID */ // Template Extension Creator UID + removals_.insert(DicomTag(0x0062, 0x0021)); /* TODO UID */ // Tracking UID + removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence + removals_.insert(DicomTag(0x0070, 0x031a)); /* TODO UID */ // Fiducial UID + removals_.insert(DicomTag(0x0070, 0x1101)); /* TODO UID */ // Presentation Display Collection UID + removals_.insert(DicomTag(0x0070, 0x1102)); /* TODO UID */ // Presentation Sequence Collection UID + removals_.insert(DicomTag(0x0088, 0x0140)); /* TODO UID */ // Storage Media File-set UID + removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence(see Note 12) + removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title + removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject + removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author + removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Keywords + removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID + removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence + removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence + removals_.insert(DicomTag(0x0400, 0x0404)); // MAC + removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence + removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence + removals_.insert(DicomTag(0x2030, 0x0020)); // Text String + removals_.insert(DicomTag(0x3006, 0x0024)); /* TODO UID */ // Referenced Frame of Reference UID + removals_.insert(DicomTag(0x3006, 0x00c2)); /* TODO UID */ // Related Frame of Reference UID + removals_.insert(DicomTag(0x3008, 0x0105)); // Source Serial Number + removals_.insert(DicomTag(0x300a, 0x0013)); /* TODO UID */ // Dose Reference UID + removals_.insert(DicomTag(0x300c, 0x0113)); // Reason for Omission Description + removals_.insert(DicomTag(0x300e, 0x0008)); /* X/Z */ // Reviewer Name + removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary + removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments + removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer + removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder + removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber + removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text + removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author + removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence + removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation + removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description + removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence + removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name + removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address + removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer + removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions + removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments + removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signatures Sequence + removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding + + // Set the DeidentificationMethod tag + ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c); + } + + + void DicomModification::SetupAnonymization(DicomVersion version) + { + removals_.clear(); + clearings_.clear(); + ClearReplacements(); + removePrivateTags_ = true; + level_ = ResourceType_Patient; + uidMap_.clear(); + privateTagsToKeep_.clear(); + + switch (version) + { + case DicomVersion_2008: + SetupAnonymization2008(); + break; + + case DicomVersion_2017c: + SetupAnonymization2017c(); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + // Set the PatientIdentityRemoved tag + ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); + + // (*) Choose a random patient name and ID + std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); + ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); + ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); + } + + void DicomModification::Apply(ParsedDicomFile& toModify) + { + // Check the request + assert(ResourceType_Patient + 1 == ResourceType_Study && + ResourceType_Study + 1 == ResourceType_Series && + ResourceType_Series + 1 == ResourceType_Instance); + + if (IsRemoved(DICOM_TAG_PATIENT_ID) || + IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || + IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || + IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadRequest); + } + + + // Sanity checks at the patient level + if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (!allowManualIdentifiers_) + { + if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + // Sanity checks at the study level + if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (!allowManualIdentifiers_) + { + if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + // Sanity checks at the series level + if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a series, the parent StudyInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (!allowManualIdentifiers_) + { + if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + } + + + // Sanity checks at the instance level + if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) + { + LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + LOG(ERROR) << "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified"; + throw OrthancException(ErrorCode_BadRequest); + } + + + // (1) Remove the private tags, if need be + if (removePrivateTags_) + { + toModify.RemovePrivateTags(privateTagsToKeep_); + } + + // (2) Clear the tags specified by the user + for (SetOfTags::const_iterator it = clearings_.begin(); + it != clearings_.end(); ++it) + { + toModify.Clear(*it, true /* only clear if the tag exists in the original file */); + } + + // (3) Remove the tags specified by the user + for (SetOfTags::const_iterator it = removals_.begin(); + it != removals_.end(); ++it) + { + toModify.Remove(*it); + } + + // (4) Replace the tags + for (Replacements::const_iterator it = replacements_.begin(); + it != replacements_.end(); ++it) + { + toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent); + } + + // (5) Update the DICOM identifiers + if (level_ <= ResourceType_Study && + !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + if (keepStudyInstanceUid_) + { + LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!"; + } + else + { + MapDicomIdentifier(toModify, ResourceType_Study); + } + } + + if (level_ <= ResourceType_Series && + !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + if (keepSeriesInstanceUid_) + { + LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!"; + } + else + { + MapDicomIdentifier(toModify, ResourceType_Series); + } + } + + if (level_ <= ResourceType_Instance && // Always true + !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) + { + MapDicomIdentifier(toModify, ResourceType_Instance); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/DicomModification.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,137 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "ParsedDicomFile.h" + +namespace Orthanc +{ + class DicomModification : public boost::noncopyable + { + /** + * Process: + * (1) Remove private tags + * (2) Remove tags specified by the user + * (3) Replace tags + **/ + + private: + typedef std::set<DicomTag> SetOfTags; + typedef std::map<DicomTag, Json::Value*> Replacements; + typedef std::map< std::pair<ResourceType, std::string>, std::string> UidMap; + + SetOfTags removals_; + SetOfTags clearings_; + Replacements replacements_; + bool removePrivateTags_; + ResourceType level_; + UidMap uidMap_; + SetOfTags privateTagsToKeep_; + bool allowManualIdentifiers_; + bool keepStudyInstanceUid_; + bool keepSeriesInstanceUid_; + + void MapDicomIdentifier(ParsedDicomFile& dicom, + ResourceType level); + + void MarkNotOrthancAnonymization(); + + void ClearReplacements(); + + bool CancelReplacement(const DicomTag& tag); + + void ReplaceInternal(const DicomTag& tag, + const Json::Value& value); + + void SetupAnonymization2008(); + + void SetupAnonymization2017c(); + + public: + DicomModification(); + + ~DicomModification(); + + void Keep(const DicomTag& tag); + + void Remove(const DicomTag& tag); + + // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) + void Clear(const DicomTag& tag); + + bool IsRemoved(const DicomTag& tag) const; + + bool IsCleared(const DicomTag& tag) const; + + // "safeForAnonymization" tells Orthanc that this replacement does + // not break the anonymization process it implements (for internal use only) + void Replace(const DicomTag& tag, + const Json::Value& value, // Encoded using UTF-8 + bool safeForAnonymization); + + bool IsReplaced(const DicomTag& tag) const; + + const Json::Value& GetReplacement(const DicomTag& tag) const; + + std::string GetReplacementAsString(const DicomTag& tag) const; + + void SetRemovePrivateTags(bool removed); + + bool ArePrivateTagsRemoved() const + { + return removePrivateTags_; + } + + void SetLevel(ResourceType level); + + ResourceType GetLevel() const + { + return level_; + } + + void SetupAnonymization(DicomVersion version); + + void Apply(ParsedDicomFile& toModify); + + void SetAllowManualIdentifiers(bool check) + { + allowManualIdentifiers_ = check; + } + + bool AreAllowManualIdentifiers() const + { + return allowManualIdentifiers_; + } + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,2075 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "../Logging.h" +#include "../SystemToolbox.h" +#include "../Toolbox.h" +#include "../TemporaryFile.h" +#include "../OrthancException.h" + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> +#include <boost/filesystem.hpp> +#include <boost/algorithm/string/predicate.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcistrmb.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrat.h> +#include <dcmtk/dcmdata/dcvrcs.h> +#include <dcmtk/dcmdata/dcvrda.h> +#include <dcmtk/dcmdata/dcvrds.h> +#include <dcmtk/dcmdata/dcvrdt.h> +#include <dcmtk/dcmdata/dcvrfd.h> +#include <dcmtk/dcmdata/dcvrfl.h> +#include <dcmtk/dcmdata/dcvris.h> +#include <dcmtk/dcmdata/dcvrlo.h> +#include <dcmtk/dcmdata/dcvrlt.h> +#include <dcmtk/dcmdata/dcvrpn.h> +#include <dcmtk/dcmdata/dcvrsh.h> +#include <dcmtk/dcmdata/dcvrsl.h> +#include <dcmtk/dcmdata/dcvrss.h> +#include <dcmtk/dcmdata/dcvrst.h> +#include <dcmtk/dcmdata/dcvrtm.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcvrul.h> +#include <dcmtk/dcmdata/dcvrus.h> +#include <dcmtk/dcmdata/dcvrut.h> + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 +# include <EmbeddedResources.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 +# include <dcmtk/dcmjpeg/djdecode.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 +# include <dcmtk/dcmjpls/djdecode.h> +#endif + + +namespace Orthanc +{ + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, + EmbeddedResources::FileResourceId resource) + { + std::string content; + EmbeddedResources::GetFileResource(content, resource); + + TemporaryFile tmp; + tmp.Write(content); + + if (!dictionary.loadDictionary(tmp.GetPath().c_str())) + { + LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " + << "your TEMP directory does not contain special characters."; + throw OrthancException(ErrorCode_InternalError); + } + } + +#else + static void LoadExternalDictionary(DcmDataDictionary& dictionary, + const std::string& directory, + const std::string& filename) + { + boost::filesystem::path p = directory; + p = p / filename; + + LOG(WARNING) << "Loading the external DICOM dictionary " << p; + + if (!dictionary.loadDictionary(p.string().c_str())) + { + throw OrthancException(ErrorCode_InternalError); + } + } +#endif + + + namespace + { + class DictionaryLocker + { + private: + DcmDataDictionary& dictionary_; + + public: + DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) + { + } + + ~DictionaryLocker() + { + dcmDataDict.unlock(); + } + + DcmDataDictionary& operator*() + { + return dictionary_; + } + + DcmDataDictionary* operator->() + { + return &dictionary_; + } + }; + } + + + void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) + { + LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; + + { + DictionaryLocker locker; + + locker->clear(); + +#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 + LOG(WARNING) << "Loading the embedded dictionaries"; + /** + * Do not load DICONDE dictionary, it breaks the other tags. The + * command "strace storescu 2>&1 |grep dic" shows that DICONDE + * dictionary is not loaded by storescu. + **/ + //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE); + + LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM); + + if (loadPrivateDictionary) + { + LOG(INFO) << "Loading the embedded dictionary of private tags"; + LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE); + } + else + { + LOG(INFO) << "The dictionary of private tags has not been loaded"; + } + +#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) + std::string path = DCMTK_DICTIONARY_DIR; + + const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); + if (env != NULL) + { + path = std::string(env); + } + + LoadExternalDictionary(*locker, path, "dicom.dic"); + + if (loadPrivateDictionary) + { + LoadExternalDictionary(*locker, path, "private.dic"); + } + else + { + LOG(INFO) << "The dictionary of private tags has not been loaded"; + } + +#else +#error Support your platform here +#endif + } + + /* make sure data dictionary is loaded */ + if (!dcmDataDict.isDictionaryLoaded()) + { + LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; + throw OrthancException(ErrorCode_InternalError); + } + + { + // Test the dictionary with a simple DICOM tag + DcmTag key(0x0010, 0x1030); // This is PatientWeight + if (key.getEVR() != EVR_DS) + { + LOG(ERROR) << "The DICOM dictionary has not been correctly read"; + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, + ValueRepresentation vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity, + const std::string& privateCreator) + { + if (minMultiplicity < 1) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + bool arbitrary = false; + if (maxMultiplicity == 0) + { + maxMultiplicity = DcmVariableVM; + arbitrary = true; + } + else if (maxMultiplicity < minMultiplicity) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + DcmEVR evr = ToDcmtkBridge::Convert(vr); + + LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " + << name << " (multiplicity: " << minMultiplicity << "-" + << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; + + std::auto_ptr<DcmDictEntry> entry; + if (privateCreator.empty()) + { + if (tag.GetGroup() % 2 == 1) + { + char buf[128]; + sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " + "but no private creator was associated with it", + tag.GetGroup(), tag.GetElement()); + LOG(WARNING) << buf; + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + NULL /* version */, + OFTrue /* doCopyString */, + NULL /* private creator */)); + } + else + { + // "Private Data Elements have an odd Group Number that is not + // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or + // (FFFF,eeee)." + if (tag.GetGroup() % 2 == 0 /* even */ || + tag.GetGroup() == 0x0001 || + tag.GetGroup() == 0x0003 || + tag.GetGroup() == 0x0005 || + tag.GetGroup() == 0x0007 || + tag.GetGroup() == 0xffff) + { + char buf[128]; + sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", + tag.GetGroup(), tag.GetElement()); + LOG(ERROR) << buf; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast<int>(minMultiplicity), + static_cast<int>(maxMultiplicity), + "private" /* version */, + OFTrue /* doCopyString */, + privateCreator.c_str())); + } + + entry->setGroupRangeRestriction(DcmDictRange_Unspecified); + entry->setElementRangeRestriction(DcmDictRange_Unspecified); + + { + DictionaryLocker locker; + + if (locker->findEntry(name.c_str())) + { + LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\""; + throw OrthancException(ErrorCode_AlreadyExistingTag); + } + + locker->addEntry(entry.release()); + } + } + + + Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset, + Encoding defaultEncoding) + { + Encoding encoding = defaultEncoding; + + OFString tmp; + if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) + { + std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); + + if (characterSet.empty()) + { + // Empty specific character set tag: Use the default encoding + } + else if (GetDicomEncoding(encoding, characterSet.c_str())) + { + // The specific character set is supported by the Orthanc core + } + else + { + LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet + << ", fallback to ASCII (remove all special characters)"; + encoding = Encoding_Ascii; + } + } + else + { + // No specific character set tag: Use the default encoding + } + + return encoding; + } + + + void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, + DcmItem& dataset, + unsigned int maxStringLength, + Encoding defaultEncoding) + { + Encoding encoding = DetectEncoding(dataset, defaultEncoding); + + target.Clear(); + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element && element->isLeaf()) + { + target.SetValue(element->getTag().getGTag(), + element->getTag().getETag(), + ConvertLeafElement(*element, DicomToJsonFlags_Default, maxStringLength, encoding)); + } + } + } + + + DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + + + DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) + { + return DicomTag(element.getGTag(), element.getETag()); + } + + + DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding) + { + if (!element.isLeaf()) + { + // This function is only applicable to leaf elements + throw OrthancException(ErrorCode_BadParameterType); + } + + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) + { + if (c == NULL) // This case corresponds to the empty string + { + return new DicomValue("", false); + } + else + { + std::string s(c); + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); + + if (maxStringLength != 0 && + utf8.size() > maxStringLength) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + return new DicomValue(utf8, false); + } + } + } + + + if (element.getVR() == EVR_UN) + { + // Unknown value representation: Lookup in the dictionary. This + // is notably the case for private tags registered with the + // "Dictionary" configuration option. + DictionaryLocker locker; + + const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), + element.getTag().getPrivateCreator()); + if (entry != NULL && + entry->getVR().isaString()) + { + Uint8* data = NULL; + + // At (*), we do not try and convert to UTF-8, as nothing says + // the encoding of the private tag is the same as that of the + // remaining of the DICOM dataset. Only go for ASCII strings. + + if (element.getUint8Array(data) == EC_Normal && + Toolbox::IsAsciiString(data, element.getLength())) // (*) + { + if (data == NULL) + { + return new DicomValue("", false); // Empty string + } + else if (maxStringLength != 0 && + element.getLength() > maxStringLength) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + std::string s(reinterpret_cast<const char*>(data), element.getLength()); + return new DicomValue(s, false); + } + } + } + } + + + try + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + switch (element.getVR()) + { + + /** + * Deal with binary data (including PixelData). + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_UN: // unknown value representation + case EVR_ox: // OB or OW depending on context + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) + { + Uint8* data = NULL; + if (element.getUint8Array(data) == EC_Normal) + { + return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); + } + } + + return new DicomValue; + } + + /** + * Numeric types + **/ + + case EVR_SL: // signed long + { + Sint32 f; + if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good()) + return new DicomValue(boost::lexical_cast<std::string>(f), false); + else + return new DicomValue; + } + + case EVR_SS: // signed short + { + Sint16 f; + if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good()) + return new DicomValue(boost::lexical_cast<std::string>(f), false); + else + return new DicomValue; + } + + case EVR_UL: // unsigned long + { + Uint32 f; + if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good()) + return new DicomValue(boost::lexical_cast<std::string>(f), false); + else + return new DicomValue; + } + + case EVR_US: // unsigned short + { + Uint16 f; + if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good()) + return new DicomValue(boost::lexical_cast<std::string>(f), false); + else + return new DicomValue; + } + + case EVR_FL: // float single-precision + { + Float32 f; + if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good()) + return new DicomValue(boost::lexical_cast<std::string>(f), false); + else + return new DicomValue; + } + + case EVR_FD: // float double-precision + { + Float64 f; + if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good()) + return new DicomValue(boost::lexical_cast<std::string>(f), false); + else + return new DicomValue; + } + + + /** + * Attribute tag. + **/ + + case EVR_AT: + { + DcmTagKey tag; + if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) + { + DicomTag t(tag.getGroup(), tag.getElement()); + return new DicomValue(t.Format(), false); + } + else + { + return new DicomValue; + } + } + + + /** + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ + + case EVR_SQ: // sequence of items + return new DicomValue; + + + /** + * Internal to DCMTK. + **/ + + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + return new DicomValue; + + + /** + * Default case. + **/ + + default: + return new DicomValue; + } + } + catch (boost::bad_lexical_cast) + { + return new DicomValue; + } + catch (std::bad_cast) + { + return new DicomValue; + } + } + + + static Json::Value& PrepareNode(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format) + { + assert(parent.type() == Json::objectValue); + + DicomTag tag(FromDcmtkBridge::GetTag(element)); + const std::string formattedTag = tag.Format(); + + if (format == DicomToJsonFormat_Short) + { + parent[formattedTag] = Json::nullValue; + return parent[formattedTag]; + } + + // This code gives access to the name of the private tags + std::string tagName = FromDcmtkBridge::GetTagName(element); + + switch (format) + { + case DicomToJsonFormat_Human: + parent[tagName] = Json::nullValue; + return parent[tagName]; + + case DicomToJsonFormat_Full: + { + parent[formattedTag] = Json::objectValue; + Json::Value& node = parent[formattedTag]; + + if (element.isLeaf()) + { + node["Name"] = tagName; + + if (element.getTag().getPrivateCreator() != NULL) + { + node["PrivateCreator"] = element.getTag().getPrivateCreator(); + } + + return node; + } + else + { + node["Name"] = tagName; + node["Type"] = "Sequence"; + node["Value"] = Json::nullValue; + return node["Value"]; + } + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + static void LeafValueToJson(Json::Value& target, + const DicomValue& value, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + Json::Value* targetValue = NULL; + Json::Value* targetType = NULL; + + switch (format) + { + case DicomToJsonFormat_Short: + case DicomToJsonFormat_Human: + { + assert(target.type() == Json::nullValue); + targetValue = ⌖ + break; + } + + case DicomToJsonFormat_Full: + { + assert(target.type() == Json::objectValue); + target["Value"] = Json::nullValue; + target["Type"] = Json::nullValue; + targetType = &target["Type"]; + targetValue = &target["Value"]; + break; + } + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + assert(targetValue != NULL); + assert(targetValue->type() == Json::nullValue); + assert(targetType == NULL || targetType->type() == Json::nullValue); + + if (value.IsNull()) + { + if (targetType != NULL) + { + *targetType = "Null"; + } + } + else if (value.IsBinary()) + { + if (flags & DicomToJsonFlags_ConvertBinaryToAscii) + { + *targetValue = Toolbox::ConvertToAscii(value.GetContent()); + } + else + { + std::string s; + value.FormatDataUriScheme(s); + *targetValue = s; + } + + if (targetType != NULL) + { + *targetType = "Binary"; + } + } + else if (maxStringLength == 0 || + value.GetContent().size() <= maxStringLength) + { + *targetValue = value.GetContent(); + + if (targetType != NULL) + { + *targetType = "String"; + } + } + else + { + if (targetType != NULL) + { + *targetType = "TooLong"; + } + } + } + + + void FromDcmtkBridge::ElementToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding) + { + if (parent.type() == Json::nullValue) + { + parent = Json::objectValue; + } + + assert(parent.type() == Json::objectValue); + Json::Value& target = PrepareNode(parent, element, format); + + if (element.isLeaf()) + { + // The "0" below lets "LeafValueToJson()" take care of "TooLong" values + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding)); + LeafValueToJson(target, *v, format, flags, maxStringLength); + } + else + { + assert(target.type() == Json::nullValue); + target = Json::arrayValue; + + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + DcmItem* child = sequence.getItem(i); + Json::Value& v = target.append(Json::objectValue); + DatasetToJson(v, *child, format, flags, maxStringLength, encoding); + } + } + } + + + void FromDcmtkBridge::DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding) + { + assert(parent.type() == Json::objectValue); + + for (unsigned long i = 0; i < item.card(); i++) + { + DcmElement* element = item.getElement(i); + if (element == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); + + /*element->getTag().isPrivate()*/ + if (tag.IsPrivate() && + !(flags & DicomToJsonFlags_IncludePrivateTags)) + { + continue; + } + + if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) + { + DictionaryLocker locker; + if (locker->findEntry(element->getTag(), NULL) == NULL) + { + continue; + } + } + + DcmEVR evr = element->getTag().getEVR(); + if (evr == EVR_OB || + evr == EVR_OF || + evr == EVR_OW || + evr == EVR_UN || + evr == EVR_ox) + { + // This is a binary tag + if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || + (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) + { + continue; + } + } + + FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding); + } + } + + + void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding defaultEncoding) + { + Encoding encoding = DetectEncoding(dataset, defaultEncoding); + + target = Json::objectValue; + DatasetToJson(target, dataset, format, flags, maxStringLength, encoding); + } + + + void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, + DcmMetaInfo& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + target = Json::objectValue; + DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii); + } + + + + static std::string GetTagNameInternal(DcmTag& tag) + { + { + // Some patches for important tags because of different DICOM + // dictionaries between DCMTK versions + DicomTag tmp(tag.getGroup(), tag.getElement()); + std::string n = tmp.GetMainTagsName(); + if (n.size() != 0) + { + return n; + } + // End of patches + } + +#if 0 + // This version explicitly calls the dictionary + const DcmDataDictionary& dict = dcmDataDict.rdlock(); + const DcmDictEntry* entry = dict.findEntry(tag, NULL); + + std::string s(DcmTag_ERROR_TagName); + if (entry != NULL) + { + s = std::string(entry->getTagName()); + } + + dcmDataDict.unlock(); + return s; +#else + const char* name = tag.getTagName(); + if (name == NULL) + { + return DcmTag_ERROR_TagName; + } + else + { + return std::string(name); + } +#endif + } + + + std::string FromDcmtkBridge::GetTagName(const DicomTag& t, + const std::string& privateCreator) + { + DcmTag tag(t.GetGroup(), t.GetElement()); + + if (!privateCreator.empty()) + { + tag.setPrivateCreator(privateCreator.c_str()); + } + + return GetTagNameInternal(tag); + } + + + std::string FromDcmtkBridge::GetTagName(const DcmElement& element) + { + // Copy the tag to ensure const-correctness of DcmElement. Note + // that the private creator information is also copied. + DcmTag tag(element.getTag()); + + return GetTagNameInternal(tag); + } + + + + DicomTag FromDcmtkBridge::ParseTag(const char* name) + { + if (strlen(name) == 9 && + isxdigit(name[0]) && + isxdigit(name[1]) && + isxdigit(name[2]) && + isxdigit(name[3]) && + (name[4] == '-' || name[4] == ',') && + isxdigit(name[5]) && + isxdigit(name[6]) && + isxdigit(name[7]) && + isxdigit(name[8])) + { + uint16_t group = GetTagValue(name); + uint16_t element = GetTagValue(name + 5); + return DicomTag(group, element); + } + + if (strlen(name) == 8 && + isxdigit(name[0]) && + isxdigit(name[1]) && + isxdigit(name[2]) && + isxdigit(name[3]) && + isxdigit(name[4]) && + isxdigit(name[5]) && + isxdigit(name[6]) && + isxdigit(name[7])) + { + uint16_t group = GetTagValue(name); + uint16_t element = GetTagValue(name + 4); + return DicomTag(group, element); + } + +#if 0 + const DcmDataDictionary& dict = dcmDataDict.rdlock(); + const DcmDictEntry* entry = dict.findEntry(name); + + if (entry == NULL) + { + dcmDataDict.unlock(); + throw OrthancException(ErrorCode_UnknownDicomTag); + } + else + { + DcmTagKey key = entry->getKey(); + DicomTag tag(key.getGroup(), key.getElement()); + dcmDataDict.unlock(); + return tag; + } +#else + DcmTag tag; + if (DcmTag::findTagFromName(name, tag).good()) + { + return DicomTag(tag.getGTag(), tag.getETag()); + } + else + { + throw OrthancException(ErrorCode_UnknownDicomTag); + } +#endif + } + + + bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) + { + DcmTag tmp(tag.GetGroup(), tag.GetElement()); + return tmp.isUnknownVR(); + } + + + void FromDcmtkBridge::ToJson(Json::Value& result, + const DicomMap& values, + bool simplify) + { + if (result.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + result.clear(); + + for (DicomMap::Map::const_iterator + it = values.map_.begin(); it != values.map_.end(); ++it) + { + // TODO Inject PrivateCreator if some is available in the DicomMap? + const std::string tagName = GetTagName(it->first, ""); + + if (simplify) + { + if (it->second->IsNull()) + { + result[tagName] = Json::nullValue; + } + else + { + // TODO IsBinary + result[tagName] = it->second->GetContent(); + } + } + else + { + Json::Value value = Json::objectValue; + + value["Name"] = tagName; + + if (it->second->IsNull()) + { + value["Type"] = "Null"; + value["Value"] = Json::nullValue; + } + else + { + // TODO IsBinary + value["Type"] = "String"; + value["Value"] = it->second->GetContent(); + } + + result[it->first.Format()] = value; + } + } + } + + + std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) + { + char uid[100]; + + switch (level) + { + case ResourceType_Patient: + // The "PatientID" field is of type LO (Long String), 64 + // Bytes Maximum. An UUID is of length 36, thus it can be used + // as a random PatientID. + return SystemToolbox::GenerateUuid(); + + case ResourceType_Instance: + return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); + + case ResourceType_Series: + return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); + + case ResourceType_Study: + return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet) + { + // Determine the transfer syntax which shall be used to write the + // information to the file. We always switch to the Little Endian + // syntax, with explicit length. + + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + + + /** + * Note that up to Orthanc 0.7.1 (inclusive), the + * "EXS_LittleEndianExplicit" was always used to save the DICOM + * dataset into memory. We now keep the original transfer syntax + * (if available). + **/ + E_TransferSyntax xfer = dataSet.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + // No information about the original transfer syntax: This is + // most probably a DICOM dataset that was read from memory. + xfer = EXS_LittleEndianExplicit; + } + + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + + // Create the meta-header information + DcmFileFormat ff(&dataSet); + ff.validateMetaInfo(xfer); + ff.removeInvalidGroups(); + + // Create a memory buffer with the proper size + { + const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) + buffer.resize(estimatedSize); + } + + DcmOutputBufferStream ob(&buffer[0], buffer.size()); + + // Fill the memory buffer with the meta-header and the dataset + ff.transferInit(); + OFCondition c = ff.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_withoutPadding); + ff.transferEnd(); + + if (c.good()) + { + // The DICOM file is successfully written, truncate the target + // buffer if its size was overestimated by (*) + ob.flush(); + + size_t effectiveSize = static_cast<size_t>(ob.tell()); + if (effectiveSize < buffer.size()) + { + buffer.resize(effectiveSize); + } + + return true; + } + else + { + // Error + buffer.clear(); + return false; + } + } + + + ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) + { + DcmTag t(tag.GetGroup(), tag.GetElement()); + return Convert(t.getEVR()); + } + + ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr) + { + switch (vr) + { + case EVR_AE: + return ValueRepresentation_ApplicationEntity; + + case EVR_AS: + return ValueRepresentation_AgeString; + + case EVR_AT: + return ValueRepresentation_AttributeTag; + + case EVR_CS: + return ValueRepresentation_CodeString; + + case EVR_DA: + return ValueRepresentation_Date; + + case EVR_DS: + return ValueRepresentation_DecimalString; + + case EVR_DT: + return ValueRepresentation_DateTime; + + case EVR_FL: + return ValueRepresentation_FloatingPointSingle; + + case EVR_FD: + return ValueRepresentation_FloatingPointDouble; + + case EVR_IS: + return ValueRepresentation_IntegerString; + + case EVR_LO: + return ValueRepresentation_LongString; + + case EVR_LT: + return ValueRepresentation_LongText; + + case EVR_OB: + return ValueRepresentation_OtherByte; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_OD: + return ValueRepresentation_OtherDouble;*/ + + case EVR_OF: + return ValueRepresentation_OtherFloat; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_OL: + return ValueRepresentation_OtherLong;*/ + + case EVR_OW: + return ValueRepresentation_OtherWord; + + case EVR_PN: + return ValueRepresentation_PersonName; + + case EVR_SH: + return ValueRepresentation_ShortString; + + case EVR_SL: + return ValueRepresentation_SignedLong; + + case EVR_SQ: + return ValueRepresentation_Sequence; + + case EVR_SS: + return ValueRepresentation_SignedShort; + + case EVR_ST: + return ValueRepresentation_ShortText; + + case EVR_TM: + return ValueRepresentation_Time; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_UC: + return ValueRepresentation_UnlimitedCharacters;*/ + + case EVR_UI: + return ValueRepresentation_UniqueIdentifier; + + case EVR_UL: + return ValueRepresentation_UnsignedLong; + + case EVR_UN: + return ValueRepresentation_Unknown; + + // Not supported as of DCMTK 3.6.0 + /*case EVR_UR: + return ValueRepresentation_UniversalResource;*/ + + case EVR_US: + return ValueRepresentation_UnsignedShort; + + case EVR_UT: + return ValueRepresentation_UnlimitedText; + + default: + return ValueRepresentation_NotSupported; + } + } + + + static bool IsBinaryTag(const DcmTag& key) + { + return (key.isUnknownVR() || + key.getEVR() == EVR_OB || + key.getEVR() == EVR_OF || + key.getEVR() == EVR_OW || + key.getEVR() == EVR_UN || + key.getEVR() == EVR_ox); + } + + + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (tag.IsPrivate() || + IsBinaryTag(key)) + { + return new DcmOtherByteOtherWord(key); + } + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * Binary types, handled above + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_UN: // unknown value representation + case EVR_ox: // OB or OW depending on context + throw OrthancException(ErrorCode_InternalError); + + + /** + * String types. + * http://support.dcmtk.org/docs/classDcmByteString.html + **/ + + case EVR_AS: // age string + return new DcmAgeString(key); + + case EVR_AE: // application entity title + return new DcmApplicationEntity(key); + + case EVR_CS: // code string + return new DcmCodeString(key); + + case EVR_DA: // date string + return new DcmDate(key); + + case EVR_DT: // date time string + return new DcmDateTime(key); + + case EVR_DS: // decimal string + return new DcmDecimalString(key); + + case EVR_IS: // integer string + return new DcmIntegerString(key); + + case EVR_TM: // time string + return new DcmTime(key); + + case EVR_UI: // unique identifier + return new DcmUniqueIdentifier(key); + + case EVR_ST: // short text + return new DcmShortText(key); + + case EVR_LO: // long string + return new DcmLongString(key); + + case EVR_LT: // long text + return new DcmLongText(key); + + case EVR_UT: // unlimited text + return new DcmUnlimitedText(key); + + case EVR_SH: // short string + return new DcmShortString(key); + + case EVR_PN: // person name + return new DcmPersonName(key); + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + return new DcmSignedLong(key); + + case EVR_SS: // signed short + return new DcmSignedShort(key); + + case EVR_UL: // unsigned long + return new DcmUnsignedLong(key); + + case EVR_US: // unsigned short + return new DcmUnsignedShort(key); + + case EVR_FL: // float single-precision + return new DcmFloatingPointSingle(key); + + case EVR_FD: // float double-precision + return new DcmFloatingPointDouble(key); + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * TODO + **/ + + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + + /** + * Internal to DCMTK. + **/ + + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + + throw OrthancException(ErrorCode_InternalError); + } + + + + void FromDcmtkBridge::FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + Encoding dicomEncoding) + { + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeDataUriScheme && + boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) + { + std::string mime; + if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + decoded = &binary; + } + else if (dicomEncoding != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); + decoded = &binary; + } + + DcmTag key(tag.GetGroup(), tag.GetElement()); + + if (tag.IsPrivate() || + IsBinaryTag(key)) + { + if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) + { + return; + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + + bool ok = false; + + try + { + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + **/ + + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + { + ok = element.putString(decoded->c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good(); + break; + } + + case EVR_UL: // unsigned long + { + ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good(); + break; + } + + case EVR_FL: // float single-precision + { + ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); + break; + } + + case EVR_FD: // float double-precision + { + ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); + break; + } + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + { + ok = false; + break; + } + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + } + catch (boost::bad_lexical_cast&) + { + ok = false; + } + + if (!ok) + { + LOG(ERROR) << "While creating a DICOM instance, tag (" << tag.Format() + << ") has out-of-range value: \"" << *decoded << "\""; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + Encoding dicomEncoding) + { + std::auto_ptr<DcmElement> element; + + switch (value.type()) + { + case Json::stringValue: + element.reset(CreateElementForTag(tag)); + FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding); + break; + + case Json::nullValue: + element.reset(CreateElementForTag(tag)); + FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding); + break; + + case Json::arrayValue: + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + if (key.getEVR() != EVR_SQ) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key); + element.reset(sequence); + + for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) + { + std::auto_ptr<DcmItem> item(new DcmItem); + + Json::Value::Members members = value[i].getMemberNames(); + for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) + { + item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding)); + } + + sequence->append(item.release()); + } + + break; + } + + default: + throw OrthancException(ErrorCode_BadParameterType); + } + + return element.release(); + } + + + DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset) + { + DcmElement *element = NULL; + if (!dataset.findAndGetElement(DCM_PixelData, element).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + DcmPixelSequence* pixelSequence = NULL; + if (!pixelData.getEncapsulatedRepresentation + (dataset.getOriginalXfer(), NULL, pixelSequence).good()) + { + return NULL; + } + else + { + return pixelSequence; + } + } + + + Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json, + Encoding defaultEncoding) + { + if (json.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + Encoding encoding = defaultEncoding; + + const Json::Value::Members tags = json.getMemberNames(); + + // Look for SpecificCharacterSet (0008,0005) in the JSON file + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + const Json::Value& value = json[tags[i]]; + if (value.type() != Json::stringValue || + (value.asString().length() != 0 && + !GetDicomEncoding(encoding, value.asCString()))) + { + LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value; + throw OrthancException(ErrorCode_BadRequest); + } + + if (value.asString().length() == 0) + { + return defaultEncoding; + } + } + } + + return encoding; + } + + + static void SetString(DcmDataset& target, + const DcmTag& tag, + const std::string& value) + { + if (!target.putAndInsertString(tag, value.c_str()).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 + bool generateIdentifiers, + bool decodeDataUriScheme, + Encoding defaultEncoding) + { + std::auto_ptr<DcmDataset> result(new DcmDataset); + Encoding encoding = ExtractEncoding(json, defaultEncoding); + + SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); + + const Json::Value::Members tags = json.getMemberNames(); + + bool hasPatientId = false; + bool hasStudyInstanceUid = false; + bool hasSeriesInstanceUid = false; + bool hasSopInstanceUid = false; + + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + const Json::Value& value = json[tags[i]]; + + if (tag == DICOM_TAG_PATIENT_ID) + { + hasPatientId = true; + } + else if (tag == DICOM_TAG_STUDY_INSTANCE_UID) + { + hasStudyInstanceUid = true; + } + else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) + { + hasSeriesInstanceUid = true; + } + else if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + hasSopInstanceUid = true; + } + + if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); + const DcmTagKey& tag = element->getTag(); + + result->findAndDeleteElement(tag); + + DcmElement* tmp = element.release(); + if (!result->insert(tmp, false, false).good()) + { + delete tmp; + throw OrthancException(ErrorCode_InternalError); + } + } + } + + if (!hasPatientId && + generateIdentifiers) + { + SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient)); + } + + if (!hasStudyInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study)); + } + + if (!hasSeriesInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series)); + } + + if (!hasSopInstanceUid && + generateIdentifiers) + { + SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance)); + } + + return result.release(); + } + + + DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer, + size_t size) + { + DcmInputBufferStream is; + if (size > 0) + { + is.setBuffer(buffer, size); + } + is.setEos(); + + std::auto_ptr<DcmFileFormat> result(new DcmFileFormat); + + result->transferInit(); + if (!result->read(is).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + result->loadAllDataIntoMemory(); + result->transferEnd(); + + return result.release(); + } + + + void FromDcmtkBridge::FromJson(DicomMap& target, + const Json::Value& source) + { + if (source.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + target.Clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& value = source[members[i]]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + target.SetValue(ParseTag(members[i]), value.asString(), false); + } + } + + + void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset, + Encoding source, + Encoding target) + { + // Recursive exploration of a dataset to change the encoding of + // each string-like element + + if (source == target) + { + return; + } + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + if (element) + { + if (element->isLeaf()) + { + char *c = NULL; + if (element->isaString() && + element->getString(c).good() && + c != NULL) + { + std::string a = Toolbox::ConvertToUtf8(c, source); + std::string b = Toolbox::ConvertFromUtf8(a, target); + element->putString(b.c_str()); + } + } + else + { + // "All subclasses of DcmElement except for DcmSequenceOfItems + // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset + // etc. are not." The following dynamic_cast is thus OK. + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + + for (unsigned long j = 0; j < sequence.card(); j++) + { + ChangeStringEncoding(*sequence.getItem(j), source, target); + } + } + } + } + } + + + bool FromDcmtkBridge::LookupTransferSyntax(std::string& result, + DcmFileFormat& dicom) + { + const char* value = NULL; + + if (dicom.getMetaInfo() != NULL && + dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && + value != NULL) + { + result.assign(value); + return true; + } + else + { + return false; + } + } + + +#if ORTHANC_ENABLE_LUA == 1 + void FromDcmtkBridge::ExecuteToDicom(DicomMap& target, + LuaFunctionCall& call) + { + Json::Value output; + call.ExecuteToJson(output, true /* keep strings */); + + target.Clear(); + + if (output.type() == Json::arrayValue && + output.size() == 0) + { + // This case happens for empty tables + return; + } + + if (output.type() != Json::objectValue) + { + LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table"; + throw OrthancException(ErrorCode_LuaBadOutput); + } + + Json::Value::Members members = output.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + if (output[members[i]].type() != Json::stringValue) + { + LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table mapping names of DICOM tags to strings"; + throw OrthancException(ErrorCode_LuaBadOutput); + } + + DicomTag tag(ParseTag(members[i])); + target.SetValue(tag, output[members[i]].asString(), false); + } + } +#endif + + + void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, + DcmItem& dataset) + { + ExtractDicomSummary(target, dataset, + ORTHANC_MAXIMUM_TAG_LENGTH, + GetDefaultDicomEncoding()); + } + + + void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset) + { + ExtractDicomAsJson(target, dataset, + DicomToJsonFormat_Full, + DicomToJsonFlags_Default, + ORTHANC_MAXIMUM_TAG_LENGTH, + GetDefaultDicomEncoding()); + } + + + void FromDcmtkBridge::InitializeCodecs() + { +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + LOG(WARNING) << "Registering JPEG Lossless codecs in DCMTK"; + DJLSDecoderRegistration::registerCodecs(); +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + LOG(WARNING) << "Registering JPEG codecs in DCMTK"; + DJDecoderRegistration::registerCodecs(); +#endif + } + + + void FromDcmtkBridge::FinalizeCodecs() + { +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + // Unregister JPEG-LS codecs + DJLSDecoderRegistration::cleanup(); +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + // Unregister JPEG codecs + DJDecoderRegistration::cleanup(); +#endif + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,239 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../DicomFormat/DicomElement.h" +#include "../DicomFormat/DicomMap.h" + +#include <dcmtk/dcmdata/dcdatset.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <json/json.h> + +#if !defined(ORTHANC_ENABLE_LUA) +# error The macro ORTHANC_ENABLE_LUA must be defined +#endif + +#if ORTHANC_ENABLE_DCMTK != 1 +# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 +#endif + +#if ORTHANC_BUILD_UNIT_TESTS == 1 +# include <gtest/gtest_prod.h> +#endif + +#if ORTHANC_ENABLE_LUA == 1 +# include "../Lua/LuaFunctionCall.h" +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined +#endif + + +namespace Orthanc +{ + class FromDcmtkBridge : public boost::noncopyable + { +#if ORTHANC_BUILD_UNIT_TESTS == 1 + FRIEND_TEST(FromDcmtkBridge, FromJson); +#endif + + friend class ParsedDicomFile; + + private: + FromDcmtkBridge(); // Pure static class + + static void ExtractDicomSummary(DicomMap& target, + DcmItem& dataset, + unsigned int maxStringLength, + Encoding defaultEncoding); + + static void DatasetToJson(Json::Value& parent, + DcmItem& item, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding); + + static void ElementToJson(Json::Value& parent, + DcmElement& element, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding dicomEncoding); + + static void ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding defaultEncoding); + + static void ChangeStringEncoding(DcmItem& dataset, + Encoding source, + Encoding target); + + public: + static void InitializeDictionary(bool loadPrivateDictionary); + + static void RegisterDictionaryTag(const DicomTag& tag, + ValueRepresentation vr, + const std::string& name, + unsigned int minMultiplicity, + unsigned int maxMultiplicity, + const std::string& privateCreator); + + static Encoding DetectEncoding(DcmItem& dataset, + Encoding defaultEncoding); + + static DicomTag Convert(const DcmTag& tag); + + static DicomTag GetTag(const DcmElement& element); + + static bool IsUnknownTag(const DicomTag& tag); + + static DicomValue* ConvertLeafElement(DcmElement& element, + DicomToJsonFlags flags, + unsigned int maxStringLength, + Encoding encoding); + + static void ExtractHeaderAsJson(Json::Value& target, + DcmMetaInfo& header, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength); + + static std::string GetTagName(const DicomTag& tag, + const std::string& privateCreator); + + static std::string GetTagName(const DcmElement& element); + + static std::string GetTagName(const DicomElement& element) + { + return GetTagName(element.GetTag(), ""); + } + + static DicomTag ParseTag(const char* name); + + static DicomTag ParseTag(const std::string& name) + { + return ParseTag(name.c_str()); + } + + static bool HasTag(const DicomMap& fields, + const std::string& tagName) + { + return fields.HasTag(ParseTag(tagName)); + } + + static const DicomValue& GetValue(const DicomMap& fields, + const std::string& tagName) + { + return fields.GetValue(ParseTag(tagName)); + } + + static void SetValue(DicomMap& target, + const std::string& tagName, + DicomValue* value) + { + target.SetValue(ParseTag(tagName), value); + } + + static void ToJson(Json::Value& result, + const DicomMap& values, + bool simplify); + + static std::string GenerateUniqueIdentifier(ResourceType level); + + static bool SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet); + + static ValueRepresentation Convert(DcmEVR vr); + + static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); + + static DcmElement* CreateElementForTag(const DicomTag& tag); + + static void FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& utf8alue, // Encoded using UTF-8 + bool decodeDataUriScheme, + Encoding dicomEncoding); + + static DcmElement* FromJson(const DicomTag& tag, + const Json::Value& element, // Encoded using UTF-8 + bool decodeDataUriScheme, + Encoding dicomEncoding); + + static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset); + + static Encoding ExtractEncoding(const Json::Value& json, + Encoding defaultEncoding); + + static DcmDataset* FromJson(const Json::Value& json, // Encoded using UTF-8 + bool generateIdentifiers, + bool decodeDataUriScheme, + Encoding defaultEncoding); + + static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer, + size_t size); + + static void FromJson(DicomMap& values, + const Json::Value& result); + + static bool LookupTransferSyntax(std::string& result, + DcmFileFormat& dicom); + +#if ORTHANC_ENABLE_LUA == 1 + static void ExecuteToDicom(DicomMap& target, + LuaFunctionCall& call); +#endif + + static void ExtractDicomSummary(DicomMap& target, + DcmItem& dataset); + + static void ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset); + + static void InitializeCodecs(); + + static void FinalizeCodecs(); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomFrameIndex.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,439 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeaders.h" +#include "DicomFrameIndex.h" + +#include "../../OrthancException.h" +#include "../../DicomFormat/DicomImageInformation.h" +#include "../FromDcmtkBridge.h" +#include "../../Endianness.h" +#include "DicomImageDecoder.h" + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcdeftag.h> +#include <dcmtk/dcmdata/dcpxitem.h> +#include <dcmtk/dcmdata/dcpixseq.h> + +namespace Orthanc +{ + class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex + { + private: + DcmPixelSequence* pixelSequence_; + std::vector<DcmPixelItem*> startFragment_; + std::vector<unsigned int> countFragments_; + std::vector<unsigned int> frameSize_; + + void GetOffsetTable(std::vector<uint32_t>& table) + { + DcmPixelItem* item = NULL; + if (!pixelSequence_->getItem(item, 0).good() || + item == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + uint32_t length = item->getLength(); + if (length == 0) + { + table.clear(); + return; + } + + if (length % 4 != 0) + { + // Error: Each fragment is index with 4 bytes (uint32_t) + throw OrthancException(ErrorCode_BadFileFormat); + } + + uint8_t* content = NULL; + if (!item->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + table.resize(length / 4); + + // The offset table is always in little endian in the DICOM + // file. Swap it to host endianness if needed. + const uint32_t* offset = reinterpret_cast<const uint32_t*>(content); + for (size_t i = 0; i < table.size(); i++, offset++) + { + table[i] = le32toh(*offset); + } + } + + + public: + FragmentIndex(DcmPixelSequence* pixelSequence, + unsigned int countFrames) : + pixelSequence_(pixelSequence) + { + assert(pixelSequence != NULL); + + startFragment_.resize(countFrames); + countFragments_.resize(countFrames); + frameSize_.resize(countFrames); + + // The first fragment corresponds to the offset table + unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card()); + if (countFragments < countFrames + 1) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + if (countFragments == countFrames + 1) + { + // Simple case: There is one fragment per frame. + + DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + for (unsigned int i = 0; i < countFrames; i++) + { + fragment = pixelSequence_->nextInContainer(fragment); + startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment); + frameSize_[i] = fragment->getLength(); + countFragments_[i] = 1; + } + + return; + } + + // Parse the offset table + std::vector<uint32_t> offsetOfFrame; + GetOffsetTable(offsetOfFrame); + + if (offsetOfFrame.size() != countFrames || + offsetOfFrame[0] != 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + + // Loop over the fragments (ignoring the offset table). This is + // an alternative, faster implementation to DCMTK's + // "DcmCodec::determineStartFragment()". + DcmObject* fragment = pixelSequence_->nextInContainer(NULL); + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table + if (fragment == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + uint32_t offset = 0; + unsigned int currentFrame = 0; + startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment); + + unsigned int currentFragment = 1; + while (fragment != NULL) + { + if (currentFrame + 1 < countFrames && + offset == offsetOfFrame[currentFrame + 1]) + { + currentFrame += 1; + startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment); + } + + frameSize_[currentFrame] += fragment->getLength(); + countFragments_[currentFrame]++; + + // 8 bytes = overhead for the item tag and length field + offset += fragment->getLength() + 8; + + currentFragment++; + fragment = pixelSequence_->nextInContainer(fragment); + } + + if (currentFragment != countFragments || + currentFrame + 1 != countFrames || + fragment != NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + assert(startFragment_.size() == countFragments_.size() && + startFragment_.size() == frameSize_.size()); + } + + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + if (index >= startFragment_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + frame.resize(frameSize_[index]); + if (frame.size() == 0) + { + return; + } + + uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]); + + size_t offset = 0; + DcmPixelItem* fragment = startFragment_[index]; + for (unsigned int i = 0; i < countFragments_[index]; i++) + { + uint8_t* content = NULL; + if (!fragment->getUint8Array(content).good() || + content == NULL) + { + throw OrthancException(ErrorCode_InternalError); + } + + assert(offset + fragment->getLength() <= frame.size()); + + memcpy(target + offset, content, fragment->getLength()); + offset += fragment->getLength(); + + fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); + } + } + }; + + + + class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex + { + private: + uint8_t* pixelData_; + size_t frameSize_; + + public: + UncompressedIndex(DcmDataset& dataset, + unsigned int countFrames, + size_t frameSize) : + pixelData_(NULL), + frameSize_(frameSize) + { + size_t size = 0; + + DcmElement* e; + if (dataset.findAndGetElement(DCM_PixelData, e).good() && + e != NULL) + { + size = e->getLength(); + + if (size > 0) + { + pixelData_ = NULL; + if (!e->getUint8Array(pixelData_).good() || + pixelData_ == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + } + + if (size < frameSize_ * countFrames) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + frame.resize(frameSize_); + if (frameSize_ > 0) + { + memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); + } + } + }; + + + class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex + { + private: + std::string pixelData_; + size_t frameSize_; + + public: + PsmctRle1Index(DcmDataset& dataset, + unsigned int countFrames, + size_t frameSize) : + frameSize_(frameSize) + { + if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) || + pixelData_.size() < frameSize * countFrames) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const + { + frame.resize(frameSize_); + if (frameSize_ > 0) + { + memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); + } + } + }; + + + + bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom) + { + // Retrieve the transfer syntax from the DICOM header + const char* value = NULL; + if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() || + value == NULL) + { + return false; + } + + const std::string transferSyntax(value); + + // Video standards supported in DICOM 2016a + // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html + if (transferSyntax == "1.2.840.10008.1.2.4.100" || // MPEG2 MP@ML option of ISO/IEC MPEG2 + transferSyntax == "1.2.840.10008.1.2.4.101" || // MPEG2 MP@HL option of ISO/IEC MPEG2 + transferSyntax == "1.2.840.10008.1.2.4.102" || // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264 + transferSyntax == "1.2.840.10008.1.2.4.103" || // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264 + transferSyntax == "1.2.840.10008.1.2.4.104" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 + transferSyntax == "1.2.840.10008.1.2.4.105" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 + transferSyntax == "1.2.840.10008.1.2.4.106") // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264 + { + return true; + } + + return false; + } + + + unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom) + { + // Assume 1 frame for video transfer syntaxes + if (IsVideo(dicom)) + { + return 1; + } + + const char* tmp = NULL; + if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() || + tmp == NULL) + { + return 1; + } + + int count = -1; + try + { + count = boost::lexical_cast<int>(tmp); + } + catch (boost::bad_lexical_cast&) + { + } + + if (count < 0) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + return count; + } + } + + + DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom) + { + countFrames_ = GetFramesCount(dicom); + if (countFrames_ == 0) + { + // The image has no frame. No index is to be built. + return; + } + + DcmDataset& dataset = *dicom.getDataset(); + + // Test whether this image is composed of a sequence of fragments + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + if (pixelSequence != NULL) + { + index_.reset(new FragmentIndex(pixelSequence, countFrames_)); + return; + } + + // Extract information about the image structure + DicomMap tags; + FromDcmtkBridge::ExtractDicomSummary(tags, dataset); + + DicomImageInformation information(tags); + + // Access to the raw pixel data + if (DicomImageDecoder::IsPsmctRle1(dataset)) + { + index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize())); + } + else + { + index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize())); + } + } + + + void DicomFrameIndex::GetRawFrame(std::string& frame, + unsigned int index) const + { + if (index >= countFrames_) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else if (index_.get() != NULL) + { + return index_->GetRawFrame(frame, index); + } + else + { + frame.clear(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomFrameIndex.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,83 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../../Enumerations.h" + +#include <dcmtk/dcmdata/dcdatset.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <vector> +#include <stdint.h> +#include <boost/noncopyable.hpp> +#include <memory> + +namespace Orthanc +{ + class DicomFrameIndex + { + private: + class IIndex : public boost::noncopyable + { + public: + virtual ~IIndex() + { + } + + virtual void GetRawFrame(std::string& frame, + unsigned int index) const = 0; + }; + + class FragmentIndex; + class UncompressedIndex; + class PsmctRle1Index; + + std::auto_ptr<IIndex> index_; + unsigned int countFrames_; + + public: + DicomFrameIndex(DcmFileFormat& dicom); + + unsigned int GetFramesCount() const + { + return countFrames_; + } + + void GetRawFrame(std::string& frame, + unsigned int index) const; + + static bool IsVideo(DcmFileFormat& dicom); + + static unsigned int GetFramesCount(DcmFileFormat& dicom); + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,817 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../../PrecompiledHeaders.h" +#include "DicomImageDecoder.h" + + +/*========================================================================= + + This file is based on portions of the following project + (cf. function "DecodePsmctRle1()"): + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + + Copyright (c) 2006-2011 Mathieu Malaterre + Copyright (c) 1993-2005 CREATIS + (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + =========================================================================*/ + + +#include "../../Logging.h" +#include "../../OrthancException.h" +#include "../../Images/Image.h" +#include "../../Images/ImageProcessing.h" +#include "../../DicomFormat/DicomIntegerPixelAccessor.h" +#include "../ToDcmtkBridge.h" +#include "../FromDcmtkBridge.h" +#include "../ParsedDicomFile.h" + +#if ORTHANC_ENABLE_PNG == 1 +# include "../../Images/PngWriter.h" +#endif + +#if ORTHANC_ENABLE_JPEG == 1 +# include "../../Images/JpegWriter.h" +#endif + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcrleccd.h> +#include <dcmtk/dcmdata/dcrlecp.h> + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 +# include <dcmtk/dcmjpls/djcodecd.h> +# include <dcmtk/dcmjpls/djcparam.h> +# include <dcmtk/dcmjpeg/djrplol.h> +#endif + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 +# include <dcmtk/dcmjpeg/djcodecd.h> +# include <dcmtk/dcmjpeg/djcparam.h> +# include <dcmtk/dcmjpeg/djdecbas.h> +# include <dcmtk/dcmjpeg/djdecext.h> +# include <dcmtk/dcmjpeg/djdeclol.h> +# include <dcmtk/dcmjpeg/djdecpro.h> +# include <dcmtk/dcmjpeg/djdecsps.h> +# include <dcmtk/dcmjpeg/djdecsv1.h> +#endif + +#if DCMTK_VERSION_NUMBER <= 360 +# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax +# define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax +# define EXS_JPEGProcess6_8 EXS_JPEGProcess6_8TransferSyntax +# define EXS_JPEGProcess10_12 EXS_JPEGProcess10_12TransferSyntax +# define EXS_JPEGProcess14 EXS_JPEGProcess14TransferSyntax +# define EXS_JPEGProcess14SV1 EXS_JPEGProcess14SV1TransferSyntax +#endif + +namespace Orthanc +{ + static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); + static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); + + + bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset) + { + DcmElement* e; + char* c; + + // Check whether the DICOM instance contains an image encoded with + // the PMSCT_RLE1 scheme. + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || + e == NULL || + !e->isaString() || + !e->getString(c).good() || + c == NULL || + strcmp("PMSCT_RLE1", c)) + { + return false; + } + else + { + return true; + } + } + + + bool DicomImageDecoder::DecodePsmctRle1(std::string& output, + DcmDataset& dataset) + { + // Check whether the DICOM instance contains an image encoded with + // the PMSCT_RLE1 scheme. + if (!IsPsmctRle1(dataset)) + { + return false; + } + + // OK, this is a custom RLE encoding from Philips. Get the pixel + // data from the appropriate private DICOM tag. + Uint8* pixData = NULL; + DcmElement* e; + if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || + e == NULL || + e->getUint8Array(pixData) != EC_Normal) + { + return false; + } + + // The "unsigned" below IS VERY IMPORTANT + const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData); + const size_t length = e->getLength(); + + /** + * The code below is an adaptation of a sample code for GDCM by + * Mathieu Malaterre (under a BSD license). + * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html + **/ + + // RLE pass + std::vector<uint8_t> temp; + temp.reserve(length); + for (size_t i = 0; i < length; i++) + { + if (inbuffer[i] == 0xa5) + { + temp.push_back(inbuffer[i+2]); + for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) + { + temp.push_back(inbuffer[i+2]); + } + i += 2; + } + else + { + temp.push_back(inbuffer[i]); + } + } + + // Delta encoding pass + uint16_t delta = 0; + output.clear(); + output.reserve(temp.size()); + for (size_t i = 0; i < temp.size(); i++) + { + uint16_t value; + + if (temp[i] == 0x5a) + { + uint16_t v1 = temp[i + 1]; + uint16_t v2 = temp[i + 2]; + value = (v2 << 8) + v1; + i += 2; + } + else + { + value = delta + (int8_t) temp[i]; + } + + output.push_back(value & 0xff); + output.push_back(value >> 8); + delta = value; + } + + if (output.size() % 2) + { + output.resize(output.size() - 1); + } + + return true; + } + + + class DicomImageDecoder::ImageSource + { + private: + std::string psmct_; + std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_; + + public: + void Setup(DcmDataset& dataset, + unsigned int frame) + { + psmct_.clear(); + slowAccessor_.reset(NULL); + + // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data + + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, dataset); + + /** + * Create an accessor to the raw values of the DICOM image. + **/ + + DcmElement* e; + if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && + e != NULL) + { + Uint8* pixData = NULL; + if (e->getUint8Array(pixData) == EC_Normal) + { + slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); + } + } + else if (DecodePsmctRle1(psmct_, dataset)) + { + LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; + Uint8* pixData = NULL; + if (psmct_.size() > 0) + { + pixData = reinterpret_cast<Uint8*>(&psmct_[0]); + } + + slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); + } + + if (slowAccessor_.get() == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + slowAccessor_->SetCurrentFrame(frame); + } + + unsigned int GetWidth() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetWidth(); + } + + unsigned int GetHeight() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetHeight(); + } + + unsigned int GetChannelCount() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetInformation().GetChannelCount(); + } + + const DicomIntegerPixelAccessor& GetAccessor() const + { + assert(slowAccessor_.get() != NULL); + return *slowAccessor_; + } + + unsigned int GetSize() const + { + assert(slowAccessor_.get() != NULL); + return slowAccessor_->GetSize(); + } + }; + + + ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset, + bool ignorePhotometricInterpretation) + { + DicomMap m; + FromDcmtkBridge::ExtractDicomSummary(m, dataset); + + DicomImageInformation info(m); + PixelFormat format; + + if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation)) + { + LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() + << "bpp, " << info.GetChannelCount() << " channels, " + << (info.IsSigned() ? "signed" : "unsigned") + << (info.IsPlanar() ? ", planar, " : ", non-planar, ") + << EnumerationToString(info.GetPhotometricInterpretation()) + << " photometric interpretation"; + throw OrthancException(ErrorCode_NotImplemented); + } + + return new Image(format, info.GetWidth(), info.GetHeight(), false); + } + + + template <typename PixelType> + static void CopyPixels(ImageAccessor& target, + const DicomIntegerPixelAccessor& source) + { + const PixelType minValue = std::numeric_limits<PixelType>::min(); + const PixelType maxValue = std::numeric_limits<PixelType>::max(); + + for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) + { + PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y)); + for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) + { + for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) + { + int32_t v = source.GetValue(x, y, c); + if (v < static_cast<int32_t>(minValue)) + { + *pixel = minValue; + } + else if (v > static_cast<int32_t>(maxValue)) + { + *pixel = maxValue; + } + else + { + *pixel = static_cast<PixelType>(v); + } + } + } + } + } + + + ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, + unsigned int frame) + { + ImageSource source; + source.Setup(dataset, frame); + + + /** + * Resize the target image. + **/ + + std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); + + if (source.GetWidth() != target->GetWidth() || + source.GetHeight() != target->GetHeight()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * If the format of the DICOM buffer is natively supported, use a + * direct access to copy its values. + **/ + + const DicomImageInformation& info = source.GetAccessor().GetInformation(); + + bool fastVersionSuccess = false; + PixelFormat sourceFormat; + if (!info.IsPlanar() && + info.ExtractPixelFormat(sourceFormat, false)) + { + try + { + size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat); + if ((frame + 1) * frameSize <= source.GetSize()) + { + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData()); + + ImageAccessor sourceImage; + sourceImage.AssignReadOnly(sourceFormat, + info.GetWidth(), + info.GetHeight(), + info.GetWidth() * GetBytesPerPixel(sourceFormat), + buffer + frame * frameSize); + + ImageProcessing::Convert(*target, sourceImage); + ImageProcessing::ShiftRight(*target, info.GetShift()); + fastVersionSuccess = true; + } + } + catch (OrthancException&) + { + // Unsupported conversion, use the slow version + } + } + + /** + * Slow version : loop over the DICOM buffer, storing its value + * into the target image. + **/ + + if (!fastVersionSuccess) + { + switch (target->GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + case PixelFormat_Grayscale8: + CopyPixels<uint8_t>(*target, source.GetAccessor()); + break; + + case PixelFormat_Grayscale16: + CopyPixels<uint16_t>(*target, source.GetAccessor()); + break; + + case PixelFormat_SignedGrayscale16: + CopyPixels<int16_t>(*target, source.GetAccessor()); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + } + + return target.release(); + } + + + ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec, + const DcmCodecParameter& parameters, + DcmDataset& dataset, + unsigned int frame) + { + DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); + if (pixelSequence == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); + + Uint32 startFragment = 0; // Default + OFString decompressedColorModel; // Out + DJ_RPLossless representationParameter; + OFCondition c = codec.decodeFrame(&representationParameter, + pixelSequence, ¶meters, + &dataset, frame, startFragment, target->GetBuffer(), + target->GetSize(), decompressedColorModel); + + if (c.good()) + { + return target.release(); + } + else + { + LOG(ERROR) << "Cannot decode an image"; + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, + unsigned int frame) + { + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + E_TransferSyntax syntax = dataset.getOriginalXfer(); + + /** + * Deal with uncompressed, raw images. + * http://support.dcmtk.org/docs/dcxfer_8h-source.html + **/ + if (syntax == EXS_Unknown || + syntax == EXS_LittleEndianImplicit || + syntax == EXS_BigEndianImplicit || + syntax == EXS_LittleEndianExplicit || + syntax == EXS_BigEndianExplicit) + { + return DecodeUncompressedImage(dataset, frame); + } + + +#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 + /** + * Deal with JPEG-LS images. + **/ + + if (syntax == EXS_JPEGLSLossless || + syntax == EXS_JPEGLSLossy) + { + DJLSCodecParameter parameters; + std::auto_ptr<DJLSDecoderBase> decoder; + + switch (syntax) + { + case EXS_JPEGLSLossless: + LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image"; + decoder.reset(new DJLSLosslessDecoder); + break; + + case EXS_JPEGLSLossy: + LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image"; + decoder.reset(new DJLSNearLosslessDecoder); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return ApplyCodec(*decoder, parameters, dataset, frame); + } +#endif + + +#if ORTHANC_ENABLE_DCMTK_JPEG == 1 + /** + * Deal with JPEG images. + **/ + + if (syntax == EXS_JPEGProcess1 || // DJDecoderBaseline + syntax == EXS_JPEGProcess2_4 || // DJDecoderExtended + syntax == EXS_JPEGProcess6_8 || // DJDecoderSpectralSelection (retired) + syntax == EXS_JPEGProcess10_12 || // DJDecoderProgressive (retired) + syntax == EXS_JPEGProcess14 || // DJDecoderLossless + syntax == EXS_JPEGProcess14SV1) // DJDecoderP14SV1 + { + // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d + DJCodecParameter parameters( + ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression + EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr + EUC_default, // Mode for UID creation, unused for decompression + EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation + std::auto_ptr<DJCodecDecoder> decoder; + + switch (syntax) + { + case EXS_JPEGProcess1: + LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image"; + decoder.reset(new DJDecoderBaseline); + break; + + case EXS_JPEGProcess2_4 : + LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image"; + decoder.reset(new DJDecoderExtended); + break; + + case EXS_JPEGProcess6_8: // Retired + LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image"; + decoder.reset(new DJDecoderSpectralSelection); + break; + + case EXS_JPEGProcess10_12: // Retired + LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image"; + decoder.reset(new DJDecoderProgressive); + break; + + case EXS_JPEGProcess14: + LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image"; + decoder.reset(new DJDecoderLossless); + break; + + case EXS_JPEGProcess14SV1: + LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image"; + decoder.reset(new DJDecoderP14SV1); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return ApplyCodec(*decoder, parameters, dataset, frame); + } +#endif + + + if (syntax == EXS_RLELossless) + { + LOG(INFO) << "Decoding a RLE lossless DICOM image"; + DcmRLECodecParameter parameters; + DcmRLECodecDecoder decoder; + return ApplyCodec(decoder, parameters, dataset, frame); + } + + + /** + * This DICOM image format is not natively supported by + * Orthanc. As a last resort, try and decode it through DCMTK by + * converting its transfer syntax to Little Endian. This will + * result in higher memory consumption. This is actually the + * second example of the following page: + * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples + **/ + + { + LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian"; + + std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); + converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); + + if (converted->canWriteXfer(EXS_LittleEndianExplicit)) + { + return DecodeUncompressedImage(*converted, frame); + } + } + + LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + + static bool IsColorImage(PixelFormat format) + { + return (format == PixelFormat_RGB24 || + format == PixelFormat_RGBA32); + } + + + bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, + PixelFormat format, + bool allowColorConversion) + { + // If specified, prevent the conversion between color and + // grayscale images + bool isSourceColor = IsColorImage(image->GetFormat()); + bool isTargetColor = IsColorImage(format); + + if (!allowColorConversion) + { + if (isSourceColor ^ isTargetColor) + { + return false; + } + } + + if (image->GetFormat() != format) + { + // A conversion is required + std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false)); + ImageProcessing::Convert(*target, *image); + image = target; + } + + return true; + } + + + bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image) + { + switch (image->GetFormat()) + { + case PixelFormat_RGB24: + { + // Directly return color images without modification (RGB) + return true; + } + + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + // Grayscale image: Stretch its dynamics to the [0,255] range + int64_t a, b; + ImageProcessing::GetMinMaxValue(a, b, *image); + + if (a == b) + { + ImageProcessing::Set(*image, 0); + } + else + { + ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); + } + + // If the source image is not grayscale 8bpp, convert it + if (image->GetFormat() != PixelFormat_Grayscale8) + { + std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); + ImageProcessing::Convert(*target, *image); + image = target; + } + + return true; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + + void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert) + { + if (image.get() == NULL) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + bool ok = false; + + switch (mode) + { + case ImageExtractionMode_UInt8: + ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false); + break; + + case ImageExtractionMode_UInt16: + ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false); + break; + + case ImageExtractionMode_Int16: + ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false); + break; + + case ImageExtractionMode_Preview: + ok = PreviewDecodedImage(image); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (ok) + { + assert(image.get() != NULL); + + if (invert) + { + Orthanc::ImageProcessing::Invert(*image); + } + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } + + +#if ORTHANC_ENABLE_PNG == 1 + void DicomImageDecoder::ExtractPngImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert) + { + ApplyExtractionMode(image, mode, invert); + + PngWriter writer; + writer.WriteToMemory(result, *image); + } +#endif + + +#if ORTHANC_ENABLE_JPEG == 1 + void DicomImageDecoder::ExtractJpegImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert, + uint8_t quality) + { + if (mode != ImageExtractionMode_UInt8 && + mode != ImageExtractionMode_Preview) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + ApplyExtractionMode(image, mode, invert); + + JpegWriter writer; + writer.SetQuality(quality); + writer.WriteToMemory(result, *image); + } +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/Internals/DicomImageDecoder.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,117 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../ParsedDicomFile.h" + +#include <memory> + +#if !defined(ORTHANC_ENABLE_JPEG) +# error The macro ORTHANC_ENABLE_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_PNG) +# error The macro ORTHANC_ENABLE_PNG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined +#endif + +#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) +# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined +#endif + + +class DcmDataset; +class DcmCodec; +class DcmCodecParameter; + +namespace Orthanc +{ + class DicomImageDecoder : public boost::noncopyable + { + private: + class ImageSource; + + DicomImageDecoder() // This is a fully abstract class, no constructor + { + } + + static ImageAccessor* CreateImage(DcmDataset& dataset, + bool ignorePhotometricInterpretation); + + static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset, + unsigned int frame); + + static ImageAccessor* ApplyCodec(const DcmCodec& codec, + const DcmCodecParameter& parameters, + DcmDataset& dataset, + unsigned int frame); + + static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, + PixelFormat format, + bool allowColorConversion); + + static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image); + + static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert); + + public: + static bool IsPsmctRle1(DcmDataset& dataset); + + static bool DecodePsmctRle1(std::string& output, + DcmDataset& dataset); + + static ImageAccessor *Decode(ParsedDicomFile& dicom, + unsigned int frame); + +#if ORTHANC_ENABLE_PNG == 1 + static void ExtractPngImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert); +#endif + +#if ORTHANC_ENABLE_JPEG == 1 + static void ExtractJpegImage(std::string& result, + std::auto_ptr<ImageAccessor>& image, + ImageExtractionMode mode, + bool invert, + uint8_t quality); +#endif + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,1476 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + + +/*========================================================================= + + This file is based on portions of the following project: + + Program: GDCM (Grassroots DICOM). A DICOM library + Module: http://gdcm.sourceforge.net/Copyright.html + +Copyright (c) 2006-2011 Mathieu Malaterre +Copyright (c) 1993-2005 CREATIS +(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any + contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + + +#include "../PrecompiledHeaders.h" + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "ParsedDicomFile.h" + +#include "FromDcmtkBridge.h" +#include "ToDcmtkBridge.h" +#include "Internals/DicomFrameIndex.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../Toolbox.h" +#include "../SystemToolbox.h" + +#if ORTHANC_ENABLE_JPEG == 1 +# include "../Images/JpegReader.h" +#endif + +#if ORTHANC_ENABLE_PNG == 1 +# include "../Images/PngReader.h" +#endif + +#include <list> +#include <limits> + +#include <boost/lexical_cast.hpp> + +#include <dcmtk/dcmdata/dcchrstr.h> +#include <dcmtk/dcmdata/dcdicent.h> +#include <dcmtk/dcmdata/dcdict.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <dcmtk/dcmdata/dcuid.h> +#include <dcmtk/dcmdata/dcmetinf.h> +#include <dcmtk/dcmdata/dcdeftag.h> + +#include <dcmtk/dcmdata/dcvrae.h> +#include <dcmtk/dcmdata/dcvras.h> +#include <dcmtk/dcmdata/dcvrcs.h> +#include <dcmtk/dcmdata/dcvrda.h> +#include <dcmtk/dcmdata/dcvrds.h> +#include <dcmtk/dcmdata/dcvrdt.h> +#include <dcmtk/dcmdata/dcvrfd.h> +#include <dcmtk/dcmdata/dcvrfl.h> +#include <dcmtk/dcmdata/dcvris.h> +#include <dcmtk/dcmdata/dcvrlo.h> +#include <dcmtk/dcmdata/dcvrlt.h> +#include <dcmtk/dcmdata/dcvrpn.h> +#include <dcmtk/dcmdata/dcvrsh.h> +#include <dcmtk/dcmdata/dcvrsl.h> +#include <dcmtk/dcmdata/dcvrss.h> +#include <dcmtk/dcmdata/dcvrst.h> +#include <dcmtk/dcmdata/dcvrtm.h> +#include <dcmtk/dcmdata/dcvrui.h> +#include <dcmtk/dcmdata/dcvrul.h> +#include <dcmtk/dcmdata/dcvrus.h> +#include <dcmtk/dcmdata/dcvrut.h> +#include <dcmtk/dcmdata/dcpixel.h> +#include <dcmtk/dcmdata/dcpixseq.h> +#include <dcmtk/dcmdata/dcpxitem.h> + + +#include <boost/math/special_functions/round.hpp> +#include <dcmtk/dcmdata/dcostrmb.h> +#include <boost/algorithm/string/predicate.hpp> + + +#if DCMTK_VERSION_NUMBER <= 360 +# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax +#endif + + +static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; + + + +namespace Orthanc +{ + struct ParsedDicomFile::PImpl + { + std::auto_ptr<DcmFileFormat> file_; + std::auto_ptr<DicomFrameIndex> frameIndex_; + }; + + + static void SendPathValueForDictionary(RestApiOutput& output, + DcmItem& dicom) + { + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < dicom.card(); i++) + { + DcmElement* element = dicom.getElement(i); + if (element) + { + char buf[16]; + sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); + v.append(buf); + } + } + + output.AnswerJson(v); + } + + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + static void ParseTagAndGroup(DcmTagKey& key, + const std::string& tag) + { + DicomTag t = FromDcmtkBridge::ParseTag(tag); + key = DcmTagKey(t.GetGroup(), t.GetElement()); + } + + + static void SendSequence(RestApiOutput& output, + DcmSequenceOfItems& sequence) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(v); + } + + + static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, + E_TransferSyntax transferSyntax) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + return pixelSequence->card(); + } + else + { + return 1; + } + } + + + namespace + { + class DicomFieldStream : public IHttpStreamAnswer + { + private: + DcmElement& element_; + uint32_t length_; + uint32_t offset_; + std::string chunk_; + size_t chunkSize_; + + public: + DicomFieldStream(DcmElement& element, + E_TransferSyntax transferSyntax) : + element_(element), + length_(element.getLength(transferSyntax)), + offset_(0), + chunkSize_(0) + { + static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB + chunk_.resize(CHUNK_SIZE); + } + + virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, + bool /*deflateAllowed*/) + { + // No support for compression + return HttpCompression_None; + } + + virtual bool HasContentFilename(std::string& filename) + { + return false; + } + + virtual std::string GetContentType() + { + return ""; + } + + virtual uint64_t GetContentLength() + { + return length_; + } + + virtual bool ReadNextChunk() + { + assert(offset_ <= length_); + + if (offset_ == length_) + { + return false; + } + else + { + if (length_ - offset_ < chunk_.size()) + { + chunkSize_ = length_ - offset_; + } + else + { + chunkSize_ = chunk_.size(); + } + + OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); + + offset_ += chunkSize_; + + if (!cond.good()) + { + LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); + throw OrthancException(ErrorCode_InternalError); + } + + return true; + } + } + + virtual const char *GetChunkContent() + { + return chunk_.c_str(); + } + + virtual size_t GetChunkSize() + { + return chunkSize_; + } + }; + } + + + static bool AnswerPixelData(RestApiOutput& output, + DcmItem& dicom, + E_TransferSyntax transferSyntax, + const std::string* blockUri) + { + DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + DcmElement *element = NULL; + if (!dicom.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + try + { + DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); + if (blockUri == NULL) + { + // The user asks how many blocks are present in this pixel data + unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); + + Json::Value result(Json::arrayValue); + for (unsigned int i = 0; i < blocks; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(result); + return true; + } + + + unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); + + if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) + { + DcmPixelSequence* pixelSequence = NULL; + if (pixelData.getEncapsulatedRepresentation + (transferSyntax, NULL, pixelSequence).good() && pixelSequence) + { + // This is the case for JPEG transfer syntaxes + if (block < pixelSequence->card()) + { + DcmPixelItem* pixelItem = NULL; + if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) + { + if (pixelItem->getLength() == 0) + { + output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); + return true; + } + + Uint8* buffer = NULL; + if (pixelItem->getUint8Array(buffer).good() && buffer) + { + output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); + return true; + } + } + } + } + else + { + // This is the case for raw, uncompressed image buffers + assert(*blockUri == "0"); + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); + } + } + } + catch (boost::bad_lexical_cast&) + { + // The URI entered by the user is not a number + } + catch (std::bad_cast&) + { + // This should never happen + } + + return false; + } + + + + static void SendPathValueForLeaf(RestApiOutput& output, + const std::string& tag, + DcmItem& dicom, + E_TransferSyntax transferSyntax) + { + DcmTagKey k; + ParseTagAndGroup(k, tag); + + DcmSequenceOfItems* sequence = NULL; + if (dicom.findAndGetSequence(k, sequence).good() && + sequence != NULL && + sequence->getVR() == EVR_SQ) + { + SendSequence(output, *sequence); + return; + } + + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && + element != NULL && + //element->getVR() != EVR_UNKNOWN && // This would forbid private tags + element->getVR() != EVR_SQ) + { + DicomFieldStream stream(*element, transferSyntax); + output.AnswerStream(stream); + } + } + + void ParsedDicomFile::SendPathValue(RestApiOutput& output, + const UriComponents& uri) + { + DcmItem* dicom = pimpl_->file_->getDataset(); + E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); + + // Special case: Accessing the pixel data + if (uri.size() == 1 || + uri.size() == 2) + { + DcmTagKey tag; + ParseTagAndGroup(tag, uri[0]); + + if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && + tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) + { + AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); + return; + } + } + + // Go down in the tag hierarchy according to the URI + for (size_t pos = 0; pos < uri.size() / 2; pos++) + { + size_t index; + try + { + index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DcmTagKey k; + DcmItem *child = NULL; + ParseTagAndGroup(k, uri[2 * pos]); + if (!dicom->findAndGetSequenceItem(k, child, index).good() || + child == NULL) + { + return; + } + + dicom = child; + } + + // We have reached the end of the URI + if (uri.size() % 2 == 0) + { + SendPathValueForDictionary(output, *dicom); + } + else + { + SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); + } + } + + + void ParsedDicomFile::Remove(const DicomTag& tag) + { + InvalidateCache(); + + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = pimpl_->file_->getDataset()->remove(key); + if (element != NULL) + { + delete element; + } + } + + + void ParsedDicomFile::Clear(const DicomTag& tag, + bool onlyIfExists) + { + InvalidateCache(); + + DcmItem* dicom = pimpl_->file_->getDataset(); + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + + if (onlyIfExists && + !dicom->tagExists(key)) + { + // The tag is non-existing, do not clear it + } + else + { + if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + } + + + void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep) + { + InvalidateCache(); + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + // Loop over the dataset to detect its private tags + typedef std::list<DcmElement*> Tags; + Tags privateTags; + + for (unsigned long i = 0; i < dataset.card(); i++) + { + DcmElement* element = dataset.getElement(i); + DcmTag tag(element->getTag()); + + // Is this a private tag? + if (tag.isPrivate()) + { + bool remove = true; + + // Check whether this private tag is to be kept + if (toKeep != NULL) + { + DicomTag tmp = FromDcmtkBridge::Convert(tag); + if (toKeep->find(tmp) != toKeep->end()) + { + remove = false; // Keep it + } + } + + if (remove) + { + privateTags.push_back(element); + } + } + } + + // Loop over the detected private tags to remove them + for (Tags::iterator it = privateTags.begin(); + it != privateTags.end(); ++it) + { + DcmElement* tmp = dataset.remove(*it); + if (tmp != NULL) + { + delete tmp; + } + } + } + + + static void InsertInternal(DcmDataset& dicom, + DcmElement* element) + { + OFCondition cond = dicom.insert(element, false, false); + if (!cond.good()) + { + // This field already exists + delete element; + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme) + { + if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag))) + { + throw OrthancException(ErrorCode_AlreadyExistingTag); + } + + if (decodeDataUriScheme && + value.type() == Json::stringValue && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(value.asString())) + { + return; + } + } + + InvalidateCache(); + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); + InsertInternal(*pimpl_->file_->getDataset(), element.release()); + } + + + static bool CanReplaceProceed(DcmDataset& dicom, + const DcmTagKey& tag, + DicomReplaceMode mode) + { + if (dicom.findAndDeleteElement(tag).good()) + { + // This tag was existing, it has been deleted + return true; + } + else + { + // This tag was absent, act wrt. the specified "mode" + switch (mode) + { + case DicomReplaceMode_InsertIfAbsent: + return true; + + case DicomReplaceMode_ThrowIfAbsent: + throw OrthancException(ErrorCode_InexistentItem); + + case DicomReplaceMode_IgnoreIfAbsent: + return false; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } + + + void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme) + { + if (tag != DICOM_TAG_SOP_CLASS_UID && + tag != DICOM_TAG_SOP_INSTANCE_UID) + { + return; + } + + std::string binary; + const std::string* decoded = &utf8Value; + + if (decodeDataUriScheme && + boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) + { + std::string mime; + if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + decoded = &binary; + } + else + { + Encoding encoding = GetEncoding(); + if (GetEncoding() != Encoding_Utf8) + { + binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); + decoded = &binary; + } + } + + /** + * dcmodify will automatically correct 'Media Storage SOP Class + * UID' and 'Media Storage SOP Instance UID' in the metaheader, if + * you make changes to the related tags in the dataset ('SOP Class + * UID' and 'SOP Instance UID') via insert or modify mode + * options. You can disable this behaviour by using the -nmu + * option. + **/ + + if (tag == DICOM_TAG_SOP_CLASS_UID) + { + ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded); + } + + if (tag == DICOM_TAG_SOP_INSTANCE_UID) + { + ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + DicomReplaceMode mode) + { + InvalidateCache(); + + DcmDataset& dicom = *pimpl_->file_->getDataset(); + if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) + { + // Either the tag was previously existing (and now removed), or + // the replace mode was set to "InsertIfAbsent" + + if (decodeDataUriScheme && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(utf8Value)) + { + return; + } + } + + std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); + FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding()); + + InsertInternal(dicom, element.release()); + UpdateStorageUid(tag, utf8Value, false); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const Json::Value& value, + bool decodeDataUriScheme, + DicomReplaceMode mode) + { + InvalidateCache(); + + DcmDataset& dicom = *pimpl_->file_->getDataset(); + if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) + { + // Either the tag was previously existing (and now removed), or + // the replace mode was set to "InsertIfAbsent" + + if (decodeDataUriScheme && + value.type() == Json::stringValue && + (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || + tag == DICOM_TAG_PIXEL_DATA)) + { + if (EmbedContentInternal(value.asString())) + { + return; + } + } + + InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); + + if (tag == DICOM_TAG_SOP_CLASS_UID || + tag == DICOM_TAG_SOP_INSTANCE_UID) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + UpdateStorageUid(tag, value.asString(), decodeDataUriScheme); + } + } + } + + + void ParsedDicomFile::Answer(RestApiOutput& output) + { + std::string serialized; + if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset())) + { + output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); + } + } + + + + bool ParsedDicomFile::GetTagValue(std::string& value, + const DicomTag& tag) + { + DcmTagKey k(tag.GetGroup(), tag.GetElement()); + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + if (tag.IsPrivate() || + FromDcmtkBridge::IsUnknownTag(tag) || + tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) + { + const Uint8* data = NULL; // This is freed in the destructor of the dataset + long unsigned int count = 0; + + if (dataset.findAndGetUint8Array(k, data, &count).good()) + { + if (count > 0) + { + assert(data != NULL); + value.assign(reinterpret_cast<const char*>(data), count); + } + else + { + value.clear(); + } + + return true; + } + else + { + return false; + } + } + else + { + DcmElement* element = NULL; + if (!dataset.findAndGetElement(k, element).good() || + element == NULL) + { + return false; + } + + std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement + (*element, DicomToJsonFlags_Default, + ORTHANC_MAXIMUM_TAG_LENGTH, GetEncoding())); + + if (v.get() == NULL || + v->IsNull()) + { + value = ""; + } + else + { + // TODO v->IsBinary() + value = v->GetContent(); + } + + return true; + } + } + + + DicomInstanceHasher ParsedDicomFile::GetHasher() + { + std::string patientId, studyUid, seriesUid, instanceUid; + + if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) || + !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || + !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || + !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); + } + + + void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) + { + FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset()); + } + + + void ParsedDicomFile::SaveToFile(const std::string& path) + { + // TODO Avoid using a temporary memory buffer, write directly on disk + std::string content; + SaveToMemoryBuffer(content); + SystemToolbox::WriteFile(content, path); + } + + + ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat); + + if (createIdentifiers) + { + ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); + ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); + ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); + ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + } + + + void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source, + Encoding defaultEncoding) + { + pimpl_->file_.reset(new DcmFileFormat); + + const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET); + if (tmp != NULL) + { + Encoding encoding; + if (tmp->IsNull() || + tmp->IsBinary() || + !GetDicomEncoding(encoding, tmp->GetContent().c_str())) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + SetEncoding(encoding); + } + } + else + { + SetEncoding(defaultEncoding); + } + + for (DicomMap::Map::const_iterator + it = source.map_.begin(); it != source.map_.end(); ++it) + { + if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET && + !it->second->IsNull()) + { + ReplacePlainString(it->first, it->second->GetContent()); + } + } + } + + + ParsedDicomFile::ParsedDicomFile(const DicomMap& map, + Encoding defaultEncoding) : + pimpl_(new PImpl) + { + CreateFromDicomMap(map, defaultEncoding); + } + + + ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : + pimpl_(new PImpl) + { + CreateFromDicomMap(map, GetDefaultDicomEncoding()); + } + + + ParsedDicomFile::ParsedDicomFile(const void* content, + size_t size) : pimpl_(new PImpl) + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size)); + } + + ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl) + { + if (content.size() == 0) + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0)); + } + else + { + pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size())); + } + } + + + ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) : + pimpl_(new PImpl) + { + pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone())); + + // Create a new instance-level identifier + ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); + } + + + ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat(&dicom)); + } + + + ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl) + { + pimpl_->file_.reset(new DcmFileFormat(dicom)); + } + + + ParsedDicomFile::~ParsedDicomFile() + { + delete pimpl_; + } + + + DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const + { + return *pimpl_->file_.get(); + } + + + ParsedDicomFile* ParsedDicomFile::Clone() + { + return new ParsedDicomFile(*this); + } + + + bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme) + { + std::string mime, content; + if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme)) + { + return false; + } + + Toolbox::ToLowerCase(mime); + + if (mime == "image/png") + { +#if ORTHANC_ENABLE_PNG == 1 + EmbedImage(mime, content); +#else + LOG(ERROR) << "Orthanc was compiled without support of PNG"; + throw OrthancException(ErrorCode_NotImplemented); +#endif + } + else if (mime == "image/jpeg") + { +#if ORTHANC_ENABLE_JPEG == 1 + EmbedImage(mime, content); +#else + LOG(ERROR) << "Orthanc was compiled without support of JPEG"; + throw OrthancException(ErrorCode_NotImplemented); +#endif + } + else if (mime == "application/pdf") + { + EmbedPdf(content); + } + else + { + LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime; + throw OrthancException(ErrorCode_NotImplemented); + } + + return true; + } + + + void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) + { + if (!EmbedContentInternal(dataUriScheme)) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + +#if (ORTHANC_ENABLE_JPEG == 1 && \ + ORTHANC_ENABLE_PNG == 1) + void ParsedDicomFile::EmbedImage(const std::string& mime, + const std::string& content) + { + if (mime == "image/png") + { + PngReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + } + else if (mime == "image/jpeg") + { + JpegReader reader; + reader.ReadFromMemory(content); + EmbedImage(reader); + } + else + { + throw OrthancException(ErrorCode_NotImplemented); + } + } +#endif + + + void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor) + { + if (accessor.GetFormat() != PixelFormat_Grayscale8 && + accessor.GetFormat() != PixelFormat_Grayscale16 && + accessor.GetFormat() != PixelFormat_SignedGrayscale16 && + accessor.GetFormat() != PixelFormat_RGB24 && + accessor.GetFormat() != PixelFormat_RGBA32) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + InvalidateCache(); + + if (accessor.GetFormat() == PixelFormat_RGBA32) + { + LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; + } + + // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html + + Remove(DICOM_TAG_PIXEL_DATA); + ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth())); + ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight())); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); + ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1"); + + if (accessor.GetFormat() == PixelFormat_SignedGrayscale16) + { + ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1"); + } + else + { + ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels + } + + ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved + ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); + + unsigned int bytesPerPixel = 0; + + switch (accessor.GetFormat()) + { + case PixelFormat_Grayscale8: + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); + bytesPerPixel = 1; + break; + + case PixelFormat_RGB24: + case PixelFormat_RGBA32: + ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); + ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); + bytesPerPixel = 3; + break; + + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16"); + ReplacePlainString(DICOM_TAG_BITS_STORED, "16"); + ReplacePlainString(DICOM_TAG_HIGH_BIT, "15"); + bytesPerPixel = 2; + break; + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + assert(bytesPerPixel != 0); + + DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), + DICOM_TAG_PIXEL_DATA.GetElement()); + + std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key)); + + unsigned int pitch = accessor.GetWidth() * bytesPerPixel; + Uint8* target = NULL; + pixels->createUint8Array(accessor.GetHeight() * pitch, target); + + for (unsigned int y = 0; y < accessor.GetHeight(); y++) + { + switch (accessor.GetFormat()) + { + case PixelFormat_RGB24: + case PixelFormat_Grayscale8: + case PixelFormat_Grayscale16: + case PixelFormat_SignedGrayscale16: + { + memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch); + target += pitch; + break; + } + + case PixelFormat_RGBA32: + { + // The alpha channel is not supported by the DICOM standard + const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)); + for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4) + { + target[0] = source[0]; + target[1] = source[1]; + target[2] = source[2]; + } + + break; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + } + + if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good()) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + Encoding ParsedDicomFile::GetEncoding() const + { + return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(), + GetDefaultDicomEncoding()); + } + + + void ParsedDicomFile::SetEncoding(Encoding encoding) + { + if (encoding == Encoding_Windows1251) + { + // This Cyrillic codepage is not officially supported by the + // DICOM standard. Do not set the SpecificCharacterSet tag. + return; + } + + std::string s = GetDicomSpecificCharacterSet(encoding); + ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s); + } + + void ParsedDicomFile::DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), + format, flags, maxStringLength, GetDefaultDicomEncoding()); + } + + + void ParsedDicomFile::DatasetToJson(Json::Value& target) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset()); + } + + + void ParsedDicomFile::HeaderToJson(Json::Value& target, + DicomToJsonFormat format) + { + FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0); + } + + + bool ParsedDicomFile::HasTag(const DicomTag& tag) const + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + return pimpl_->file_->getDataset()->tagExists(key); + } + + + void ParsedDicomFile::EmbedPdf(const std::string& pdf) + { + if (pdf.size() < 5 || // (*) + strncmp("%PDF-", pdf.c_str(), 5) != 0) + { + LOG(ERROR) << "Not a PDF file"; + throw OrthancException(ErrorCode_BadFileFormat); + } + + InvalidateCache(); + + ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT"); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); + ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf"); + //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); + + std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); + + size_t s = pdf.size(); + if (s & 1) + { + // The size of the buffer must be even + s += 1; + } + + Uint8* bytes = NULL; + OFCondition result = element->createUint8Array(s, bytes); + if (!result.good() || bytes == NULL) + { + throw OrthancException(ErrorCode_NotEnoughMemory); + } + + // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) + bytes[s - 1] = 0; + + memcpy(bytes, pdf.c_str(), pdf.size()); + + DcmPolymorphOBOW* obj = element.release(); + result = pimpl_->file_->getDataset()->insert(obj); + + if (!result.good()) + { + delete obj; + throw OrthancException(ErrorCode_NotEnoughMemory); + } + } + + + bool ParsedDicomFile::ExtractPdf(std::string& pdf) + { + std::string sop, mime; + + if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || + !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || + sop != UID_EncapsulatedPDFStorage || + mime != "application/pdf") + { + return false; + } + + if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) + { + return false; + } + + // Strip the possible pad byte at the end of file, because the + // encapsulated documents must always have an even length. The PDF + // format expects files to end with %%EOF followed by CR/LF. If + // the last character of the file is not a CR or LF, we assume it + // is a pad byte and remove it. + if (pdf.size() > 0) + { + char last = *pdf.rbegin(); + + if (last != 10 && last != 13) + { + pdf.resize(pdf.size() - 1); + } + } + + return true; + } + + + ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, + DicomFromJsonFlags flags) + { + const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; + const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; + + std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers)); + result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding())); + + const Json::Value::Members tags = json.getMemberNames(); + + for (size_t i = 0; i < tags.size(); i++) + { + DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); + const Json::Value& value = json[tags[i]]; + + if (tag == DICOM_TAG_PIXEL_DATA || + tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + else + { + result->EmbedContent(value.asString()); + } + } + else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent); + } + } + + return result.release(); + } + + + void ParsedDicomFile::GetRawFrame(std::string& target, + std::string& mime, + unsigned int frameId) + { + if (pimpl_->frameIndex_.get() == NULL) + { + pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_)); + } + + pimpl_->frameIndex_->GetRawFrame(target, frameId); + + E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); + switch (transferSyntax) + { + case EXS_JPEGProcess1: + mime = "image/jpeg"; + break; + + case EXS_JPEG2000LosslessOnly: + case EXS_JPEG2000: + mime = "image/jp2"; + break; + + default: + mime = "application/octet-stream"; + break; + } + } + + + void ParsedDicomFile::InvalidateCache() + { + pimpl_->frameIndex_.reset(NULL); + } + + + unsigned int ParsedDicomFile::GetFramesCount() const + { + return DicomFrameIndex::GetFramesCount(*pimpl_->file_); + } + + + void ParsedDicomFile::ChangeEncoding(Encoding target) + { + Encoding source = GetEncoding(); + + if (source != target) // Avoid unnecessary conversion + { + ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target)); + FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target); + } + } + + + void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const + { + FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset()); + } + + + void ParsedDicomFile::ExtractDicomAsJson(Json::Value& target) const + { + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset()); + } + + + bool ParsedDicomFile::LookupTransferSyntax(std::string& result) + { + return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_); + } + + + bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const + { + DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), + DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); + + DcmDataset& dataset = *pimpl_->file_->getDataset(); + + const char *c = NULL; + if (dataset.findAndGetString(k, c).good() && + c != NULL) + { + result = StringToPhotometricInterpretation(c); + return true; + } + else + { + return false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ParsedDicomFile.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,203 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../DicomFormat/DicomInstanceHasher.h" +#include "../Images/ImageAccessor.h" +#include "../IDynamicObject.h" +#include "../RestApi/RestApiOutput.h" +#include "../Toolbox.h" + +#if !defined(ORTHANC_ENABLE_JPEG) +# error Macro ORTHANC_ENABLE_JPEG must be defined to use this file +#endif + +#if !defined(ORTHANC_ENABLE_PNG) +# error Macro ORTHANC_ENABLE_PNG must be defined to use this file +#endif + +class DcmDataset; +class DcmFileFormat; + +namespace Orthanc +{ + class ParsedDicomFile : public IDynamicObject + { + private: + struct PImpl; + PImpl* pimpl_; + + ParsedDicomFile(ParsedDicomFile& other); + + void CreateFromDicomMap(const DicomMap& source, + Encoding defaultEncoding); + + void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep); + + void UpdateStorageUid(const DicomTag& tag, + const std::string& value, + bool decodeDataUriScheme); + + void InvalidateCache(); + + bool EmbedContentInternal(const std::string& dataUriScheme); + + public: + ParsedDicomFile(bool createIdentifiers); // Create a minimal DICOM instance + + ParsedDicomFile(const DicomMap& map, + Encoding defaultEncoding); + + ParsedDicomFile(const DicomMap& map); + + ParsedDicomFile(const void* content, + size_t size); + + ParsedDicomFile(const std::string& content); + + ParsedDicomFile(DcmDataset& dicom); + + ParsedDicomFile(DcmFileFormat& dicom); + + ~ParsedDicomFile(); + + DcmFileFormat& GetDcmtkObject() const; + + ParsedDicomFile* Clone(); + + void SendPathValue(RestApiOutput& output, + const UriComponents& uri); + + void Answer(RestApiOutput& output); + + void Remove(const DicomTag& tag); + + // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) + void Clear(const DicomTag& tag, + bool onlyIfExists); + + void Replace(const DicomTag& tag, + const std::string& utf8Value, + bool decodeDataUriScheme, + DicomReplaceMode mode); + + void Replace(const DicomTag& tag, + const Json::Value& value, // Assumed to be encoded with UTF-8 + bool decodeDataUriScheme, + DicomReplaceMode mode); + + void Insert(const DicomTag& tag, + const Json::Value& value, // Assumed to be encoded with UTF-8 + bool decodeDataUriScheme); + + void ReplacePlainString(const DicomTag& tag, + const std::string& utf8Value) + { + Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent); + } + + void RemovePrivateTags() + { + RemovePrivateTagsInternal(NULL); + } + + void RemovePrivateTags(const std::set<DicomTag>& toKeep) + { + RemovePrivateTagsInternal(&toKeep); + } + + // WARNING: This function handles the decoding of strings to UTF8 + bool GetTagValue(std::string& value, + const DicomTag& tag); + + DicomInstanceHasher GetHasher(); + + void SaveToMemoryBuffer(std::string& buffer); + + void SaveToFile(const std::string& path); + + void EmbedContent(const std::string& dataUriScheme); + + void EmbedImage(const ImageAccessor& accessor); + +#if (ORTHANC_ENABLE_JPEG == 1 && \ + ORTHANC_ENABLE_PNG == 1) + void EmbedImage(const std::string& mime, + const std::string& content); +#endif + + Encoding GetEncoding() const; + + // WARNING: This function only sets the encoding, it will not + // convert the encoding of the tags. Use "ChangeEncoding()" if need be. + void SetEncoding(Encoding encoding); + + void DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength); + + // This version uses the default parameters for + // FileContentType_DicomAsJson + void DatasetToJson(Json::Value& target); + + void HeaderToJson(Json::Value& target, + DicomToJsonFormat format); + + bool HasTag(const DicomTag& tag) const; + + void EmbedPdf(const std::string& pdf); + + bool ExtractPdf(std::string& pdf); + + void GetRawFrame(std::string& target, // OUT + std::string& mime, // OUT + unsigned int frameId); // IN + + unsigned int GetFramesCount() const; + + static ParsedDicomFile* CreateFromJson(const Json::Value& value, + DicomFromJsonFlags flags); + + void ChangeEncoding(Encoding target); + + void ExtractDicomSummary(DicomMap& target) const; + + void ExtractDicomAsJson(Json::Value& target) const; + + bool LookupTransferSyntax(std::string& result); + + bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; + }; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ToDcmtkBridge.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,150 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "ToDcmtkBridge.h" + +#include <memory> +#include <dcmtk/dcmnet/diutil.h> + +#include "../OrthancException.h" + + +namespace Orthanc +{ + DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr) + { + switch (vr) + { + case ValueRepresentation_ApplicationEntity: + return EVR_AE; + + case ValueRepresentation_AgeString: + return EVR_AS; + + case ValueRepresentation_AttributeTag: + return EVR_AT; + + case ValueRepresentation_CodeString: + return EVR_CS; + + case ValueRepresentation_Date: + return EVR_DA; + + case ValueRepresentation_DecimalString: + return EVR_DS; + + case ValueRepresentation_DateTime: + return EVR_DT; + + case ValueRepresentation_FloatingPointSingle: + return EVR_FL; + + case ValueRepresentation_FloatingPointDouble: + return EVR_FD; + + case ValueRepresentation_IntegerString: + return EVR_IS; + + case ValueRepresentation_LongString: + return EVR_LO; + + case ValueRepresentation_LongText: + return EVR_LT; + + case ValueRepresentation_OtherByte: + return EVR_OB; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_OtherDouble: + return EVR_OD;*/ + + case ValueRepresentation_OtherFloat: + return EVR_OF; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_OtherLong: + return EVR_OL;*/ + + case ValueRepresentation_OtherWord: + return EVR_OW; + + case ValueRepresentation_PersonName: + return EVR_PN; + + case ValueRepresentation_ShortString: + return EVR_SH; + + case ValueRepresentation_SignedLong: + return EVR_SL; + + case ValueRepresentation_Sequence: + return EVR_SQ; + + case ValueRepresentation_SignedShort: + return EVR_SS; + + case ValueRepresentation_ShortText: + return EVR_ST; + + case ValueRepresentation_Time: + return EVR_TM; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_UnlimitedCharacters: + return EVR_UC;*/ + + case ValueRepresentation_UniqueIdentifier: + return EVR_UI; + + case ValueRepresentation_UnsignedLong: + return EVR_UL; + + case ValueRepresentation_Unknown: + return EVR_UN; + + // Not supported as of DCMTK 3.6.0 + /*case ValueRepresentation_UniversalResource: + return EVR_UR;*/ + + case ValueRepresentation_UnsignedShort: + return EVR_US; + + case ValueRepresentation_UnlimitedText: + return EVR_UT; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Core/DicomParsing/ToDcmtkBridge.h Tue Aug 29 21:17:35 2017 +0200 @@ -0,0 +1,55 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017 Osimis, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_DCMTK != 1 +# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 +#endif + +#include "../DicomFormat/DicomMap.h" +#include <dcmtk/dcmdata/dcdatset.h> + +namespace Orthanc +{ + class ToDcmtkBridge + { + public: + static DcmTagKey Convert(const DicomTag& tag) + { + return DcmTagKey(tag.GetGroup(), tag.GetElement()); + } + + static DcmEVR Convert(ValueRepresentation vr); + }; +}
--- a/Core/Enumerations.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/Core/Enumerations.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -758,6 +758,116 @@ } + const char* EnumerationToString(ModalityManufacturer manufacturer) + { + switch (manufacturer) + { + case ModalityManufacturer_Generic: + return "Generic"; + + case ModalityManufacturer_GenericNoWildcardInDates: + return "GenericNoWildcardInDates"; + + case ModalityManufacturer_GenericNoUniversalWildcard: + return "GenericNoUniversalWildcard"; + + case ModalityManufacturer_StoreScp: + return "StoreScp"; + + case ModalityManufacturer_ClearCanvas: + return "ClearCanvas"; + + case ModalityManufacturer_Dcm4Chee: + return "Dcm4Chee"; + + case ModalityManufacturer_Vitrea: + return "Vitrea"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(DicomRequestType type) + { + switch (type) + { + case DicomRequestType_Echo: + return "Echo"; + break; + + case DicomRequestType_Find: + return "Find"; + break; + + case DicomRequestType_Get: + return "Get"; + break; + + case DicomRequestType_Move: + return "Move"; + break; + + case DicomRequestType_Store: + return "Store"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(TransferSyntax syntax) + { + switch (syntax) + { + case TransferSyntax_Deflated: + return "Deflated"; + + case TransferSyntax_Jpeg: + return "JPEG"; + + case TransferSyntax_Jpeg2000: + return "JPEG2000"; + + case TransferSyntax_JpegLossless: + return "JPEG Lossless"; + + case TransferSyntax_Jpip: + return "JPIP"; + + case TransferSyntax_Mpeg2: + return "MPEG2"; + + case TransferSyntax_Rle: + return "RLE"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + const char* EnumerationToString(DicomVersion version) + { + switch (version) + { + case DicomVersion_2008: + return "2008"; + break; + + case DicomVersion_2017c: + return "2017c"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + Encoding StringToEncoding(const char* encoding) { std::string s(encoding); @@ -1127,6 +1237,86 @@ } + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) + { + ModalityManufacturer result; + bool obsolete = false; + + if (manufacturer == "Generic") + { + return ModalityManufacturer_Generic; + } + else if (manufacturer == "GenericNoWildcardInDates") + { + return ModalityManufacturer_GenericNoWildcardInDates; + } + else if (manufacturer == "GenericNoUniversalWildcard") + { + return ModalityManufacturer_GenericNoUniversalWildcard; + } + else if (manufacturer == "ClearCanvas") + { + return ModalityManufacturer_ClearCanvas; + } + else if (manufacturer == "StoreScp") + { + return ModalityManufacturer_StoreScp; + } + else if (manufacturer == "Dcm4Chee") + { + return ModalityManufacturer_Dcm4Chee; + } + else if (manufacturer == "Vitrea") + { + return ModalityManufacturer_Vitrea; + } + else if (manufacturer == "AgfaImpax" || + manufacturer == "SyngoVia") + { + result = ModalityManufacturer_GenericNoWildcardInDates; + obsolete = true; + } + else if (manufacturer == "EFilm2" || + manufacturer == "MedInria") + { + result = ModalityManufacturer_Generic; + obsolete = true; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + if (obsolete) + { + LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since " + << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc " + << "releases, you should replace it by \"" + << EnumerationToString(result) + << "\" in your configuration file."; + } + + return result; + } + + + DicomVersion StringToDicomVersion(const std::string& version) + { + if (version == "2008") + { + return DicomVersion_2008; + } + else if (version == "2017c") + { + return DicomVersion_2017c; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + unsigned int GetBytesPerPixel(PixelFormat format) { switch (format)
--- a/Core/Enumerations.h Tue Aug 29 19:59:01 2017 +0200 +++ b/Core/Enumerations.h Tue Aug 29 21:17:35 2017 +0200 @@ -443,6 +443,81 @@ ValueRepresentation_NotSupported // Not supported by Orthanc, or tag not in dictionary }; + enum DicomReplaceMode + { + DicomReplaceMode_InsertIfAbsent, + DicomReplaceMode_ThrowIfAbsent, + DicomReplaceMode_IgnoreIfAbsent + }; + + enum DicomToJsonFormat + { + DicomToJsonFormat_Full, + DicomToJsonFormat_Short, + DicomToJsonFormat_Human + }; + + enum DicomToJsonFlags + { + DicomToJsonFlags_IncludeBinary = (1 << 0), + DicomToJsonFlags_IncludePrivateTags = (1 << 1), + DicomToJsonFlags_IncludeUnknownTags = (1 << 2), + DicomToJsonFlags_IncludePixelData = (1 << 3), + DicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), + DicomToJsonFlags_ConvertBinaryToNull = (1 << 5), + + // Some predefined combinations + DicomToJsonFlags_None = 0, + DicomToJsonFlags_Default = (DicomToJsonFlags_IncludeBinary | + DicomToJsonFlags_IncludePixelData | + DicomToJsonFlags_IncludePrivateTags | + DicomToJsonFlags_IncludeUnknownTags | + DicomToJsonFlags_ConvertBinaryToNull) + }; + + enum DicomFromJsonFlags + { + DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0), + DicomFromJsonFlags_GenerateIdentifiers = (1 << 1) + }; + + enum DicomVersion + { + DicomVersion_2008, + DicomVersion_2017c + }; + + enum ModalityManufacturer + { + ModalityManufacturer_Generic, + ModalityManufacturer_GenericNoWildcardInDates, + ModalityManufacturer_GenericNoUniversalWildcard, + ModalityManufacturer_StoreScp, + ModalityManufacturer_ClearCanvas, + ModalityManufacturer_Dcm4Chee, + ModalityManufacturer_Vitrea + }; + + enum DicomRequestType + { + DicomRequestType_Echo, + DicomRequestType_Find, + DicomRequestType_Get, + DicomRequestType_Move, + DicomRequestType_Store + }; + + enum TransferSyntax + { + TransferSyntax_Deflated, + TransferSyntax_Jpeg, + TransferSyntax_Jpeg2000, + TransferSyntax_JpegLossless, + TransferSyntax_Jpip, + TransferSyntax_Mpeg2, + TransferSyntax_Rle + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -513,6 +588,14 @@ const char* EnumerationToString(PixelFormat format); + const char* EnumerationToString(ModalityManufacturer manufacturer); + + const char* EnumerationToString(DicomRequestType type); + + const char* EnumerationToString(TransferSyntax syntax); + + const char* EnumerationToString(DicomVersion version); + Encoding StringToEncoding(const char* encoding); ResourceType StringToResourceType(const char* type); @@ -525,6 +608,10 @@ bool throwIfUnsupported); PhotometricInterpretation StringToPhotometricInterpretation(const char* value); + + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); + + DicomVersion StringToDicomVersion(const std::string& version); unsigned int GetBytesPerPixel(PixelFormat format);
--- a/Core/PrecompiledHeaders.h Tue Aug 29 19:59:01 2017 +0200 +++ b/Core/PrecompiledHeaders.h Tue Aug 29 21:17:35 2017 +0200 @@ -50,7 +50,7 @@ #include <json/value.h> #if ORTHANC_ENABLE_PUGIXML == 1 -#include <pugixml.hpp> +# include <pugixml.hpp> #endif #include "Enumerations.h" @@ -58,4 +58,50 @@ #include "OrthancException.h" #include "Toolbox.h" +#if ORTHANC_ENABLE_DCMTK == 1 +# include "DicomParsing/ParsedDicomFile.h" + +# include <dcmtk/dcmdata/dcchrstr.h> +# include <dcmtk/dcmdata/dcdeftag.h> +# include <dcmtk/dcmdata/dcdicent.h> +# include <dcmtk/dcmdata/dcdict.h> +# include <dcmtk/dcmdata/dcfilefo.h> +# include <dcmtk/dcmdata/dcistrmb.h> +# include <dcmtk/dcmdata/dcistrmf.h> +# include <dcmtk/dcmdata/dcmetinf.h> +# include <dcmtk/dcmdata/dcostrmb.h> +# include <dcmtk/dcmdata/dcpixel.h> +# include <dcmtk/dcmdata/dcpixseq.h> +# include <dcmtk/dcmdata/dcpxitem.h> +# include <dcmtk/dcmdata/dcuid.h> +# include <dcmtk/dcmdata/dcvrae.h> +# include <dcmtk/dcmdata/dcvras.h> +# include <dcmtk/dcmdata/dcvrcs.h> +# include <dcmtk/dcmdata/dcvrda.h> +# include <dcmtk/dcmdata/dcvrds.h> +# include <dcmtk/dcmdata/dcvrdt.h> +# include <dcmtk/dcmdata/dcvrfd.h> +# include <dcmtk/dcmdata/dcvrfl.h> +# include <dcmtk/dcmdata/dcvris.h> +# include <dcmtk/dcmdata/dcvrlo.h> +# include <dcmtk/dcmdata/dcvrlt.h> +# include <dcmtk/dcmdata/dcvrpn.h> +# include <dcmtk/dcmdata/dcvrsh.h> +# include <dcmtk/dcmdata/dcvrsl.h> +# include <dcmtk/dcmdata/dcvrss.h> +# include <dcmtk/dcmdata/dcvrst.h> +# include <dcmtk/dcmdata/dcvrtm.h> +# include <dcmtk/dcmdata/dcvrui.h> +# include <dcmtk/dcmdata/dcvrul.h> +# include <dcmtk/dcmdata/dcvrus.h> +# include <dcmtk/dcmdata/dcvrut.h> #endif + +#if ORTHANC_ENABLE_DCMTK_NETWORKING == 1 +# include "DicomParsing/DicomServer.h" + +# include <dcmtk/dcmnet/dcasccfg.h> +# include <dcmtk/dcmnet/diutil.h> +#endif + +#endif
--- a/OrthancServer/DefaultDicomImageDecoder.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/DefaultDicomImageDecoder.h Tue Aug 29 21:17:35 2017 +0200 @@ -34,8 +34,8 @@ #pragma once #include "IDicomImageDecoder.h" -#include "ParsedDicomFile.h" -#include "Internals/DicomImageDecoder.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" +#include "../Core/DicomParsing/Internals/DicomImageDecoder.h" namespace Orthanc {
--- a/OrthancServer/DicomDirWriter.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,510 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - - -/*** - - Validation: - - # sudo apt-get install dicom3tools - # dciodvfy DICOMDIR 2>&1 | less - # dcentvfy DICOMDIR 2>&1 | less - - http://www.dclunie.com/dicom3tools/dciodvfy.html - - DICOMDIR viewer working with Wine under Linux: - http://www.microdicom.com/ - - ***/ - - -#include "PrecompiledHeadersServer.h" -#include "DicomDirWriter.h" - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" - -#include "../Core/Logging.h" -#include "../Core/OrthancException.h" -#include "../Core/TemporaryFile.h" -#include "../Core/Toolbox.h" -#include "../Core/SystemToolbox.h" - -#include <dcmtk/dcmdata/dcdicdir.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcddirif.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcsequen.h> -#include <dcmtk/dcmdata/dcostrmf.h> -#include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ -#include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ - -#include <memory> - -namespace Orthanc -{ - class DicomDirWriter::PImpl - { - private: - std::string fileSetId_; - TemporaryFile file_; - std::auto_ptr<DcmDicomDir> dir_; - - typedef std::pair<ResourceType, std::string> IndexKey; - typedef std::map<IndexKey, DcmDirectoryRecord* > Index; - Index index_; - - - DcmDicomDir& GetDicomDir() - { - if (dir_.get() == NULL) - { - dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), - fileSetId_.c_str())); - //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8)); - } - - return *dir_; - } - - - DcmDirectoryRecord& GetRoot() - { - return GetDicomDir().getRootRecord(); - } - - - static bool GetUtf8TagValue(std::string& result, - DcmItem& source, - Encoding encoding, - const DcmTagKey& key) - { - DcmElement* element = NULL; - - if (source.findAndGetElement(key, element).good()) - { - char* s = NULL; - if (element->isLeaf() && - element->getString(s).good() && - s != NULL) - { - result = Toolbox::ConvertToUtf8(s, encoding); - return true; - } - } - - result.clear(); - return false; - } - - - static void SetTagValue(DcmDirectoryRecord& target, - const DcmTagKey& key, - const std::string& valueUtf8) - { - std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii); - - if (!target.putAndInsertString(key, s.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - - static bool CopyString(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - const DcmTagKey& key, - bool optional, - bool copyEmpty) - { - if (optional && - !source.tagExistsWithValue(key) && - !(copyEmpty && source.tagExists(key))) - { - return false; - } - - std::string value; - bool found = GetUtf8TagValue(value, source, encoding, key); - - SetTagValue(target, key, value); - return found; - } - - - static void CopyStringType1(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - const DcmTagKey& key) - { - CopyString(target, source, encoding, key, false, false); - } - - static void CopyStringType1C(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - const DcmTagKey& key) - { - CopyString(target, source, encoding, key, true, false); - } - - static void CopyStringType2(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - const DcmTagKey& key) - { - CopyString(target, source, encoding, key, false, true); - } - - - public: - PImpl() : fileSetId_("ORTHANC_MEDIA") - { - } - - void FillPatient(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding) - { - // cf. "DicomDirInterface::buildPatientRecord()" - - CopyStringType1C(record, dicom, encoding, DCM_PatientID); - CopyStringType2(record, dicom, encoding, DCM_PatientName); - } - - void FillStudy(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding) - { - // cf. "DicomDirInterface::buildStudyRecord()" - - std::string nowDate, nowTime; - SystemToolbox::GetNowDicom(nowDate, nowTime); - - std::string studyDate; - if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate)) - { - studyDate = nowDate; - } - - std::string studyTime; - if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime)) - { - studyTime = nowTime; - } - - /* copy attribute values from dataset to study record */ - SetTagValue(record, DCM_StudyDate, studyDate); - SetTagValue(record, DCM_StudyTime, studyTime); - CopyStringType2(record, dicom, encoding, DCM_StudyDescription); - CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID); - /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - CopyStringType1C(record, dicom, encoding, DCM_StudyID); - CopyStringType2(record, dicom, encoding, DCM_AccessionNumber); - } - - void FillSeries(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding) - { - // cf. "DicomDirInterface::buildSeriesRecord()" - - /* copy attribute values from dataset to series record */ - CopyStringType1(record, dicom, encoding, DCM_Modality); - CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID); - /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber); - } - - void FillInstance(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding, - DcmMetaInfo& metaInfo, - const char* path) - { - // cf. "DicomDirInterface::buildImageRecord()" - - /* copy attribute values from dataset to image record */ - CopyStringType1(record, dicom, encoding, DCM_InstanceNumber); - //CopyElementType1C(record, dicom, encoding, DCM_ImageType); - - // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); - - std::string sopClassUid, sopInstanceUid, transferSyntaxUid; - if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) || - !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) || - !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - SetTagValue(record, DCM_ReferencedFileID, path); - SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid); - SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid); - SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid); - } - - - - bool CreateResource(DcmDirectoryRecord*& target, - ResourceType level, - ParsedDicomFile& dicom, - const char* filename, - const char* path) - { - DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - Encoding encoding = dicom.GetEncoding(); - - bool found; - std::string id; - E_DirRecType type; - - switch (level) - { - case ResourceType_Patient: - found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID); - type = ERT_Patient; - break; - - case ResourceType_Study: - found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID); - type = ERT_Study; - break; - - case ResourceType_Series: - found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID); - type = ERT_Series; - break; - - case ResourceType_Instance: - found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID); - type = ERT_Image; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (!found) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - IndexKey key = std::make_pair(level, std::string(id.c_str())); - Index::iterator it = index_.find(key); - - if (it != index_.end()) - { - target = it->second; - return false; // Already existing - } - - std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename)); - - switch (level) - { - case ResourceType_Patient: - FillPatient(*record, dataset, encoding); - break; - - case ResourceType_Study: - FillStudy(*record, dataset, encoding); - break; - - case ResourceType_Series: - FillSeries(*record, dataset, encoding); - break; - - case ResourceType_Instance: - FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet); - - target = record.get(); - GetRoot().insertSub(record.release()); - index_[key] = target; - - return true; // Newly created - } - - void Read(std::string& s) - { - if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, - EET_UndefinedLength /*encodingType*/, - EGL_withoutGL /*groupLength*/).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - file_.Read(s); - } - - void SetFileSetId(const std::string& id) - { - dir_.reset(NULL); - fileSetId_ = id; - } - }; - - - DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) - { - } - - DicomDirWriter::~DicomDirWriter() - { - if (pimpl_) - { - delete pimpl_; - } - } - - void DicomDirWriter::SetFileSetId(const std::string& id) - { - pimpl_->SetFileSetId(id); - } - - void DicomDirWriter::Add(const std::string& directory, - const std::string& filename, - ParsedDicomFile& dicom) - { - std::string path; - if (directory.empty()) - { - path = filename; - } - else - { - if (directory[directory.length() - 1] == '/' || - directory[directory.length() - 1] == '\\') - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - path = directory + '\\' + filename; - } - - DcmDirectoryRecord* instance; - bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str()); - if (isNewInstance) - { - DcmDirectoryRecord* series; - bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL); - series->insertSub(instance); - - if (isNewSeries) - { - DcmDirectoryRecord* study; - bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL); - study->insertSub(series); - - if (isNewStudy) - { - DcmDirectoryRecord* patient; - pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL); - patient->insertSub(study); - } - } - } - } - - void DicomDirWriter::Encode(std::string& target) - { - pimpl_->Read(target); - } -}
--- a/OrthancServer/DicomDirWriter.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ParsedDicomFile.h" - -#include <boost/noncopyable.hpp> - -namespace Orthanc -{ - class DicomDirWriter : public boost::noncopyable - { - private: - class PImpl; - PImpl* pimpl_; - - public: - DicomDirWriter(); - - ~DicomDirWriter(); - - void SetFileSetId(const std::string& id); - - void Add(const std::string& directory, - const std::string& filename, - ParsedDicomFile& dicom); - - void Encode(std::string& target); - }; - -}
--- a/OrthancServer/DicomInstanceToStore.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/DicomInstanceToStore.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -34,7 +34,7 @@ #include "PrecompiledHeadersServer.h" #include "DicomInstanceToStore.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/Logging.h" #include <dcmtk/dcmdata/dcfilefo.h>
--- a/OrthancServer/DicomInstanceToStore.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/DicomInstanceToStore.h Tue Aug 29 21:17:35 2017 +0200 @@ -33,7 +33,7 @@ #pragma once -#include "ParsedDicomFile.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "ServerIndex.h" #include "../Core/OrthancException.h" #include "../Core/RestApi/RestApiCall.h"
--- a/OrthancServer/DicomModification.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1132 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" -#include "DicomModification.h" - -#include "../Core/Logging.h" -#include "../Core/OrthancException.h" -#include "FromDcmtkBridge.h" - -#include <memory> // For std::auto_ptr - - -static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = - "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; - -static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = - "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile"; - -namespace Orthanc -{ - bool DicomModification::CancelReplacement(const DicomTag& tag) - { - Replacements::iterator it = replacements_.find(tag); - - if (it != replacements_.end()) - { - delete it->second; - replacements_.erase(it); - return true; - } - else - { - return false; - } - } - - - void DicomModification::ReplaceInternal(const DicomTag& tag, - const Json::Value& value) - { - Replacements::iterator it = replacements_.find(tag); - - if (it != replacements_.end()) - { - delete it->second; - it->second = NULL; // In the case of an exception during the clone - it->second = new Json::Value(value); // Clone - } - else - { - replacements_[tag] = new Json::Value(value); // Clone - } - } - - - void DicomModification::ClearReplacements() - { - for (Replacements::iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - delete it->second; - } - - replacements_.clear(); - } - - - void DicomModification::MarkNotOrthancAnonymization() - { - Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); - - if (it != replacements_.end() && - (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || - it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) - { - delete it->second; - replacements_.erase(it); - } - } - - - void DicomModification::MapDicomIdentifier(ParsedDicomFile& dicom, - ResourceType level) - { - std::auto_ptr<DicomTag> tag; - - switch (level) - { - case ResourceType_Study: - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - break; - - case ResourceType_Series: - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - break; - - case ResourceType_Instance: - tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string original; - if (!dicom.GetTagValue(original, *tag)) - { - original = ""; - } - - std::string mapped; - - UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); - if (previous == uidMap_.end()) - { - mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); - uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); - } - else - { - mapped = previous->second; - } - - dicom.Replace(*tag, mapped, false /* don't try and decode data URI scheme for UIDs */, DicomReplaceMode_InsertIfAbsent); - } - - DicomModification::DicomModification() : - removePrivateTags_(false), - level_(ResourceType_Instance), - allowManualIdentifiers_(true), - keepStudyInstanceUid_(false), - keepSeriesInstanceUid_(false) - { - } - - DicomModification::~DicomModification() - { - ClearReplacements(); - } - - void DicomModification::Keep(const DicomTag& tag) - { - bool wasRemoved = IsRemoved(tag); - bool wasCleared = IsCleared(tag); - - removals_.erase(tag); - clearings_.erase(tag); - - bool wasReplaced = CancelReplacement(tag); - - if (tag == DICOM_TAG_STUDY_INSTANCE_UID) - { - keepStudyInstanceUid_ = true; - } - else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - keepSeriesInstanceUid_ = true; - } - else if (tag.IsPrivate()) - { - privateTagsToKeep_.insert(tag); - } - else if (!wasRemoved && - !wasReplaced && - !wasCleared) - { - LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format(); - } - - MarkNotOrthancAnonymization(); - } - - void DicomModification::Remove(const DicomTag& tag) - { - removals_.insert(tag); - clearings_.erase(tag); - CancelReplacement(tag); - privateTagsToKeep_.erase(tag); - - MarkNotOrthancAnonymization(); - } - - void DicomModification::Clear(const DicomTag& tag) - { - removals_.erase(tag); - clearings_.insert(tag); - CancelReplacement(tag); - privateTagsToKeep_.erase(tag); - - MarkNotOrthancAnonymization(); - } - - bool DicomModification::IsRemoved(const DicomTag& tag) const - { - return removals_.find(tag) != removals_.end(); - } - - bool DicomModification::IsCleared(const DicomTag& tag) const - { - return clearings_.find(tag) != clearings_.end(); - } - - void DicomModification::Replace(const DicomTag& tag, - const Json::Value& value, - bool safeForAnonymization) - { - clearings_.erase(tag); - removals_.erase(tag); - privateTagsToKeep_.erase(tag); - ReplaceInternal(tag, value); - - if (!safeForAnonymization) - { - MarkNotOrthancAnonymization(); - } - } - - - bool DicomModification::IsReplaced(const DicomTag& tag) const - { - return replacements_.find(tag) != replacements_.end(); - } - - const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const - { - Replacements::const_iterator it = replacements_.find(tag); - - if (it == replacements_.end()) - { - throw OrthancException(ErrorCode_InexistentItem); - } - else - { - return *it->second; - } - } - - - std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const - { - const Json::Value& json = GetReplacement(tag); - - if (json.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - else - { - return json.asString(); - } - } - - - void DicomModification::SetRemovePrivateTags(bool removed) - { - removePrivateTags_ = removed; - - if (!removed) - { - MarkNotOrthancAnonymization(); - } - } - - void DicomModification::SetLevel(ResourceType level) - { - uidMap_.clear(); - level_ = level; - - if (level != ResourceType_Patient) - { - MarkNotOrthancAnonymization(); - } - } - - - void DicomModification::SetupAnonymization2008() - { - // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles - // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf - - removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID - //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() - removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number - removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name - removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name - removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID - removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) - //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) - removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids - removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number - removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() - //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() - removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID - removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals_.insert(DicomTag(0x0040, 0xa124)); // UID - removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID - removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID - removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID - - // Some more removals (from the experience of DICOM files at the CHU of Liege) - removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers - removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - - // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008); - } - - -#if 0 - /** - * This is a manual implementation by Alain Mazy. Only kept for reference. - * https://bitbucket.org/sjodogne/orthanc/commits/c6defdc4c611fca2ab528ba2c6937a742e0329a8?at=issue-46-anonymization - **/ - - void DicomModification::SetupAnonymization2011() - { - // This is Table E.1-1 from PS 3.15-2011 - DICOM Part 15: Security and System Management Profiles - // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2011/11_15pu.pdf - - removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID - removals_.insert(DicomTag(0x0000, 0x1001)); // Requested SOP Instance UID - removals_.insert(DicomTag(0x0002, 0x0003)); // Media Storage SOP Instance UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances - removals_.insert(DicomTag(0x0004, 0x1511)); // Referenced SOP Instance UID in File - removals_.insert(DicomTag(0x0008, 0x0010)); // Irradiation Event UID - removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID - //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() - clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date - clearings_.insert(DicomTag(0x0008, 0x0021)); // Series Date - clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time - clearings_.insert(DicomTag(0x0008, 0x0031)); // Series Time - removals_.insert(DicomTag(0x0008, 0x0022)); // Acquisition Date - removals_.insert(DicomTag(0x0008, 0x0023)); // Content Date - removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date - removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date - removals_.insert(DicomTag(0x0008, 0x002a)); // Acquisition DateTime - removals_.insert(DicomTag(0x0008, 0x0032)); // Acquisition Time - removals_.insert(DicomTag(0x0008, 0x0033)); // Content Time - removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time - removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time - removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number - removals_.insert(DicomTag(0x0008, 0x0058)); // Failed SOP Instance UID List - removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name - removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals_.insert(DicomTag(0x0008, 0x0082)); // Institution Code Sequence - removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician's Identification Sequence - removals_.insert(DicomTag(0x0008, 0x010d)); // Context Group Extension Creator UID - removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC - removals_.insert(DicomTag(0x0008, 0x0300)); // Current Patient Location - removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name - removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physicians Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals_.insert(DicomTag(0x0008, 0x1062)); // Physician Reading Study Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - removals_.insert(DicomTag(0x0008, 0x1072)); // Operators' Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence - removals_.insert(DicomTag(0x0008, 0x1110)); // Referenced Study Sequence - removals_.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence - removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence - removals_.insert(DicomTag(0x0008, 0x1140)); // Referenced Image Sequence - removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID - removals_.insert(DicomTag(0x0008, 0x1195)); // Transaction UID - removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - removals_.insert(DicomTag(0x0008, 0x2112)); // Source Image Sequence - removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments - removals_.insert(DicomTag(0x0008, 0x9123)); // Creator Version UID - //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) - //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) - removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence - removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence - removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence - removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids - removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence - removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name - removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification - removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name - removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank - removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service - removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies - removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence - removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence - removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers - removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status - removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status - removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date - removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference - removals_.insert(DicomTag(0x0010, 0x2203)); // Patient's Sex Neutered - removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person - removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization - removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals_.insert(DicomTag(0x0018, 0x0010)); // Contrast Bolus Agent - removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number - removals_.insert(DicomTag(0x0018, 0x1002)); // Device UID - removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID - removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID - removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID - removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID - removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - removals_.insert(DicomTag(0x0018, 0x1400)); // Acquisition Device Processing Description - removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments - removals_.insert(DicomTag(0x0018, 0x700a)); // Detector ID - removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description - removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description - //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() - //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() - removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID - removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID - removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer - removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description - removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments - removals_.insert(DicomTag(0x0020, 0x9161)); // Concatenation UID - removals_.insert(DicomTag(0x0020, 0x9164)); // Dimension Organization UID - //removals_.insert(DicomTag(0x0028, 0x1199)); // Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances - //removals_.insert(DicomTag(0x0028, 0x1214)); // Large Palette Color Lookup Table UID => TODO: replace with a non-zero length UID that is internally consistent within a set of Instances - removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments - removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer - removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location - removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title - removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study - removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service - removals_.insert(DicomTag(0x0032, 0x1060)); // Requesting Procedure Description - removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent - removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments - removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID - removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID - removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence - removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date - removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time - removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description - removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs - removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID - removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID - removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description - removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence - removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State - removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments - removals_.insert(DicomTag(0x0038, 0x1234)); // Referenced Patient Alias Sequence - removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title - removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date - removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time - removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date - removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time - removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name - removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description - removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence - removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name - removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location - removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication - removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title - removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name - removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location - removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date - removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time - removals_.insert(DicomTag(0x0040, 0x0248)); // Performed Station Name Code Sequence - removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID - removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description - removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on Performed Procedure Step - removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence - removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID - removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results - removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipient of Results Identification Sequence - removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements - removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location - removals_.insert(DicomTag(0x0040, 0x1101)); // Person Identification Code Sequence - removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address - removals_.insert(DicomTag(0x0040, 0x1103)); // Person Telephone Numbers - removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments - removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for Imaging Service Request - removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By - removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location - removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number - removals_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number of Imaging Service Request - removals_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number of Imaging Service Request - removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments - removals_.insert(DicomTag(0x0040, 0x4023)); // Referenced General Purpose Scheduled Procedure Step Transaction UID - removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence - removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence - removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence - removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence - removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence - removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization - removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name - removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization - removals_.insert(DicomTag(0x0040, 0xa073)); // Verifying Observer Sequence - removals_.insert(DicomTag(0x0040, 0xa075)); // Verifying Observer Name - removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence - removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence - removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence - removals_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence - removals_.insert(DicomTag(0x0040, 0xa123)); // Person Name - removals_.insert(DicomTag(0x0040, 0xa124)); // UID - removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description - removals_.insert(DicomTag(0x0040, 0xdb0c)); // Template Extension Organization UID - removals_.insert(DicomTag(0x0040, 0xdb0d)); // Template Extension Creator UID - removals_.insert(DicomTag(0x0070, 0x0001)); // Graphic Annotation Sequence - removals_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name - removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence - removals_.insert(DicomTag(0x0070, 0x031a)); // Fiducial UID - removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID - removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence - removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title - removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject - removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author - removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Key Words - removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID - removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence - removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence - removals_.insert(DicomTag(0x0400, 0x0404)); // MAC - removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence - removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence - removals_.insert(DicomTag(0x2030, 0x0020)); // Text String - removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID - removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID - removals_.insert(DicomTag(0x300a, 0x0013)); // Dose Reference UID - removals_.insert(DicomTag(0x300e, 0x0008)); // Reviewer Name - removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary - removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments - removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer - removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder - removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber - removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text - removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author - removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence - removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation - removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description - removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence - removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name - removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address - removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer - removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions - removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments - removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signature Sequence - removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding - //removals_.insert(DicomTag(0x60xx, 0x4000)); // Overlay Comments => TODO - //removals_.insert(DicomTag(0x60xx, 0x3000)); // Overlay Data => TODO - - // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2011); - } -#endif - - - - void DicomModification::SetupAnonymization2017c() - { - /** - * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security - * and System Management Profiles), "basic profile" column. It was - * generated automatically with the - * "../Resources/GenerateAnonymizationProfile.py" script. - * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf - **/ - - // TODO: (50xx,xxxx) with rule X // Curve Data - // TODO: (60xx,3000) with rule X // Overlay Data - // TODO: (60xx,4000) with rule X // Overlay Comments - // Tag (0x0008, 0x0018) is set in Apply() // SOP Instance UID - // Tag (0x0010, 0x0010) is set below (*) // Patient's Name - // Tag (0x0010, 0x0020) is set below (*) // Patient ID - // Tag (0x0020, 0x000d) is set in Apply() // Study Instance UID - // Tag (0x0020, 0x000e) is set in Apply() // Series Instance UID - clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date - clearings_.insert(DicomTag(0x0008, 0x0023)); /* Z/D */ // Content Date - clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time - clearings_.insert(DicomTag(0x0008, 0x0033)); /* Z/D */ // Content Time - clearings_.insert(DicomTag(0x0008, 0x0050)); // Accession Number - clearings_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - clearings_.insert(DicomTag(0x0008, 0x009c)); // Consulting Physician's Name - clearings_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - clearings_.insert(DicomTag(0x0018, 0x0010)); /* Z/D */ // Contrast Bolus Agent - clearings_.insert(DicomTag(0x0020, 0x0010)); // Study ID - clearings_.insert(DicomTag(0x0040, 0x1101)); /* D */ // Person Identification Code Sequence - clearings_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number / Imaging Service Request - clearings_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number / Imaging Service Request - clearings_.insert(DicomTag(0x0040, 0xa073)); /* D */ // Verifying Observer Sequence - clearings_.insert(DicomTag(0x0040, 0xa075)); /* D */ // Verifying Observer Name - clearings_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence - clearings_.insert(DicomTag(0x0040, 0xa123)); /* D */ // Person Name - clearings_.insert(DicomTag(0x0070, 0x0001)); /* D */ // Graphic Annotation Sequence - clearings_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name - removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID - removals_.insert(DicomTag(0x0000, 0x1001)); /* TODO UID */ // Requested SOP Instance UID - removals_.insert(DicomTag(0x0002, 0x0003)); /* TODO UID */ // Media Storage SOP Instance UID - removals_.insert(DicomTag(0x0004, 0x1511)); /* TODO UID */ // Referenced SOP Instance UID in File - removals_.insert(DicomTag(0x0008, 0x0014)); /* TODO UID */ // Instance Creator UID - removals_.insert(DicomTag(0x0008, 0x0015)); // Instance Coercion DateTime - removals_.insert(DicomTag(0x0008, 0x0021)); /* X/D */ // Series Date - removals_.insert(DicomTag(0x0008, 0x0022)); /* X/Z */ // Acquisition Date - removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date - removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date - removals_.insert(DicomTag(0x0008, 0x002a)); /* X/D */ // Acquisition DateTime - removals_.insert(DicomTag(0x0008, 0x0031)); /* X/D */ // Series Time - removals_.insert(DicomTag(0x0008, 0x0032)); /* X/Z */ // Acquisition Time - removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time - removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time - removals_.insert(DicomTag(0x0008, 0x0058)); /* TODO UID */ // Failed SOP Instance UID List - removals_.insert(DicomTag(0x0008, 0x0080)); /* X/Z/D */ // Institution Name - removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals_.insert(DicomTag(0x0008, 0x0082)); /* X/Z/D */ // Institution Code Sequence - removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician Identification Sequence - removals_.insert(DicomTag(0x0008, 0x009d)); // Consulting Physician Identification Sequence - removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC - removals_.insert(DicomTag(0x0008, 0x1010)); /* X/Z/D */ // Station Name - removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals_.insert(DicomTag(0x0008, 0x1062)); // Physician(s) Reading Study Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1070)); /* X/Z/D */ // Operators' Name - removals_.insert(DicomTag(0x0008, 0x1072)); /* X/D */ // Operators' Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence - removals_.insert(DicomTag(0x0008, 0x1110)); /* X/Z */ // Referenced Study Sequence - removals_.insert(DicomTag(0x0008, 0x1111)); /* X/Z/D */ // Referenced Performed Procedure Step Sequence - removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence - removals_.insert(DicomTag(0x0008, 0x1140)); /* X/Z/U* */ // Referenced Image Sequence - removals_.insert(DicomTag(0x0008, 0x1155)); /* TODO UID */ // Referenced SOP Instance UID - removals_.insert(DicomTag(0x0008, 0x1195)); /* TODO UID */ // Transaction UID - removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - removals_.insert(DicomTag(0x0008, 0x2112)); /* X/Z/U* */ // Source Image Sequence - removals_.insert(DicomTag(0x0008, 0x3010)); /* TODO UID */ // Irradiation Event UID - removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments - removals_.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID - removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence - removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence - removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence - removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient IDs - removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence - removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name - removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals_.insert(DicomTag(0x0010, 0x1040)); // Patient Address - removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification - removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name - removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank - removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service - removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals_.insert(DicomTag(0x0010, 0x1100)); // Referenced Patient Photo Sequence - removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies - removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence - removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence - removals_.insert(DicomTag(0x0010, 0x2154)); // Patient's Telephone Numbers - removals_.insert(DicomTag(0x0010, 0x2155)); // Patient's Telecom Information - removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status - removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status - removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date - removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference - removals_.insert(DicomTag(0x0010, 0x2203)); /* X/Z */ // Patient Sex Neutered - removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person - removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization - removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals_.insert(DicomTag(0x0018, 0x1000)); /* X/Z/D */ // Device Serial Number - removals_.insert(DicomTag(0x0018, 0x1002)); /* TODO UID */ // Device UID - removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID - removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID - removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID - removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID - removals_.insert(DicomTag(0x0018, 0x1030)); /* X/D */ // Protocol Name - removals_.insert(DicomTag(0x0018, 0x1400)); /* X/D */ // Acquisition Device Processing Description - removals_.insert(DicomTag(0x0018, 0x2042)); /* TODO UID */ // Target UID - removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments - removals_.insert(DicomTag(0x0018, 0x700a)); /* X/D */ // Detector ID - removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description - removals_.insert(DicomTag(0x0018, 0x9516)); /* X/D */ // Start Acquisition DateTime - removals_.insert(DicomTag(0x0018, 0x9517)); /* X/D */ // End Acquisition DateTime - removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description - removals_.insert(DicomTag(0x0020, 0x0052)); /* TODO UID */ // Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x0200)); /* TODO UID */ // Synchronization Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID - removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer - removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description - removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments - removals_.insert(DicomTag(0x0020, 0x9161)); /* TODO UID */ // Concatenation UID - removals_.insert(DicomTag(0x0020, 0x9164)); /* TODO UID */ // Dimension Organization UID - removals_.insert(DicomTag(0x0028, 0x1199)); /* TODO UID */ // Palette Color Lookup Table UID - removals_.insert(DicomTag(0x0028, 0x1214)); /* TODO UID */ // Large Palette Color Lookup Table UID - removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments - removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer - removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location - removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title - removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study - removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service - removals_.insert(DicomTag(0x0032, 0x1060)); /* X/Z */ // Requested Procedure Description - removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent - removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments - removals_.insert(DicomTag(0x0038, 0x0004)); // Referenced Patient Alias Sequence - removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID - removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID - removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence - removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date - removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time - removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description - removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs - removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID - removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID - removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description - removals_.insert(DicomTag(0x0038, 0x0300)); // Current Patient Location - removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence - removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State - removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments - removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title - removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date - removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time - removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date - removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time - removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name - removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description - removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence - removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name - removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location - removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication - removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title - removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name - removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location - removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date - removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time - removals_.insert(DicomTag(0x0040, 0x0250)); // Performed Procedure Step End Date - removals_.insert(DicomTag(0x0040, 0x0251)); // Performed Procedure Step End Time - removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID - removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description - removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step - removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence - removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID - removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements - removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location - removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results - removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipients of Results Identification Sequence - removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address - removals_.insert(DicomTag(0x0040, 0x1103)); // Person's Telephone Numbers - removals_.insert(DicomTag(0x0040, 0x1104)); // Person's Telecom Information - removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments - removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for the Imaging Service Request - removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By - removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location - removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number - removals_.insert(DicomTag(0x0040, 0x2011)); // Order Callback Telecom Information - removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments - removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description - removals_.insert(DicomTag(0x0040, 0x4005)); // Scheduled Procedure Step Start DateTime - removals_.insert(DicomTag(0x0040, 0x4010)); // Scheduled Procedure Step Modification DateTime - removals_.insert(DicomTag(0x0040, 0x4011)); // Expected Completion DateTime - removals_.insert(DicomTag(0x0040, 0x4023)); /* TODO UID */ // Referenced General Purpose Scheduled Procedure Step Transaction UID - removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence - removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence - removals_.insert(DicomTag(0x0040, 0x4028)); // Performed Station Name Code Sequence - removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence - removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence - removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence - removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization - removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name - removals_.insert(DicomTag(0x0040, 0x4050)); // Performed Procedure Step Start DateTime - removals_.insert(DicomTag(0x0040, 0x4051)); // Performed Procedure Step End DateTime - removals_.insert(DicomTag(0x0040, 0x4052)); // Procedure Step Cancellation DateTime - removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization - removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence - removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence - removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence - removals_.insert(DicomTag(0x0040, 0xa124)); /* TODO UID */ // UID - removals_.insert(DicomTag(0x0040, 0xa171)); /* TODO UID */ // Observation UID - removals_.insert(DicomTag(0x0040, 0xa172)); /* TODO UID */ // Referenced Observation UID (Trial) - removals_.insert(DicomTag(0x0040, 0xa192)); // Observation Date (Trial) - removals_.insert(DicomTag(0x0040, 0xa193)); // Observation Time (Trial) - removals_.insert(DicomTag(0x0040, 0xa307)); // Current Observer (Trial) - removals_.insert(DicomTag(0x0040, 0xa352)); // Verbal Source (Trial) - removals_.insert(DicomTag(0x0040, 0xa353)); // Address (Trial) - removals_.insert(DicomTag(0x0040, 0xa354)); // Telephone Number (Trial) - removals_.insert(DicomTag(0x0040, 0xa358)); // Verbal Source Identifier Code Sequence (Trial) - removals_.insert(DicomTag(0x0040, 0xa402)); /* TODO UID */ // Observation Subject UID (Trial) - removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals_.insert(DicomTag(0x0040, 0xdb0c)); /* TODO UID */ // Template Extension Organization UID - removals_.insert(DicomTag(0x0040, 0xdb0d)); /* TODO UID */ // Template Extension Creator UID - removals_.insert(DicomTag(0x0062, 0x0021)); /* TODO UID */ // Tracking UID - removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence - removals_.insert(DicomTag(0x0070, 0x031a)); /* TODO UID */ // Fiducial UID - removals_.insert(DicomTag(0x0070, 0x1101)); /* TODO UID */ // Presentation Display Collection UID - removals_.insert(DicomTag(0x0070, 0x1102)); /* TODO UID */ // Presentation Sequence Collection UID - removals_.insert(DicomTag(0x0088, 0x0140)); /* TODO UID */ // Storage Media File-set UID - removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence(see Note 12) - removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title - removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject - removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author - removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Keywords - removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID - removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence - removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence - removals_.insert(DicomTag(0x0400, 0x0404)); // MAC - removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence - removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence - removals_.insert(DicomTag(0x2030, 0x0020)); // Text String - removals_.insert(DicomTag(0x3006, 0x0024)); /* TODO UID */ // Referenced Frame of Reference UID - removals_.insert(DicomTag(0x3006, 0x00c2)); /* TODO UID */ // Related Frame of Reference UID - removals_.insert(DicomTag(0x3008, 0x0105)); // Source Serial Number - removals_.insert(DicomTag(0x300a, 0x0013)); /* TODO UID */ // Dose Reference UID - removals_.insert(DicomTag(0x300c, 0x0113)); // Reason for Omission Description - removals_.insert(DicomTag(0x300e, 0x0008)); /* X/Z */ // Reviewer Name - removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary - removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments - removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer - removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder - removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber - removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text - removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author - removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence - removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation - removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description - removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence - removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name - removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address - removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer - removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions - removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments - removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signatures Sequence - removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding - - // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c); - } - - - void DicomModification::SetupAnonymization(DicomVersion version) - { - removals_.clear(); - clearings_.clear(); - ClearReplacements(); - removePrivateTags_ = true; - level_ = ResourceType_Patient; - uidMap_.clear(); - privateTagsToKeep_.clear(); - - switch (version) - { - case DicomVersion_2008: - SetupAnonymization2008(); - break; - - case DicomVersion_2017c: - SetupAnonymization2017c(); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Set the PatientIdentityRemoved tag - ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); - - // (*) Choose a random patient name and ID - std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); - ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); - ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); - } - - void DicomModification::Apply(ParsedDicomFile& toModify) - { - // Check the request - assert(ResourceType_Patient + 1 == ResourceType_Study && - ResourceType_Study + 1 == ResourceType_Series && - ResourceType_Series + 1 == ResourceType_Instance); - - if (IsRemoved(DICOM_TAG_PATIENT_ID) || - IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || - IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || - IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest); - } - - - // Sanity checks at the patient level - if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying a patient, her PatientID is required to be modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a patient, the StudyInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a patient, the SeriesInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a patient, the SopInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - - // Sanity checks at the study level - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying a study, the parent PatientID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a study, the SeriesInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a study, the SopInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - - // Sanity checks at the series level - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying a series, the parent PatientID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a series, the parent StudyInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying a series, the SopInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - } - - - // Sanity checks at the instance level - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - LOG(ERROR) << "When modifying an instance, the parent PatientID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - LOG(ERROR) << "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified"; - throw OrthancException(ErrorCode_BadRequest); - } - - - // (1) Remove the private tags, if need be - if (removePrivateTags_) - { - toModify.RemovePrivateTags(privateTagsToKeep_); - } - - // (2) Clear the tags specified by the user - for (SetOfTags::const_iterator it = clearings_.begin(); - it != clearings_.end(); ++it) - { - toModify.Clear(*it, true /* only clear if the tag exists in the original file */); - } - - // (3) Remove the tags specified by the user - for (SetOfTags::const_iterator it = removals_.begin(); - it != removals_.end(); ++it) - { - toModify.Remove(*it); - } - - // (4) Replace the tags - for (Replacements::const_iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent); - } - - // (5) Update the DICOM identifiers - if (level_ <= ResourceType_Study && - !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - if (keepStudyInstanceUid_) - { - LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!"; - } - else - { - MapDicomIdentifier(toModify, ResourceType_Study); - } - } - - if (level_ <= ResourceType_Series && - !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - if (keepSeriesInstanceUid_) - { - LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!"; - } - else - { - MapDicomIdentifier(toModify, ResourceType_Series); - } - } - - if (level_ <= ResourceType_Instance && // Always true - !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - MapDicomIdentifier(toModify, ResourceType_Instance); - } - } -}
--- a/OrthancServer/DicomModification.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ParsedDicomFile.h" - -namespace Orthanc -{ - class DicomModification : public boost::noncopyable - { - /** - * Process: - * (1) Remove private tags - * (2) Remove tags specified by the user - * (3) Replace tags - **/ - - private: - typedef std::set<DicomTag> SetOfTags; - typedef std::map<DicomTag, Json::Value*> Replacements; - typedef std::map< std::pair<ResourceType, std::string>, std::string> UidMap; - - SetOfTags removals_; - SetOfTags clearings_; - Replacements replacements_; - bool removePrivateTags_; - ResourceType level_; - UidMap uidMap_; - SetOfTags privateTagsToKeep_; - bool allowManualIdentifiers_; - bool keepStudyInstanceUid_; - bool keepSeriesInstanceUid_; - - void MapDicomIdentifier(ParsedDicomFile& dicom, - ResourceType level); - - void MarkNotOrthancAnonymization(); - - void ClearReplacements(); - - bool CancelReplacement(const DicomTag& tag); - - void ReplaceInternal(const DicomTag& tag, - const Json::Value& value); - - void SetupAnonymization2008(); - - void SetupAnonymization2017c(); - - public: - DicomModification(); - - ~DicomModification(); - - void Keep(const DicomTag& tag); - - void Remove(const DicomTag& tag); - - // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) - void Clear(const DicomTag& tag); - - bool IsRemoved(const DicomTag& tag) const; - - bool IsCleared(const DicomTag& tag) const; - - // "safeForAnonymization" tells Orthanc that this replacement does - // not break the anonymization process it implements (for internal use only) - void Replace(const DicomTag& tag, - const Json::Value& value, // Encoded using UTF-8 - bool safeForAnonymization); - - bool IsReplaced(const DicomTag& tag) const; - - const Json::Value& GetReplacement(const DicomTag& tag) const; - - std::string GetReplacementAsString(const DicomTag& tag) const; - - void SetRemovePrivateTags(bool removed); - - bool ArePrivateTagsRemoved() const - { - return removePrivateTags_; - } - - void SetLevel(ResourceType level); - - ResourceType GetLevel() const - { - return level_; - } - - void SetupAnonymization(DicomVersion version); - - void Apply(ParsedDicomFile& toModify); - - void SetAllowManualIdentifiers(bool check) - { - allowManualIdentifiers_ = check; - } - - bool AreAllowManualIdentifiers() const - { - return allowManualIdentifiers_; - } - }; -}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomFindAnswers.h" - -#include "../OrthancInitialization.h" -#include "../FromDcmtkBridge.h" -#include "../../Core/OrthancException.h" - -#include <memory> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <boost/noncopyable.hpp> - - -namespace Orthanc -{ - void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer) - { - std::auto_ptr<ParsedDicomFile> protection(answer); - - if (isWorklist_) - { - // These lines are necessary when serving worklists, otherwise - // Orthanc does not behave as "wlmscpfs" - protection->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID); - protection->Remove(DICOM_TAG_SOP_INSTANCE_UID); - } - - protection->ChangeEncoding(encoding_); - - answers_.push_back(protection.release()); - } - - - DicomFindAnswers::DicomFindAnswers(bool isWorklist) : - encoding_(GetDefaultDicomEncoding()), - isWorklist_(isWorklist), - complete_(true) - { - } - - - void DicomFindAnswers::SetEncoding(Encoding encoding) - { - for (size_t i = 0; i < answers_.size(); i++) - { - assert(answers_[i] != NULL); - answers_[i]->ChangeEncoding(encoding); - } - - encoding_ = encoding; - } - - - void DicomFindAnswers::SetWorklist(bool isWorklist) - { - if (answers_.empty()) - { - isWorklist_ = isWorklist; - } - else - { - // This set of answers is not empty anymore, cannot change its type - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - void DicomFindAnswers::Clear() - { - for (size_t i = 0; i < answers_.size(); i++) - { - assert(answers_[i] != NULL); - delete answers_[i]; - } - - answers_.clear(); - } - - - void DicomFindAnswers::Reserve(size_t size) - { - if (size > answers_.size()) - { - answers_.reserve(size); - } - } - - - void DicomFindAnswers::Add(const DicomMap& map) - { - AddAnswerInternal(new ParsedDicomFile(map, encoding_)); - } - - - void DicomFindAnswers::Add(ParsedDicomFile& dicom) - { - AddAnswerInternal(dicom.Clone()); - } - - void DicomFindAnswers::Add(const void* dicom, - size_t size) - { - AddAnswerInternal(new ParsedDicomFile(dicom, size)); - } - - - ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const - { - if (index < answers_.size()) - { - return *answers_[index]; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const - { - return new DcmDataset(*GetAnswer(index).GetDcmtkObject().getDataset()); - } - - - void DicomFindAnswers::ToJson(Json::Value& target, - size_t index, - bool simplify) const - { - DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full); - GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0); - } - - - void DicomFindAnswers::ToJson(Json::Value& target, - bool simplify) const - { - target = Json::arrayValue; - - for (size_t i = 0; i < GetSize(); i++) - { - Json::Value answer; - ToJson(answer, i, simplify); - target.append(answer); - } - } -}
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ParsedDicomFile.h" - -namespace Orthanc -{ - class DicomFindAnswers : public boost::noncopyable - { - private: - Encoding encoding_; - bool isWorklist_; - std::vector<ParsedDicomFile*> answers_; - bool complete_; - - void AddAnswerInternal(ParsedDicomFile* answer); - - public: - DicomFindAnswers(bool isWorklist); - - ~DicomFindAnswers() - { - Clear(); - } - - Encoding GetEncoding() const - { - return encoding_; - } - - void SetEncoding(Encoding encoding); - - void SetWorklist(bool isWorklist); - - bool IsWorklist() const - { - return isWorklist_; - } - - void Clear(); - - void Reserve(size_t index); - - void Add(const DicomMap& map); - - void Add(ParsedDicomFile& dicom); - - void Add(const void* dicom, - size_t size); - - size_t GetSize() const - { - return answers_.size(); - } - - ParsedDicomFile& GetAnswer(size_t index) const; - - DcmDataset* ExtractDcmDataset(size_t index) const; - - void ToJson(Json::Value& target, - bool simplify) const; - - void ToJson(Json::Value& target, - size_t index, - bool simplify) const; - - bool IsComplete() const - { - return complete_; - } - - void SetComplete(bool isComplete) - { - complete_ = isComplete; - } - }; -}
--- a/OrthancServer/DicomProtocol/DicomServer.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,382 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomServer.h" - -#include "../../Core/Logging.h" -#include "../../Core/MultiThreading/RunnableWorkersPool.h" -#include "../../Core/OrthancException.h" -#include "../../Core/Toolbox.h" -#include "../Internals/CommandDispatcher.h" - -#include <boost/thread.hpp> - -#if defined(__linux__) -#include <cstdlib> -#endif - - -namespace Orthanc -{ - struct DicomServer::PImpl - { - boost::thread thread_; - T_ASC_Network *network_; - std::auto_ptr<RunnableWorkersPool> workers_; - }; - - - void DicomServer::ServerThread(DicomServer* server) - { - LOG(INFO) << "DICOM server started"; - - while (server->continue_) - { - /* receive an association and acknowledge or reject it. If the association was */ - /* acknowledged, offer corresponding services and invoke one or more if required. */ - std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); - - try - { - if (dispatcher.get() != NULL) - { - server->pimpl_->workers_->Add(dispatcher.release()); - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception in the DICOM server thread: " << e.What(); - } - } - - LOG(INFO) << "DICOM server stopping"; - } - - - DicomServer::DicomServer() : - pimpl_(new PImpl), - aet_("ANY-SCP") - { - port_ = 104; - modalities_ = NULL; - findRequestHandlerFactory_ = NULL; - moveRequestHandlerFactory_ = NULL; - storeRequestHandlerFactory_ = NULL; - worklistRequestHandlerFactory_ = NULL; - applicationEntityFilter_ = NULL; - checkCalledAet_ = true; - associationTimeout_ = 30; - continue_ = false; - } - - DicomServer::~DicomServer() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - void DicomServer::SetPortNumber(uint16_t port) - { - Stop(); - port_ = port; - } - - uint16_t DicomServer::GetPortNumber() const - { - return port_; - } - - void DicomServer::SetAssociationTimeout(uint32_t seconds) - { - LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): " - << seconds << " seconds (0 = no timeout)"; - - Stop(); - associationTimeout_ = seconds; - } - - uint32_t DicomServer::GetAssociationTimeout() const - { - return associationTimeout_; - } - - - void DicomServer::SetCalledApplicationEntityTitleCheck(bool check) - { - Stop(); - checkCalledAet_ = check; - } - - bool DicomServer::HasCalledApplicationEntityTitleCheck() const - { - return checkCalledAet_; - } - - void DicomServer::SetApplicationEntityTitle(const std::string& aet) - { - if (aet.size() == 0) - { - throw OrthancException(ErrorCode_BadApplicationEntityTitle); - } - - if (aet.size() > 16) - { - throw OrthancException(ErrorCode_BadApplicationEntityTitle); - } - - for (size_t i = 0; i < aet.size(); i++) - { - if (!(aet[i] == '-' || - aet[i] == '_' || - isdigit(aet[i]) || - (aet[i] >= 'A' && aet[i] <= 'Z'))) - { - LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\""; - break; - } - } - - Stop(); - aet_ = aet; - } - - const std::string& DicomServer::GetApplicationEntityTitle() const - { - return aet_; - } - - void DicomServer::SetRemoteModalities(IRemoteModalities& modalities) - { - Stop(); - modalities_ = &modalities; - } - - DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const - { - if (modalities_ == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return *modalities_; - } - } - - void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory) - { - Stop(); - findRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasFindRequestHandlerFactory() const - { - return (findRequestHandlerFactory_ != NULL); - } - - IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const - { - if (HasFindRequestHandlerFactory()) - { - return *findRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCFindHandler); - } - } - - void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory) - { - Stop(); - moveRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasMoveRequestHandlerFactory() const - { - return (moveRequestHandlerFactory_ != NULL); - } - - IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const - { - if (HasMoveRequestHandlerFactory()) - { - return *moveRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCMoveHandler); - } - } - - void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory) - { - Stop(); - storeRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasStoreRequestHandlerFactory() const - { - return (storeRequestHandlerFactory_ != NULL); - } - - IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const - { - if (HasStoreRequestHandlerFactory()) - { - return *storeRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCStoreHandler); - } - } - - void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory) - { - Stop(); - worklistRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasWorklistRequestHandlerFactory() const - { - return (worklistRequestHandlerFactory_ != NULL); - } - - IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const - { - if (HasWorklistRequestHandlerFactory()) - { - return *worklistRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoWorklistHandler); - } - } - - void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory) - { - Stop(); - applicationEntityFilter_ = &factory; - } - - bool DicomServer::HasApplicationEntityFilter() const - { - return (applicationEntityFilter_ != NULL); - } - - IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const - { - if (HasApplicationEntityFilter()) - { - return *applicationEntityFilter_; - } - else - { - throw OrthancException(ErrorCode_NoApplicationEntityFilter); - } - } - - void DicomServer::Start() - { - if (modalities_ == NULL) - { - LOG(ERROR) << "No list of modalities was provided to the DICOM server"; - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - Stop(); - - /* initialize network, i.e. create an instance of T_ASC_Network*. */ - OFCondition cond = ASC_initializeNetwork - (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_); - if (cond.bad()) - { - LOG(ERROR) << "cannot create network: " << cond.text(); - throw OrthancException(ErrorCode_DicomPortInUse); - } - - continue_ = true; - pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? - pimpl_->thread_ = boost::thread(ServerThread, this); - } - - - void DicomServer::Stop() - { - if (continue_) - { - continue_ = false; - - if (pimpl_->thread_.joinable()) - { - pimpl_->thread_.join(); - } - - pimpl_->workers_.reset(NULL); - - /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ - /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ - OFCondition cond = ASC_dropNetwork(&pimpl_->network_); - if (cond.bad()) - { - LOG(ERROR) << "Error while dropping the network: " << cond.text(); - } - } - } - - - bool DicomServer::IsMyAETitle(const std::string& aet) const - { - if (modalities_ == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (!HasCalledApplicationEntityTitleCheck()) - { - // OK, no check on the AET. - return true; - } - else - { - return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle()); - } - } - -}
--- a/OrthancServer/DicomProtocol/DicomServer.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "IFindRequestHandlerFactory.h" -#include "IMoveRequestHandlerFactory.h" -#include "IStoreRequestHandlerFactory.h" -#include "IWorklistRequestHandlerFactory.h" -#include "IApplicationEntityFilter.h" -#include "RemoteModalityParameters.h" - -#include <boost/shared_ptr.hpp> -#include <boost/noncopyable.hpp> - - -namespace Orthanc -{ - class DicomServer : public boost::noncopyable - { - public: - // WARNING: The methods of this class must be thread-safe - class IRemoteModalities : public boost::noncopyable - { - public: - virtual ~IRemoteModalities() - { - } - - virtual bool IsSameAETitle(const std::string& aet1, - const std::string& aet2) = 0; - - virtual bool LookupAETitle(RemoteModalityParameters& modality, - const std::string& aet) = 0; - }; - - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - bool checkCalledAet_; - std::string aet_; - uint16_t port_; - bool continue_; - uint32_t associationTimeout_; - IRemoteModalities* modalities_; - IFindRequestHandlerFactory* findRequestHandlerFactory_; - IMoveRequestHandlerFactory* moveRequestHandlerFactory_; - IStoreRequestHandlerFactory* storeRequestHandlerFactory_; - IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_; - IApplicationEntityFilter* applicationEntityFilter_; - - static void ServerThread(DicomServer* server); - - public: - DicomServer(); - - ~DicomServer(); - - void SetPortNumber(uint16_t port); - uint16_t GetPortNumber() const; - - void SetAssociationTimeout(uint32_t seconds); - uint32_t GetAssociationTimeout() const; - - void SetCalledApplicationEntityTitleCheck(bool check); - bool HasCalledApplicationEntityTitleCheck() const; - - void SetApplicationEntityTitle(const std::string& aet); - const std::string& GetApplicationEntityTitle() const; - - void SetRemoteModalities(IRemoteModalities& modalities); - IRemoteModalities& GetRemoteModalities() const; - - void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler); - bool HasFindRequestHandlerFactory() const; - IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const; - - void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler); - bool HasMoveRequestHandlerFactory() const; - IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const; - - void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler); - bool HasStoreRequestHandlerFactory() const; - IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const; - - void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler); - bool HasWorklistRequestHandlerFactory() const; - IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const; - - void SetApplicationEntityFilter(IApplicationEntityFilter& handler); - bool HasApplicationEntityFilter() const; - IApplicationEntityFilter& GetApplicationEntityFilter() const; - - void Start(); - - void Stop(); - - bool IsMyAETitle(const std::string& aet) const; - }; - -}
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1220 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomUserConnection.h" - -#include "../../Core/DicomFormat/DicomArray.h" -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" - -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcistrmf.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmnet/diutil.h> - -#include <set> - - -#ifdef _WIN32 -/** - * "The maximum length, in bytes, of the string returned in the buffer - * pointed to by the name parameter is dependent on the namespace provider, - * but this string must be 256 bytes or less. - * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx - **/ -# define HOST_NAME_MAX 256 -# include <winsock.h> -#endif - - -#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) -/** - * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that - * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an - * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect - * that the result will fit." - * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html - **/ -#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX -#endif - - -static const char* DEFAULT_PREFERRED_TRANSFER_SYNTAX = UID_LittleEndianImplicitTransferSyntax; - -/** - * "If we have more than 64 storage SOP classes, tools such as - * storescu will fail because they attempt to negotiate two - * presentation contexts for each SOP class, and there is a total - * limit of 128 contexts for one association." - **/ -static const unsigned int MAXIMUM_STORAGE_SOP_CLASSES = 64; - - -namespace Orthanc -{ - // By default, the timeout for DICOM SCU (client) connections is set to 10 seconds - static uint32_t defaultTimeout_ = 10; - - struct DicomUserConnection::PImpl - { - // Connection state - uint32_t dimseTimeout_; - uint32_t acseTimeout_; - T_ASC_Network* net_; - T_ASC_Parameters* params_; - T_ASC_Association* assoc_; - - bool IsOpen() const - { - return assoc_ != NULL; - } - - void CheckIsOpen() const; - - void Store(DcmInputStream& is, - DicomUserConnection& connection, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - }; - - - static void Check(const OFCondition& cond) - { - if (cond.bad()) - { - LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text()); - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - void DicomUserConnection::PImpl::CheckIsOpen() const - { - if (!IsOpen()) - { - LOG(ERROR) << "DicomUserConnection: First open the connection"; - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - - void DicomUserConnection::CheckIsOpen() const - { - pimpl_->CheckIsOpen(); - } - - - static void RegisterStorageSOPClass(T_ASC_Parameters* params, - unsigned int& presentationContextId, - const std::string& sopClass, - const char* asPreferred[], - std::vector<const char*>& asFallback) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), asPreferred, 1)); - presentationContextId += 2; - - if (asFallback.size() > 0) - { - Check(ASC_addPresentationContext(params, presentationContextId, - sopClass.c_str(), &asFallback[0], asFallback.size())); - presentationContextId += 2; - } - } - - - void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) - { - // Flatten an array with the preferred transfer syntax - const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; - - // Setup the fallback transfer syntaxes - std::set<std::string> fallbackSyntaxes; - fallbackSyntaxes.insert(UID_LittleEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_BigEndianExplicitTransferSyntax); - fallbackSyntaxes.insert(UID_LittleEndianImplicitTransferSyntax); - fallbackSyntaxes.erase(preferredTransferSyntax); - - // Flatten an array with the fallback transfer syntaxes - std::vector<const char*> asFallback; - asFallback.reserve(fallbackSyntaxes.size()); - for (std::set<std::string>::const_iterator - it = fallbackSyntaxes.begin(); it != fallbackSyntaxes.end(); ++it) - { - asFallback.push_back(it->c_str()); - } - - CheckStorageSOPClassesInvariant(); - unsigned int presentationContextId = 1; - - for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); - it != reservedStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback); - } - - for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); - it != storageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback); - } - - for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); - it != defaultStorageSOPClasses_.end(); ++it) - { - RegisterStorageSOPClass(pimpl_->params_, presentationContextId, - *it, asPreferred, asFallback); - } - } - - - static bool IsGenericTransferSyntax(const std::string& syntax) - { - return (syntax == UID_LittleEndianExplicitTransferSyntax || - syntax == UID_BigEndianExplicitTransferSyntax || - syntax == UID_LittleEndianImplicitTransferSyntax); - } - - - void DicomUserConnection::PImpl::Store(DcmInputStream& is, - DicomUserConnection& connection, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - CheckIsOpen(); - - DcmFileFormat dcmff; - Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength)); - - // Determine the storage SOP class UID for this instance - static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016); - OFString sopClassUid; - if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good()) - { - connection.AddStorageSOPClass(sopClassUid.c_str()); - } - - // Determine whether a new presentation context must be - // negotiated, depending on the transfer syntax of this instance - DcmXfer xfer(dcmff.getDataset()->getOriginalXfer()); - const std::string syntax(xfer.getXferID()); - bool isGeneric = IsGenericTransferSyntax(syntax); - - bool renegotiate; - if (isGeneric) - { - // Are we making a generic-to-specific or specific-to-generic change of - // the transfer syntax? If this is the case, renegotiate the connection. - renegotiate = !IsGenericTransferSyntax(connection.GetPreferredTransferSyntax()); - } - else - { - // We are using a specific transfer syntax. Renegotiate if the - // current connection does not match this transfer syntax. - renegotiate = (syntax != connection.GetPreferredTransferSyntax()); - } - - if (renegotiate) - { - LOG(INFO) << "Change in the transfer syntax: the C-Store associated must be renegotiated"; - - if (isGeneric) - { - connection.ResetPreferredTransferSyntax(); - } - else - { - connection.SetPreferredTransferSyntax(syntax); - } - } - - if (!connection.IsOpen()) - { - LOG(INFO) << "Renegotiating a C-Store association due to a change in the parameters"; - connection.Open(); - } - - // Figure out which SOP class and SOP instance is encapsulated in the file - DIC_UI sopClass; - DIC_UI sopInstance; - if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance)) - { - throw OrthancException(ErrorCode_NoSopClassOrInstance); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); - if (presID == 0) - { - const char *modalityName = dcmSOPClassUIDToModality(sopClass); - if (!modalityName) modalityName = dcmFindNameOfUID(sopClass); - if (!modalityName) modalityName = "unknown SOP class"; - throw OrthancException(ErrorCode_NoPresentationContext); - } - - // Prepare the transmission of data - T_DIMSE_C_StoreRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = assoc_->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.AffectedSOPInstanceUID, sopInstance, DIC_UI_LEN); - - if (!moveOriginatorAET.empty()) - { - strncpy(request.MoveOriginatorApplicationEntityTitle, - moveOriginatorAET.c_str(), DIC_AE_LEN); - request.opts = O_STORE_MOVEORIGINATORAETITLE; - - request.MoveOriginatorID = moveOriginatorID; // The type DIC_US is an alias for uint16_t - request.opts |= O_STORE_MOVEORIGINATORID; - } - - // Finally conduct transmission of data - T_DIMSE_C_StoreRSP rsp; - DcmDataset* statusDetail = NULL; - Check(DIMSE_storeUser(assoc_, presID, &request, - NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL, - /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_, - &rsp, &statusDetail, NULL)); - - if (statusDetail != NULL) - { - delete statusDetail; - } - } - - - namespace - { - struct FindPayload - { - DicomFindAnswers* answers; - const char* level; - bool isWorklist; - }; - } - - - static void FindCallback( - /* in */ - void *callbackData, - T_DIMSE_C_FindRQ *request, /* original find request */ - int responseCount, - T_DIMSE_C_FindRSP *response, /* pending response received */ - DcmDataset *responseIdentifiers /* pending response identifiers */ - ) - { - FindPayload& payload = *reinterpret_cast<FindPayload*>(callbackData); - - if (responseIdentifiers != NULL) - { - if (payload.isWorklist) - { - ParsedDicomFile answer(*responseIdentifiers); - payload.answers->Add(answer); - } - else - { - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); - - if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); - } - - payload.answers->Add(m); - } - } - } - - - static void FixFindQuery(DicomMap& fixedQuery, - ResourceType level, - const DicomMap& fields) - { - std::set<DicomTag> allowedTags; - - // WARNING: Do not add "break" or reorder items in this switch-case! - switch (level) - { - case ResourceType_Instance: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); - - case ResourceType_Series: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); - - case ResourceType_Study: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); - - case ResourceType_Patient: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - switch (level) - { - case ResourceType_Patient: - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); - break; - - case ResourceType_Study: - allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); - allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); - break; - - case ResourceType_Series: - allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); - break; - - default: - break; - } - - allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); - - DicomArray query(fields); - for (size_t i = 0; i < query.GetSize(); i++) - { - const DicomTag& tag = query.GetElement(i).GetTag(); - if (allowedTags.find(tag) == allowedTags.end()) - { - LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; - } - else - { - fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); - } - } - } - - - static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, - ModalityManufacturer manufacturer) - { - // Fix outgoing C-Find requests issue for Syngo.Via and its - // solution was reported by Emsy Chan by private mail on - // 2015-06-17. According to Robert van Ommen (2015-11-30), the - // same fix is required for Agfa Impax. This was generalized for - // generic manufacturer since it seems to affect PhilipsADW, - // GEWAServer as well: - // https://bitbucket.org/sjodogne/orthanc/issues/31/ - - switch (manufacturer) - { - case ModalityManufacturer_GenericNoWildcardInDates: - case ModalityManufacturer_GenericNoUniversalWildcard: - { - std::auto_ptr<DicomMap> fix(fields.Clone()); - - std::set<DicomTag> tags; - fix->GetTags(tags); - - for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it) - { - // Replace a "*" wildcard query by an empty query ("") for - // "date" or "all" value representations depending on the - // type of manufacturer. - if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || - (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && - FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) - { - const DicomValue* value = fix->TestAndGetValue(*it); - - if (value != NULL && - !value->IsNull() && - value->GetContent() == "*") - { - fix->SetValue(*it, "", false); - } - } - } - - return new ParsedDicomFile(*fix); - } - - default: - return new ParsedDicomFile(fields); - } - } - - - static void ExecuteFind(DicomFindAnswers& answers, - T_ASC_Association* association, - DcmDataset* dataset, - const char* sopClass, - bool isWorklist, - const char* level, - uint32_t dimseTimeout) - { - assert(isWorklist ^ (level != NULL)); - - FindPayload payload; - payload.answers = &answers; - payload.level = level; - payload.isWorklist = isWorklist; - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(association, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomFindUnavailable); - } - - T_DIMSE_C_FindRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = association->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - - T_DIMSE_C_FindRSP response; - DcmDataset* statusDetail = NULL; - OFCondition cond = DIMSE_findUser(association, presID, &request, dataset, - FindCallback, &payload, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ dimseTimeout, - &response, &statusDetail); - - if (statusDetail) - { - delete statusDetail; - } - - Check(cond); - } - - - void DicomUserConnection::Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& originalFields) - { - DicomMap fields; - FixFindQuery(fields, level, originalFields); - - CheckIsOpen(); - - std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* clevel = NULL; - const char* sopClass = NULL; - - switch (level) - { - case ResourceType_Patient: - clevel = "PATIENT"; - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - break; - - case ResourceType_Study: - clevel = "STUDY"; - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Series: - clevel = "SERIES"; - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Instance: - clevel = "INSTANCE"; - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee) - { - // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. - // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J - // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE"); - } - else - { - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE"); - } - - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Add the expected tags for this query level. - // WARNING: Do not reorder or add "break" in this switch-case! - switch (level) - { - case ResourceType_Instance: - // SOP Instance UID - if (!fields.HasTag(0x0008, 0x0018)) - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), ""); - - case ResourceType_Series: - // Series instance UID - if (!fields.HasTag(0x0020, 0x000e)) - DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), ""); - - case ResourceType_Study: - // Accession number - if (!fields.HasTag(0x0008, 0x0050)) - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), ""); - - // Study instance UID - if (!fields.HasTag(0x0020, 0x000d)) - DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), ""); - - case ResourceType_Patient: - // Patient ID - if (!fields.HasTag(0x0010, 0x0020)) - DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), ""); - - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(clevel != NULL && sopClass != NULL); - ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, false, clevel, pimpl_->dimseTimeout_); - } - - - void DicomUserConnection::MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields) - { - CheckIsOpen(); - - std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_)); - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; - switch (level) - { - case ResourceType_Patient: - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT"); - break; - - case ResourceType_Study: - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY"); - break; - - case ResourceType_Series: - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES"); - break; - - case ResourceType_Instance: - if (manufacturer_ == ModalityManufacturer_ClearCanvas || - manufacturer_ == ModalityManufacturer_Dcm4Chee) - { - // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>. - // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J - // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE"); - } - else - { - DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE"); - } - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomMoveUnavailable); - } - - T_DIMSE_C_MoveRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = pimpl_->assoc_->nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); - - T_DIMSE_C_MoveRSP response; - DcmDataset* statusDetail = NULL; - DcmDataset* responseIdentifiers = NULL; - OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset, - NULL, NULL, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - pimpl_->net_, NULL, NULL, - &response, &statusDetail, &responseIdentifiers); - - if (statusDetail) - { - delete statusDetail; - } - - if (responseIdentifiers) - { - delete responseIdentifiers; - } - - Check(cond); - } - - - void DicomUserConnection::ResetStorageSOPClasses() - { - CheckStorageSOPClassesInvariant(); - - storageSOPClasses_.clear(); - defaultStorageSOPClasses_.clear(); - - // Copy the short list of storage SOP classes from DCMTK, making - // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**). - - std::set<std::string> uncommon; - uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage); - uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage); - uncommon.insert(UID_ColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage); - uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage); - - // Add the storage syntaxes for C-STORE - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++) - { - if (uncommon.find(dcmShortSCUStorageSOPClassUIDs[i]) == uncommon.end()) - { - defaultStorageSOPClasses_.insert(dcmShortSCUStorageSOPClassUIDs[i]); - } - } - - CheckStorageSOPClassesInvariant(); - } - - - DicomUserConnection::DicomUserConnection() : - pimpl_(new PImpl), - preferredTransferSyntax_(DEFAULT_PREFERRED_TRANSFER_SYNTAX), - localAet_("STORESCU"), - remoteAet_("ANY-SCP"), - remoteHost_("127.0.0.1") - { - remotePort_ = 104; - manufacturer_ = ModalityManufacturer_Generic; - - SetTimeout(defaultTimeout_); - pimpl_->net_ = NULL; - pimpl_->params_ = NULL; - pimpl_->assoc_ = NULL; - - // SOP classes for C-ECHO, C-FIND and C-MOVE (**) - reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass); - reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel); - - ResetStorageSOPClasses(); - } - - DicomUserConnection::~DicomUserConnection() - { - Close(); - } - - - void DicomUserConnection::SetRemoteModality(const RemoteModalityParameters& parameters) - { - SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); - SetRemoteHost(parameters.GetHost()); - SetRemotePort(parameters.GetPort()); - SetRemoteManufacturer(parameters.GetManufacturer()); - } - - - void DicomUserConnection::SetLocalApplicationEntityTitle(const std::string& aet) - { - if (localAet_ != aet) - { - Close(); - localAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteApplicationEntityTitle(const std::string& aet) - { - if (remoteAet_ != aet) - { - Close(); - remoteAet_ = aet; - } - } - - void DicomUserConnection::SetRemoteManufacturer(ModalityManufacturer manufacturer) - { - if (manufacturer_ != manufacturer) - { - Close(); - manufacturer_ = manufacturer; - } - } - - void DicomUserConnection::ResetPreferredTransferSyntax() - { - SetPreferredTransferSyntax(DEFAULT_PREFERRED_TRANSFER_SYNTAX); - } - - void DicomUserConnection::SetPreferredTransferSyntax(const std::string& preferredTransferSyntax) - { - if (preferredTransferSyntax_ != preferredTransferSyntax) - { - Close(); - preferredTransferSyntax_ = preferredTransferSyntax; - } - } - - - void DicomUserConnection::SetRemoteHost(const std::string& host) - { - if (remoteHost_ != host) - { - if (host.size() > HOST_NAME_MAX - 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - Close(); - remoteHost_ = host; - } - } - - void DicomUserConnection::SetRemotePort(uint16_t port) - { - if (remotePort_ != port) - { - Close(); - remotePort_ = port; - } - } - - void DicomUserConnection::Open() - { - if (IsOpen()) - { - // Don't reopen the connection - return; - } - - LOG(INFO) << "Opening a DICOM SCU connection from AET \"" << GetLocalApplicationEntityTitle() - << "\" to AET \"" << GetRemoteApplicationEntityTitle() << "\" on host " - << GetRemoteHost() << ":" << GetRemotePort() - << " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")"; - - Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_)); - Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); - - // Set this application's title and the called application's title in the params - Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL)); - - // Set the network addresses of the local and remote entities - char localHost[HOST_NAME_MAX]; - gethostname(localHost, HOST_NAME_MAX - 1); - - char remoteHostAndPort[HOST_NAME_MAX]; - -#ifdef _MSC_VER - _snprintf -#else - snprintf -#endif - (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_); - - Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort)); - - // Set various options - Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false)); - - SetupPresentationContexts(preferredTransferSyntax_); - - // Do the association - Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_)); - - if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0) - { - throw OrthancException(ErrorCode_NoPresentationContext); - } - } - - void DicomUserConnection::Close() - { - if (pimpl_->assoc_ != NULL) - { - ASC_releaseAssociation(pimpl_->assoc_); - ASC_destroyAssociation(&pimpl_->assoc_); - pimpl_->assoc_ = NULL; - pimpl_->params_ = NULL; - } - else - { - if (pimpl_->params_ != NULL) - { - ASC_destroyAssociationParameters(&pimpl_->params_); - pimpl_->params_ = NULL; - } - } - - if (pimpl_->net_ != NULL) - { - ASC_dropNetwork(&pimpl_->net_); - pimpl_->net_ = NULL; - } - } - - bool DicomUserConnection::IsOpen() const - { - return pimpl_->IsOpen(); - } - - void DicomUserConnection::Store(const char* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - if (size > 0) - is.setBuffer(buffer, size); - is.setEos(); - - pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); - } - - void DicomUserConnection::Store(const std::string& buffer, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - if (buffer.size() > 0) - Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveOriginatorAET, moveOriginatorID); - else - Store(NULL, 0, moveOriginatorAET, moveOriginatorID); - } - - void DicomUserConnection::StoreFile(const std::string& path, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - // Prepare an input stream for the file - DcmInputFileStream is(path.c_str()); - pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); - } - - bool DicomUserConnection::Echo() - { - CheckIsOpen(); - DIC_US status; - Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ pimpl_->dimseTimeout_, - &status, NULL)); - return status == STATUS_Success; - } - - - static void TestAndCopyTag(DicomMap& result, - const DicomMap& source, - const DicomTag& tag) - { - if (!source.HasTag(tag)) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result.SetValue(tag, source.GetValue(tag)); - } - } - - - void DicomUserConnection::Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult) - { - DicomMap move; - switch (level) - { - case ResourceType_Patient: - TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); - break; - - case ResourceType_Study: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - break; - - case ResourceType_Series: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - break; - - case ResourceType_Instance: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - MoveInternal(targetAet, level, move); - } - - - void DicomUserConnection::Move(const std::string& targetAet, - const DicomMap& findResult) - { - if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - throw OrthancException(ErrorCode_InternalError); - } - - const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); - ResourceType level = StringToResourceType(tmp.c_str()); - - Move(targetAet, level, findResult); - } - - - void DicomUserConnection::MovePatient(const std::string& targetAet, - const std::string& patientId) - { - DicomMap query; - query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); - MoveInternal(targetAet, ResourceType_Patient, query); - } - - void DicomUserConnection::MoveStudy(const std::string& targetAet, - const std::string& studyUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - MoveInternal(targetAet, ResourceType_Study, query); - } - - void DicomUserConnection::MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - MoveInternal(targetAet, ResourceType_Series, query); - } - - void DicomUserConnection::MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); - MoveInternal(targetAet, ResourceType_Instance, query); - } - - - void DicomUserConnection::SetTimeout(uint32_t seconds) - { - if (seconds == 0) - { - DisableTimeout(); - } - else - { - dcmConnectionTimeout.set(seconds); - pimpl_->dimseTimeout_ = seconds; - pimpl_->acseTimeout_ = 10; // Timeout used during association negociation - } - } - - - void DicomUserConnection::DisableTimeout() - { - /** - * Global timeout (seconds) for connecting to remote hosts. - * Default value is -1 which selects infinite timeout, i.e. blocking connect(). - */ - dcmConnectionTimeout.set(-1); - pimpl_->dimseTimeout_ = 0; - pimpl_->acseTimeout_ = 10; // Timeout used during association negociation - } - - - void DicomUserConnection::CheckStorageSOPClassesInvariant() const - { - assert(storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() + - reservedStorageSOPClasses_.size() <= MAXIMUM_STORAGE_SOP_CLASSES); - } - - void DicomUserConnection::AddStorageSOPClass(const char* sop) - { - CheckStorageSOPClassesInvariant(); - - if (storageSOPClasses_.find(sop) != storageSOPClasses_.end()) - { - // This storage SOP class is already explicitly registered. Do - // nothing. - return; - } - - if (defaultStorageSOPClasses_.find(sop) != defaultStorageSOPClasses_.end()) - { - // This storage SOP class is not explicitly registered, but is - // used by default. Just register it explicitly. - defaultStorageSOPClasses_.erase(sop); - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - return; - } - - // This storage SOP class is neither explicitly, nor implicitly - // registered. Close the connection and register it explicitly. - - Close(); - - if (reservedStorageSOPClasses_.size() + - storageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) // (*) - { - // The maximum number of SOP classes is reached - ResetStorageSOPClasses(); - defaultStorageSOPClasses_.erase(sop); - } - else if (reservedStorageSOPClasses_.size() + storageSOPClasses_.size() + - defaultStorageSOPClasses_.size() >= MAXIMUM_STORAGE_SOP_CLASSES) - { - // Make room in the default storage syntaxes - assert(!defaultStorageSOPClasses_.empty()); // Necessarily true because condition (*) is false - defaultStorageSOPClasses_.erase(*defaultStorageSOPClasses_.rbegin()); - } - - // Explicitly register the new storage syntax - storageSOPClasses_.insert(sop); - - CheckStorageSOPClassesInvariant(); - } - - - void DicomUserConnection::FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query) - { - CheckIsOpen(); - - DcmDataset* dataset = query.GetDcmtkObject().getDataset(); - const char* sopClass = UID_FINDModalityWorklistInformationModel; - - ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_); - } - - - void DicomUserConnection::SetDefaultTimeout(uint32_t seconds) - { - LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " - << seconds << " seconds (0 = no timeout)"; - defaultTimeout_ = seconds; - } -}
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "DicomFindAnswers.h" -#include "../ServerEnumerations.h" -#include "RemoteModalityParameters.h" - -#include <stdint.h> -#include <boost/shared_ptr.hpp> -#include <boost/noncopyable.hpp> -#include <list> - -namespace Orthanc -{ - class DicomUserConnection : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr<PImpl> pimpl_; - - // Connection parameters - std::string preferredTransferSyntax_; - std::string localAet_; - std::string remoteAet_; - std::string remoteHost_; - uint16_t remotePort_; - ModalityManufacturer manufacturer_; - std::set<std::string> storageSOPClasses_; - std::list<std::string> reservedStorageSOPClasses_; - std::set<std::string> defaultStorageSOPClasses_; - - void CheckIsOpen() const; - - void SetupPresentationContexts(const std::string& preferredTransferSyntax); - - void MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields); - - void ResetStorageSOPClasses(); - - void CheckStorageSOPClassesInvariant() const; - - public: - DicomUserConnection(); - - ~DicomUserConnection(); - - void SetRemoteModality(const RemoteModalityParameters& parameters); - - void SetLocalApplicationEntityTitle(const std::string& aet); - - const std::string& GetLocalApplicationEntityTitle() const - { - return localAet_; - } - - void SetRemoteApplicationEntityTitle(const std::string& aet); - - const std::string& GetRemoteApplicationEntityTitle() const - { - return remoteAet_; - } - - void SetRemoteHost(const std::string& host); - - const std::string& GetRemoteHost() const - { - return remoteHost_; - } - - void SetRemotePort(uint16_t port); - - uint16_t GetRemotePort() const - { - return remotePort_; - } - - void SetRemoteManufacturer(ModalityManufacturer manufacturer); - - ModalityManufacturer GetRemoteManufacturer() const - { - return manufacturer_; - } - - void ResetPreferredTransferSyntax(); - - void SetPreferredTransferSyntax(const std::string& preferredTransferSyntax); - - const std::string& GetPreferredTransferSyntax() const - { - return preferredTransferSyntax_; - } - - void AddStorageSOPClass(const char* sop); - - void Open(); - - void Close(); - - bool IsOpen() const; - - bool Echo(); - - void Store(const char* buffer, - size_t size, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(const char* buffer, - size_t size) - { - Store(buffer, size, "", 0); // Not a C-Move - } - - void Store(const std::string& buffer, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(const std::string& buffer) - { - Store(buffer, "", 0); // Not a C-Move - } - - void StoreFile(const std::string& path, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void StoreFile(const std::string& path) - { - StoreFile(path, "", 0); // Not a C-Move - } - - void Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& fields); - - void Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult); - - void Move(const std::string& targetAet, - const DicomMap& findResult); - - void MovePatient(const std::string& targetAet, - const std::string& patientId); - - void MoveStudy(const std::string& targetAet, - const std::string& studyUid); - - void MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid); - - void MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - - void SetTimeout(uint32_t seconds); - - void DisableTimeout(); - - void FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query); - - static void SetDefaultTimeout(uint32_t seconds); - }; -}
--- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ServerEnumerations.h" - -#include <string> - -namespace Orthanc -{ - class IApplicationEntityFilter : public boost::noncopyable - { - public: - virtual ~IApplicationEntityFilter() - { - } - - virtual bool IsAllowedConnection(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - - virtual bool IsAllowedRequest(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - DicomRequestType type) = 0; - - virtual bool IsAllowedTransferSyntax(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - TransferSyntax syntax) = 0; - - virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomFindAnswers.h" - -namespace Orthanc -{ - class IFindRequestHandler : public boost::noncopyable - { - public: - virtual ~IFindRequestHandler() - { - } - - virtual void Handle(DicomFindAnswers& answers, - const DicomMap& input, - const std::list<DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IFindRequestHandler.h" - -namespace Orthanc -{ - class IFindRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IFindRequestHandlerFactory() - { - } - - virtual IFindRequestHandler* ConstructFindRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IMoveRequestHandler.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/DicomFormat/DicomMap.h" - -#include <vector> -#include <string> - - -namespace Orthanc -{ - class IMoveRequestIterator : public boost::noncopyable - { - public: - enum Status - { - Status_Success, - Status_Failure, - Status_Warning - }; - - virtual ~IMoveRequestIterator() - { - } - - virtual unsigned int GetSubOperationCount() const = 0; - - virtual Status DoNext() = 0; - }; - - - class IMoveRequestHandler - { - public: - virtual ~IMoveRequestHandler() - { - } - - virtual IMoveRequestIterator* Handle(const std::string& targetAet, - const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, - uint16_t originatorId) = 0; - }; - -}
--- a/OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IMoveRequestHandler.h" - -namespace Orthanc -{ - class IMoveRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IMoveRequestHandlerFactory() - { - } - - virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/DicomFormat/DicomMap.h" - -#include <vector> -#include <string> -#include <json/json.h> - -namespace Orthanc -{ - class IStoreRequestHandler : public boost::noncopyable - { - public: - virtual ~IStoreRequestHandler() - { - } - - virtual void Handle(const std::string& dicomFile, - const DicomMap& dicomSummary, - const Json::Value& dicomJson, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IStoreRequestHandlerFactory.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IStoreRequestHandler.h" - -namespace Orthanc -{ - class IStoreRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IStoreRequestHandlerFactory() - { - } - - virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IWorklistRequestHandler.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomFindAnswers.h" - -namespace Orthanc -{ - class IWorklistRequestHandler : public boost::noncopyable - { - public: - virtual ~IWorklistRequestHandler() - { - } - - virtual void Handle(DicomFindAnswers& answers, - ParsedDicomFile& query, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) = 0; - }; -}
--- a/OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "IWorklistRequestHandler.h" - -namespace Orthanc -{ - class IWorklistRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IWorklistRequestHandlerFactory() - { - } - - virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0; - }; -}
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "RemoteModalityParameters.h" - -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" - -#include <boost/lexical_cast.hpp> -#include <stdexcept> - -namespace Orthanc -{ - RemoteModalityParameters::RemoteModalityParameters() : - aet_("ORTHANC"), - host_("127.0.0.1"), - port_(104), - manufacturer_(ModalityManufacturer_Generic) - { - } - - RemoteModalityParameters::RemoteModalityParameters(const std::string& aet, - const std::string& host, - uint16_t port, - ModalityManufacturer manufacturer) - { - SetApplicationEntityTitle(aet); - SetHost(host); - SetPort(port); - SetManufacturer(manufacturer); - } - - - void RemoteModalityParameters::FromJson(const Json::Value& modality) - { - if (!modality.isArray() || - (modality.size() != 3 && modality.size() != 4)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - SetApplicationEntityTitle(modality.get(0u, "").asString()); - SetHost(modality.get(1u, "").asString()); - - const Json::Value& portValue = modality.get(2u, ""); - try - { - int tmp = portValue.asInt(); - - if (tmp <= 0 || tmp >= 65535) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - SetPort(static_cast<uint16_t>(tmp)); - } - catch (std::runtime_error /* error inside JsonCpp */) - { - try - { - SetPort(boost::lexical_cast<uint16_t>(portValue.asString())); - } - catch (boost::bad_lexical_cast) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - if (modality.size() == 4) - { - const std::string& manufacturer = modality.get(3u, "").asString(); - - try - { - SetManufacturer(manufacturer); - } - catch (OrthancException&) - { - LOG(ERROR) << "Unknown modality manufacturer: \"" << manufacturer << "\""; - throw; - } - } - else - { - SetManufacturer(ModalityManufacturer_Generic); - } - } - - void RemoteModalityParameters::ToJson(Json::Value& value) const - { - value = Json::arrayValue; - value.append(GetApplicationEntityTitle()); - value.append(GetHost()); - value.append(GetPort()); - value.append(EnumerationToString(GetManufacturer())); - } -}
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ServerEnumerations.h" - -#include <stdint.h> -#include <string> -#include <json/json.h> - -namespace Orthanc -{ - class RemoteModalityParameters - { - private: - std::string aet_; - std::string host_; - uint16_t port_; - ModalityManufacturer manufacturer_; - - public: - RemoteModalityParameters(); - - RemoteModalityParameters(const std::string& aet, - const std::string& host, - uint16_t port, - ModalityManufacturer manufacturer); - - const std::string& GetApplicationEntityTitle() const - { - return aet_; - } - - void SetApplicationEntityTitle(const std::string& aet) - { - aet_ = aet; - } - - const std::string& GetHost() const - { - return host_; - } - - void SetHost(const std::string& host) - { - host_ = host; - } - - uint16_t GetPort() const - { - return port_; - } - - void SetPort(uint16_t port) - { - port_ = port; - } - - ModalityManufacturer GetManufacturer() const - { - return manufacturer_; - } - - void SetManufacturer(ModalityManufacturer manufacturer) - { - manufacturer_ = manufacturer; - } - - void SetManufacturer(const std::string& manufacturer) - { - manufacturer_ = StringToModalityManufacturer(manufacturer); - } - - void FromJson(const Json::Value& modality); - - void ToJson(Json::Value& value) const; - }; -}
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,188 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "ReusableDicomUserConnection.h" - -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" - -namespace Orthanc -{ - static boost::posix_time::ptime Now() - { - return boost::posix_time::microsec_clock::local_time(); - } - - void ReusableDicomUserConnection::Open(const std::string& localAet, - const RemoteModalityParameters& remote) - { - if (connection_ != NULL && - connection_->GetLocalApplicationEntityTitle() == localAet && - connection_->GetRemoteApplicationEntityTitle() == remote.GetApplicationEntityTitle() && - connection_->GetRemoteHost() == remote.GetHost() && - connection_->GetRemotePort() == remote.GetPort() && - connection_->GetRemoteManufacturer() == remote.GetManufacturer()) - { - // The current connection can be reused - LOG(INFO) << "Reusing the previous SCU connection"; - return; - } - - Close(); - - connection_ = new DicomUserConnection(); - connection_->SetLocalApplicationEntityTitle(localAet); - connection_->SetRemoteModality(remote); - connection_->Open(); - } - - void ReusableDicomUserConnection::Close() - { - if (connection_ != NULL) - { - delete connection_; - connection_ = NULL; - } - } - - void ReusableDicomUserConnection::CloseThread(ReusableDicomUserConnection* that) - { - for (;;) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - if (!that->continue_) - { - //LOG(INFO) << "Finishing the thread watching the global SCU connection"; - return; - } - - { - boost::mutex::scoped_lock lock(that->mutex_); - if (that->connection_ != NULL && - Now() >= that->lastUse_ + that->timeBeforeClose_) - { - LOG(INFO) << "Closing the global SCU connection after timeout"; - that->Close(); - } - } - } - } - - - ReusableDicomUserConnection::Locker::Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote) : - ::Orthanc::Locker(that) - { - that.Open(localAet, remote); - connection_ = that.connection_; - } - - - DicomUserConnection& ReusableDicomUserConnection::Locker::GetConnection() - { - if (connection_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *connection_; - } - - ReusableDicomUserConnection::ReusableDicomUserConnection() : - connection_(NULL), - timeBeforeClose_(boost::posix_time::seconds(5)) // By default, close connection after 5 seconds - { - lastUse_ = Now(); - continue_ = true; - closeThread_ = boost::thread(CloseThread, this); - } - - ReusableDicomUserConnection::~ReusableDicomUserConnection() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: ReusableDicomUserConnection::Finalize() should be invoked manually to avoid mess in the destruction order!"; - Finalize(); - } - } - - void ReusableDicomUserConnection::SetMillisecondsBeforeClose(uint64_t ms) - { - boost::mutex::scoped_lock lock(mutex_); - - if (ms == 0) - { - ms = 1; - } - - timeBeforeClose_ = boost::posix_time::milliseconds(ms); - } - - void ReusableDicomUserConnection::Lock() - { - mutex_.lock(); - } - - void ReusableDicomUserConnection::Unlock() - { - if (connection_ != NULL && - connection_->GetRemoteManufacturer() == ModalityManufacturer_StoreScp) - { - // "storescp" from DCMTK has problems when reusing a - // connection. Always close. - Close(); - } - - lastUse_ = Now(); - mutex_.unlock(); - } - - - void ReusableDicomUserConnection::Finalize() - { - if (continue_) - { - continue_ = false; - - if (closeThread_.joinable()) - { - closeThread_.join(); - } - - Close(); - } - } -} -
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "DicomUserConnection.h" -#include "../../Core/MultiThreading/Locker.h" - -#include <boost/thread.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> - -namespace Orthanc -{ - class ReusableDicomUserConnection : public ILockable - { - private: - boost::mutex mutex_; - DicomUserConnection* connection_; - bool continue_; - boost::posix_time::time_duration timeBeforeClose_; - boost::posix_time::ptime lastUse_; - boost::thread closeThread_; - - void Open(const std::string& localAet, - const RemoteModalityParameters& remote); - - void Close(); - - static void CloseThread(ReusableDicomUserConnection* that); - - protected: - virtual void Lock(); - - virtual void Unlock(); - - public: - class Locker : public ::Orthanc::Locker - { - private: - DicomUserConnection* connection_; - - public: - Locker(ReusableDicomUserConnection& that, - const std::string& localAet, - const RemoteModalityParameters& remote); - - DicomUserConnection& GetConnection(); - }; - - ReusableDicomUserConnection(); - - virtual ~ReusableDicomUserConnection(); - - void SetMillisecondsBeforeClose(uint64_t ms); - - void Finalize(); - }; -} -
--- a/OrthancServer/FromDcmtkBridge.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2075 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" -#include "../Core/Logging.h" -#include "../Core/SystemToolbox.h" -#include "../Core/Toolbox.h" -#include "../Core/TemporaryFile.h" -#include "../Core/OrthancException.h" - -#include <list> -#include <limits> - -#include <boost/lexical_cast.hpp> -#include <boost/filesystem.hpp> -#include <boost/algorithm/string/predicate.hpp> - -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcdicent.h> -#include <dcmtk/dcmdata/dcdict.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <dcmtk/dcmdata/dcpixel.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcistrmb.h> - -#include <dcmtk/dcmdata/dcvrae.h> -#include <dcmtk/dcmdata/dcvras.h> -#include <dcmtk/dcmdata/dcvrat.h> -#include <dcmtk/dcmdata/dcvrcs.h> -#include <dcmtk/dcmdata/dcvrda.h> -#include <dcmtk/dcmdata/dcvrds.h> -#include <dcmtk/dcmdata/dcvrdt.h> -#include <dcmtk/dcmdata/dcvrfd.h> -#include <dcmtk/dcmdata/dcvrfl.h> -#include <dcmtk/dcmdata/dcvris.h> -#include <dcmtk/dcmdata/dcvrlo.h> -#include <dcmtk/dcmdata/dcvrlt.h> -#include <dcmtk/dcmdata/dcvrpn.h> -#include <dcmtk/dcmdata/dcvrsh.h> -#include <dcmtk/dcmdata/dcvrsl.h> -#include <dcmtk/dcmdata/dcvrss.h> -#include <dcmtk/dcmdata/dcvrst.h> -#include <dcmtk/dcmdata/dcvrtm.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcvrul.h> -#include <dcmtk/dcmdata/dcvrus.h> -#include <dcmtk/dcmdata/dcvrut.h> - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 -# include <EmbeddedResources.h> -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 -# include <dcmtk/dcmjpeg/djdecode.h> -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 -# include <dcmtk/dcmjpls/djdecode.h> -#endif - - -namespace Orthanc -{ - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, - EmbeddedResources::FileResourceId resource) - { - std::string content; - EmbeddedResources::GetFileResource(content, resource); - - TemporaryFile tmp; - tmp.Write(content); - - if (!dictionary.loadDictionary(tmp.GetPath().c_str())) - { - LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " - << "your TEMP directory does not contain special characters."; - throw OrthancException(ErrorCode_InternalError); - } - } - -#else - static void LoadExternalDictionary(DcmDataDictionary& dictionary, - const std::string& directory, - const std::string& filename) - { - boost::filesystem::path p = directory; - p = p / filename; - - LOG(WARNING) << "Loading the external DICOM dictionary " << p; - - if (!dictionary.loadDictionary(p.string().c_str())) - { - throw OrthancException(ErrorCode_InternalError); - } - } -#endif - - - namespace - { - class DictionaryLocker - { - private: - DcmDataDictionary& dictionary_; - - public: - DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) - { - } - - ~DictionaryLocker() - { - dcmDataDict.unlock(); - } - - DcmDataDictionary& operator*() - { - return dictionary_; - } - - DcmDataDictionary* operator->() - { - return &dictionary_; - } - }; - } - - - void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) - { - LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; - - { - DictionaryLocker locker; - - locker->clear(); - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - LOG(WARNING) << "Loading the embedded dictionaries"; - /** - * Do not load DICONDE dictionary, it breaks the other tags. The - * command "strace storescu 2>&1 |grep dic" shows that DICONDE - * dictionary is not loaded by storescu. - **/ - //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE); - - LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM); - - if (loadPrivateDictionary) - { - LOG(INFO) << "Loading the embedded dictionary of private tags"; - LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE); - } - else - { - LOG(INFO) << "The dictionary of private tags has not been loaded"; - } - -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) - std::string path = DCMTK_DICTIONARY_DIR; - - const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); - if (env != NULL) - { - path = std::string(env); - } - - LoadExternalDictionary(*locker, path, "dicom.dic"); - - if (loadPrivateDictionary) - { - LoadExternalDictionary(*locker, path, "private.dic"); - } - else - { - LOG(INFO) << "The dictionary of private tags has not been loaded"; - } - -#else -#error Support your platform here -#endif - } - - /* make sure data dictionary is loaded */ - if (!dcmDataDict.isDictionaryLoaded()) - { - LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; - throw OrthancException(ErrorCode_InternalError); - } - - { - // Test the dictionary with a simple DICOM tag - DcmTag key(0x0010, 0x1030); // This is PatientWeight - if (key.getEVR() != EVR_DS) - { - LOG(ERROR) << "The DICOM dictionary has not been correctly read"; - throw OrthancException(ErrorCode_InternalError); - } - } - } - - - void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, - ValueRepresentation vr, - const std::string& name, - unsigned int minMultiplicity, - unsigned int maxMultiplicity, - const std::string& privateCreator) - { - if (minMultiplicity < 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - bool arbitrary = false; - if (maxMultiplicity == 0) - { - maxMultiplicity = DcmVariableVM; - arbitrary = true; - } - else if (maxMultiplicity < minMultiplicity) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - DcmEVR evr = ToDcmtkBridge::Convert(vr); - - LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " - << name << " (multiplicity: " << minMultiplicity << "-" - << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; - - std::auto_ptr<DcmDictEntry> entry; - if (privateCreator.empty()) - { - if (tag.GetGroup() % 2 == 1) - { - char buf[128]; - sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " - "but no private creator was associated with it", - tag.GetGroup(), tag.GetElement()); - LOG(WARNING) << buf; - } - - entry.reset(new DcmDictEntry(tag.GetGroup(), - tag.GetElement(), - evr, name.c_str(), - static_cast<int>(minMultiplicity), - static_cast<int>(maxMultiplicity), - NULL /* version */, - OFTrue /* doCopyString */, - NULL /* private creator */)); - } - else - { - // "Private Data Elements have an odd Group Number that is not - // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or - // (FFFF,eeee)." - if (tag.GetGroup() % 2 == 0 /* even */ || - tag.GetGroup() == 0x0001 || - tag.GetGroup() == 0x0003 || - tag.GetGroup() == 0x0005 || - tag.GetGroup() == 0x0007 || - tag.GetGroup() == 0xffff) - { - char buf[128]; - sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", - tag.GetGroup(), tag.GetElement()); - LOG(ERROR) << buf; - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - entry.reset(new DcmDictEntry(tag.GetGroup(), - tag.GetElement(), - evr, name.c_str(), - static_cast<int>(minMultiplicity), - static_cast<int>(maxMultiplicity), - "private" /* version */, - OFTrue /* doCopyString */, - privateCreator.c_str())); - } - - entry->setGroupRangeRestriction(DcmDictRange_Unspecified); - entry->setElementRangeRestriction(DcmDictRange_Unspecified); - - { - DictionaryLocker locker; - - if (locker->findEntry(name.c_str())) - { - LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\""; - throw OrthancException(ErrorCode_AlreadyExistingTag); - } - - locker->addEntry(entry.release()); - } - } - - - Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset, - Encoding defaultEncoding) - { - Encoding encoding = defaultEncoding; - - OFString tmp; - if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) - { - std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); - - if (characterSet.empty()) - { - // Empty specific character set tag: Use the default encoding - } - else if (GetDicomEncoding(encoding, characterSet.c_str())) - { - // The specific character set is supported by the Orthanc core - } - else - { - LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet - << ", fallback to ASCII (remove all special characters)"; - encoding = Encoding_Ascii; - } - } - else - { - // No specific character set tag: Use the default encoding - } - - return encoding; - } - - - void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, - DcmItem& dataset, - unsigned int maxStringLength, - Encoding defaultEncoding) - { - Encoding encoding = DetectEncoding(dataset, defaultEncoding); - - target.Clear(); - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - if (element && element->isLeaf()) - { - target.SetValue(element->getTag().getGTag(), - element->getTag().getETag(), - ConvertLeafElement(*element, DicomToJsonFlags_Default, maxStringLength, encoding)); - } - } - } - - - DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) - { - return DicomTag(tag.getGTag(), tag.getETag()); - } - - - DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) - { - return DicomTag(element.getGTag(), element.getETag()); - } - - - DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding) - { - if (!element.isLeaf()) - { - // This function is only applicable to leaf elements - throw OrthancException(ErrorCode_BadParameterType); - } - - char *c = NULL; - if (element.isaString() && - element.getString(c).good()) - { - if (c == NULL) // This case corresponds to the empty string - { - return new DicomValue("", false); - } - else - { - std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); - - if (maxStringLength != 0 && - utf8.size() > maxStringLength) - { - return new DicomValue; // Too long, create a NULL value - } - else - { - return new DicomValue(utf8, false); - } - } - } - - - if (element.getVR() == EVR_UN) - { - // Unknown value representation: Lookup in the dictionary. This - // is notably the case for private tags registered with the - // "Dictionary" configuration option. - DictionaryLocker locker; - - const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), - element.getTag().getPrivateCreator()); - if (entry != NULL && - entry->getVR().isaString()) - { - Uint8* data = NULL; - - // At (*), we do not try and convert to UTF-8, as nothing says - // the encoding of the private tag is the same as that of the - // remaining of the DICOM dataset. Only go for ASCII strings. - - if (element.getUint8Array(data) == EC_Normal && - Toolbox::IsAsciiString(data, element.getLength())) // (*) - { - if (data == NULL) - { - return new DicomValue("", false); // Empty string - } - else if (maxStringLength != 0 && - element.getLength() > maxStringLength) - { - return new DicomValue; // Too long, create a NULL value - } - else - { - std::string s(reinterpret_cast<const char*>(data), element.getLength()); - return new DicomValue(s, false); - } - } - } - } - - - try - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - switch (element.getVR()) - { - - /** - * Deal with binary data (including PixelData). - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_UN: // unknown value representation - case EVR_ox: // OB or OW depending on context - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - { - if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) - { - Uint8* data = NULL; - if (element.getUint8Array(data) == EC_Normal) - { - return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); - } - } - - return new DicomValue; - } - - /** - * Numeric types - **/ - - case EVR_SL: // signed long - { - Sint32 f; - if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_SS: // signed short - { - Sint16 f; - if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_UL: // unsigned long - { - Uint32 f; - if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_US: // unsigned short - { - Uint16 f; - if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_FL: // float single-precision - { - Float32 f; - if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - case EVR_FD: // float double-precision - { - Float64 f; - if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good()) - return new DicomValue(boost::lexical_cast<std::string>(f), false); - else - return new DicomValue; - } - - - /** - * Attribute tag. - **/ - - case EVR_AT: - { - DcmTagKey tag; - if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) - { - DicomTag t(tag.getGroup(), tag.getElement()); - return new DicomValue(t.Format(), false); - } - else - { - return new DicomValue; - } - } - - - /** - * Sequence types, should never occur at this point because of - * "element.isLeaf()". - **/ - - case EVR_SQ: // sequence of items - return new DicomValue; - - - /** - * Internal to DCMTK. - **/ - - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - return new DicomValue; - - - /** - * Default case. - **/ - - default: - return new DicomValue; - } - } - catch (boost::bad_lexical_cast) - { - return new DicomValue; - } - catch (std::bad_cast) - { - return new DicomValue; - } - } - - - static Json::Value& PrepareNode(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format) - { - assert(parent.type() == Json::objectValue); - - DicomTag tag(FromDcmtkBridge::GetTag(element)); - const std::string formattedTag = tag.Format(); - - if (format == DicomToJsonFormat_Short) - { - parent[formattedTag] = Json::nullValue; - return parent[formattedTag]; - } - - // This code gives access to the name of the private tags - std::string tagName = FromDcmtkBridge::GetTagName(element); - - switch (format) - { - case DicomToJsonFormat_Human: - parent[tagName] = Json::nullValue; - return parent[tagName]; - - case DicomToJsonFormat_Full: - { - parent[formattedTag] = Json::objectValue; - Json::Value& node = parent[formattedTag]; - - if (element.isLeaf()) - { - node["Name"] = tagName; - - if (element.getTag().getPrivateCreator() != NULL) - { - node["PrivateCreator"] = element.getTag().getPrivateCreator(); - } - - return node; - } - else - { - node["Name"] = tagName; - node["Type"] = "Sequence"; - node["Value"] = Json::nullValue; - return node["Value"]; - } - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - static void LeafValueToJson(Json::Value& target, - const DicomValue& value, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - Json::Value* targetValue = NULL; - Json::Value* targetType = NULL; - - switch (format) - { - case DicomToJsonFormat_Short: - case DicomToJsonFormat_Human: - { - assert(target.type() == Json::nullValue); - targetValue = ⌖ - break; - } - - case DicomToJsonFormat_Full: - { - assert(target.type() == Json::objectValue); - target["Value"] = Json::nullValue; - target["Type"] = Json::nullValue; - targetType = &target["Type"]; - targetValue = &target["Value"]; - break; - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(targetValue != NULL); - assert(targetValue->type() == Json::nullValue); - assert(targetType == NULL || targetType->type() == Json::nullValue); - - if (value.IsNull()) - { - if (targetType != NULL) - { - *targetType = "Null"; - } - } - else if (value.IsBinary()) - { - if (flags & DicomToJsonFlags_ConvertBinaryToAscii) - { - *targetValue = Toolbox::ConvertToAscii(value.GetContent()); - } - else - { - std::string s; - value.FormatDataUriScheme(s); - *targetValue = s; - } - - if (targetType != NULL) - { - *targetType = "Binary"; - } - } - else if (maxStringLength == 0 || - value.GetContent().size() <= maxStringLength) - { - *targetValue = value.GetContent(); - - if (targetType != NULL) - { - *targetType = "String"; - } - } - else - { - if (targetType != NULL) - { - *targetType = "TooLong"; - } - } - } - - - void FromDcmtkBridge::ElementToJson(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding) - { - if (parent.type() == Json::nullValue) - { - parent = Json::objectValue; - } - - assert(parent.type() == Json::objectValue); - Json::Value& target = PrepareNode(parent, element, format); - - if (element.isLeaf()) - { - // The "0" below lets "LeafValueToJson()" take care of "TooLong" values - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding)); - LeafValueToJson(target, *v, format, flags, maxStringLength); - } - else - { - assert(target.type() == Json::nullValue); - target = Json::arrayValue; - - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following dynamic_cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - DcmItem* child = sequence.getItem(i); - Json::Value& v = target.append(Json::objectValue); - DatasetToJson(v, *child, format, flags, maxStringLength, encoding); - } - } - } - - - void FromDcmtkBridge::DatasetToJson(Json::Value& parent, - DcmItem& item, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding) - { - assert(parent.type() == Json::objectValue); - - for (unsigned long i = 0; i < item.card(); i++) - { - DcmElement* element = item.getElement(i); - if (element == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); - - /*element->getTag().isPrivate()*/ - if (tag.IsPrivate() && - !(flags & DicomToJsonFlags_IncludePrivateTags)) - { - continue; - } - - if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) - { - DictionaryLocker locker; - if (locker->findEntry(element->getTag(), NULL) == NULL) - { - continue; - } - } - - DcmEVR evr = element->getTag().getEVR(); - if (evr == EVR_OB || - evr == EVR_OF || - evr == EVR_OW || - evr == EVR_UN || - evr == EVR_ox) - { - // This is a binary tag - if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || - (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) - { - continue; - } - } - - FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding); - } - } - - - void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding defaultEncoding) - { - Encoding encoding = DetectEncoding(dataset, defaultEncoding); - - target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, encoding); - } - - - void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, - DcmMetaInfo& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii); - } - - - - static std::string GetTagNameInternal(DcmTag& tag) - { - { - // Some patches for important tags because of different DICOM - // dictionaries between DCMTK versions - DicomTag tmp(tag.getGroup(), tag.getElement()); - std::string n = tmp.GetMainTagsName(); - if (n.size() != 0) - { - return n; - } - // End of patches - } - -#if 0 - // This version explicitly calls the dictionary - const DcmDataDictionary& dict = dcmDataDict.rdlock(); - const DcmDictEntry* entry = dict.findEntry(tag, NULL); - - std::string s(DcmTag_ERROR_TagName); - if (entry != NULL) - { - s = std::string(entry->getTagName()); - } - - dcmDataDict.unlock(); - return s; -#else - const char* name = tag.getTagName(); - if (name == NULL) - { - return DcmTag_ERROR_TagName; - } - else - { - return std::string(name); - } -#endif - } - - - std::string FromDcmtkBridge::GetTagName(const DicomTag& t, - const std::string& privateCreator) - { - DcmTag tag(t.GetGroup(), t.GetElement()); - - if (!privateCreator.empty()) - { - tag.setPrivateCreator(privateCreator.c_str()); - } - - return GetTagNameInternal(tag); - } - - - std::string FromDcmtkBridge::GetTagName(const DcmElement& element) - { - // Copy the tag to ensure const-correctness of DcmElement. Note - // that the private creator information is also copied. - DcmTag tag(element.getTag()); - - return GetTagNameInternal(tag); - } - - - - DicomTag FromDcmtkBridge::ParseTag(const char* name) - { - if (strlen(name) == 9 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - (name[4] == '-' || name[4] == ',') && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7]) && - isxdigit(name[8])) - { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 5); - return DicomTag(group, element); - } - - if (strlen(name) == 8 && - isxdigit(name[0]) && - isxdigit(name[1]) && - isxdigit(name[2]) && - isxdigit(name[3]) && - isxdigit(name[4]) && - isxdigit(name[5]) && - isxdigit(name[6]) && - isxdigit(name[7])) - { - uint16_t group = GetTagValue(name); - uint16_t element = GetTagValue(name + 4); - return DicomTag(group, element); - } - -#if 0 - const DcmDataDictionary& dict = dcmDataDict.rdlock(); - const DcmDictEntry* entry = dict.findEntry(name); - - if (entry == NULL) - { - dcmDataDict.unlock(); - throw OrthancException(ErrorCode_UnknownDicomTag); - } - else - { - DcmTagKey key = entry->getKey(); - DicomTag tag(key.getGroup(), key.getElement()); - dcmDataDict.unlock(); - return tag; - } -#else - DcmTag tag; - if (DcmTag::findTagFromName(name, tag).good()) - { - return DicomTag(tag.getGTag(), tag.getETag()); - } - else - { - throw OrthancException(ErrorCode_UnknownDicomTag); - } -#endif - } - - - bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) - { - DcmTag tmp(tag.GetGroup(), tag.GetElement()); - return tmp.isUnknownVR(); - } - - - void FromDcmtkBridge::ToJson(Json::Value& result, - const DicomMap& values, - bool simplify) - { - if (result.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - result.clear(); - - for (DicomMap::Map::const_iterator - it = values.map_.begin(); it != values.map_.end(); ++it) - { - // TODO Inject PrivateCreator if some is available in the DicomMap? - const std::string tagName = GetTagName(it->first, ""); - - if (simplify) - { - if (it->second->IsNull()) - { - result[tagName] = Json::nullValue; - } - else - { - // TODO IsBinary - result[tagName] = it->second->GetContent(); - } - } - else - { - Json::Value value = Json::objectValue; - - value["Name"] = tagName; - - if (it->second->IsNull()) - { - value["Type"] = "Null"; - value["Value"] = Json::nullValue; - } - else - { - // TODO IsBinary - value["Type"] = "String"; - value["Value"] = it->second->GetContent(); - } - - result[it->first.Format()] = value; - } - } - } - - - std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) - { - char uid[100]; - - switch (level) - { - case ResourceType_Patient: - // The "PatientID" field is of type LO (Long String), 64 - // Bytes Maximum. An UUID is of length 36, thus it can be used - // as a random PatientID. - return SystemToolbox::GenerateUuid(); - - case ResourceType_Instance: - return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); - - case ResourceType_Series: - return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); - - case ResourceType_Study: - return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet) - { - // Determine the transfer syntax which shall be used to write the - // information to the file. We always switch to the Little Endian - // syntax, with explicit length. - - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - - - /** - * Note that up to Orthanc 0.7.1 (inclusive), the - * "EXS_LittleEndianExplicit" was always used to save the DICOM - * dataset into memory. We now keep the original transfer syntax - * (if available). - **/ - E_TransferSyntax xfer = dataSet.getOriginalXfer(); - if (xfer == EXS_Unknown) - { - // No information about the original transfer syntax: This is - // most probably a DICOM dataset that was read from memory. - xfer = EXS_LittleEndianExplicit; - } - - E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; - - // Create the meta-header information - DcmFileFormat ff(&dataSet); - ff.validateMetaInfo(xfer); - ff.removeInvalidGroups(); - - // Create a memory buffer with the proper size - { - const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) - buffer.resize(estimatedSize); - } - - DcmOutputBufferStream ob(&buffer[0], buffer.size()); - - // Fill the memory buffer with the meta-header and the dataset - ff.transferInit(); - OFCondition c = ff.write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding); - ff.transferEnd(); - - if (c.good()) - { - // The DICOM file is successfully written, truncate the target - // buffer if its size was overestimated by (*) - ob.flush(); - - size_t effectiveSize = static_cast<size_t>(ob.tell()); - if (effectiveSize < buffer.size()) - { - buffer.resize(effectiveSize); - } - - return true; - } - else - { - // Error - buffer.clear(); - return false; - } - } - - - ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) - { - DcmTag t(tag.GetGroup(), tag.GetElement()); - return Convert(t.getEVR()); - } - - ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr) - { - switch (vr) - { - case EVR_AE: - return ValueRepresentation_ApplicationEntity; - - case EVR_AS: - return ValueRepresentation_AgeString; - - case EVR_AT: - return ValueRepresentation_AttributeTag; - - case EVR_CS: - return ValueRepresentation_CodeString; - - case EVR_DA: - return ValueRepresentation_Date; - - case EVR_DS: - return ValueRepresentation_DecimalString; - - case EVR_DT: - return ValueRepresentation_DateTime; - - case EVR_FL: - return ValueRepresentation_FloatingPointSingle; - - case EVR_FD: - return ValueRepresentation_FloatingPointDouble; - - case EVR_IS: - return ValueRepresentation_IntegerString; - - case EVR_LO: - return ValueRepresentation_LongString; - - case EVR_LT: - return ValueRepresentation_LongText; - - case EVR_OB: - return ValueRepresentation_OtherByte; - - // Not supported as of DCMTK 3.6.0 - /*case EVR_OD: - return ValueRepresentation_OtherDouble;*/ - - case EVR_OF: - return ValueRepresentation_OtherFloat; - - // Not supported as of DCMTK 3.6.0 - /*case EVR_OL: - return ValueRepresentation_OtherLong;*/ - - case EVR_OW: - return ValueRepresentation_OtherWord; - - case EVR_PN: - return ValueRepresentation_PersonName; - - case EVR_SH: - return ValueRepresentation_ShortString; - - case EVR_SL: - return ValueRepresentation_SignedLong; - - case EVR_SQ: - return ValueRepresentation_Sequence; - - case EVR_SS: - return ValueRepresentation_SignedShort; - - case EVR_ST: - return ValueRepresentation_ShortText; - - case EVR_TM: - return ValueRepresentation_Time; - - // Not supported as of DCMTK 3.6.0 - /*case EVR_UC: - return ValueRepresentation_UnlimitedCharacters;*/ - - case EVR_UI: - return ValueRepresentation_UniqueIdentifier; - - case EVR_UL: - return ValueRepresentation_UnsignedLong; - - case EVR_UN: - return ValueRepresentation_Unknown; - - // Not supported as of DCMTK 3.6.0 - /*case EVR_UR: - return ValueRepresentation_UniversalResource;*/ - - case EVR_US: - return ValueRepresentation_UnsignedShort; - - case EVR_UT: - return ValueRepresentation_UnlimitedText; - - default: - return ValueRepresentation_NotSupported; - } - } - - - static bool IsBinaryTag(const DcmTag& key) - { - return (key.isUnknownVR() || - key.getEVR() == EVR_OB || - key.getEVR() == EVR_OF || - key.getEVR() == EVR_OW || - key.getEVR() == EVR_UN || - key.getEVR() == EVR_ox); - } - - - DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (tag.IsPrivate() || - IsBinaryTag(key)) - { - return new DcmOtherByteOtherWord(key); - } - - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * Binary types, handled above - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_UN: // unknown value representation - case EVR_ox: // OB or OW depending on context - throw OrthancException(ErrorCode_InternalError); - - - /** - * String types. - * http://support.dcmtk.org/docs/classDcmByteString.html - **/ - - case EVR_AS: // age string - return new DcmAgeString(key); - - case EVR_AE: // application entity title - return new DcmApplicationEntity(key); - - case EVR_CS: // code string - return new DcmCodeString(key); - - case EVR_DA: // date string - return new DcmDate(key); - - case EVR_DT: // date time string - return new DcmDateTime(key); - - case EVR_DS: // decimal string - return new DcmDecimalString(key); - - case EVR_IS: // integer string - return new DcmIntegerString(key); - - case EVR_TM: // time string - return new DcmTime(key); - - case EVR_UI: // unique identifier - return new DcmUniqueIdentifier(key); - - case EVR_ST: // short text - return new DcmShortText(key); - - case EVR_LO: // long string - return new DcmLongString(key); - - case EVR_LT: // long text - return new DcmLongText(key); - - case EVR_UT: // unlimited text - return new DcmUnlimitedText(key); - - case EVR_SH: // short string - return new DcmShortString(key); - - case EVR_PN: // person name - return new DcmPersonName(key); - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - return new DcmSignedLong(key); - - case EVR_SS: // signed short - return new DcmSignedShort(key); - - case EVR_UL: // unsigned long - return new DcmUnsignedLong(key); - - case EVR_US: // unsigned short - return new DcmUnsignedShort(key); - - case EVR_FL: // float single-precision - return new DcmFloatingPointSingle(key); - - case EVR_FD: // float double-precision - return new DcmFloatingPointDouble(key); - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * TODO - **/ - - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - - /** - * Internal to DCMTK. - **/ - - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - default: - break; - } - - throw OrthancException(ErrorCode_InternalError); - } - - - - void FromDcmtkBridge::FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme, - Encoding dicomEncoding) - { - std::string binary; - const std::string* decoded = &utf8Value; - - if (decodeDataUriScheme && - boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) - { - std::string mime; - if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - decoded = &binary; - } - else if (dicomEncoding != Encoding_Utf8) - { - binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); - decoded = &binary; - } - - DcmTag key(tag.GetGroup(), tag.GetElement()); - - if (tag.IsPrivate() || - IsBinaryTag(key)) - { - if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) - { - return; - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - - bool ok = false; - - try - { - switch (key.getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - case EVR_UN: // unknown value representation - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * String types. - **/ - - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - { - ok = element.putString(decoded->c_str()).good(); - break; - } - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good(); - break; - } - - case EVR_SS: // signed short - { - ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good(); - break; - } - - case EVR_UL: // unsigned long - { - ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); - break; - } - - case EVR_US: // unsigned short - { - ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good(); - break; - } - - case EVR_FL: // float single-precision - { - ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); - break; - } - - case EVR_FD: // float double-precision - { - ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); - break; - } - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - { - ok = false; - break; - } - - - /** - * Internal to DCMTK. - **/ - - case EVR_ox: // OB or OW depending on context - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - default: - break; - } - } - catch (boost::bad_lexical_cast&) - { - ok = false; - } - - if (!ok) - { - LOG(ERROR) << "While creating a DICOM instance, tag (" << tag.Format() - << ") has out-of-range value: \"" << *decoded << "\""; - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, - const Json::Value& value, - bool decodeDataUriScheme, - Encoding dicomEncoding) - { - std::auto_ptr<DcmElement> element; - - switch (value.type()) - { - case Json::stringValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding); - break; - - case Json::nullValue: - element.reset(CreateElementForTag(tag)); - FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding); - break; - - case Json::arrayValue: - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - if (key.getEVR() != EVR_SQ) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key); - element.reset(sequence); - - for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) - { - std::auto_ptr<DcmItem> item(new DcmItem); - - Json::Value::Members members = value[i].getMemberNames(); - for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) - { - item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding)); - } - - sequence->append(item.release()); - } - - break; - } - - default: - throw OrthancException(ErrorCode_BadParameterType); - } - - return element.release(); - } - - - DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset) - { - DcmElement *element = NULL; - if (!dataset.findAndGetElement(DCM_PixelData, element).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); - DcmPixelSequence* pixelSequence = NULL; - if (!pixelData.getEncapsulatedRepresentation - (dataset.getOriginalXfer(), NULL, pixelSequence).good()) - { - return NULL; - } - else - { - return pixelSequence; - } - } - - - Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json, - Encoding defaultEncoding) - { - if (json.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - Encoding encoding = defaultEncoding; - - const Json::Value::Members tags = json.getMemberNames(); - - // Look for SpecificCharacterSet (0008,0005) in the JSON file - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); - if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - const Json::Value& value = json[tags[i]]; - if (value.type() != Json::stringValue || - (value.asString().length() != 0 && - !GetDicomEncoding(encoding, value.asCString()))) - { - LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value; - throw OrthancException(ErrorCode_BadRequest); - } - - if (value.asString().length() == 0) - { - return defaultEncoding; - } - } - } - - return encoding; - } - - - static void SetString(DcmDataset& target, - const DcmTag& tag, - const std::string& value) - { - if (!target.putAndInsertString(tag, value.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 - bool generateIdentifiers, - bool decodeDataUriScheme, - Encoding defaultEncoding) - { - std::auto_ptr<DcmDataset> result(new DcmDataset); - Encoding encoding = ExtractEncoding(json, defaultEncoding); - - SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); - - const Json::Value::Members tags = json.getMemberNames(); - - bool hasPatientId = false; - bool hasStudyInstanceUid = false; - bool hasSeriesInstanceUid = false; - bool hasSopInstanceUid = false; - - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); - const Json::Value& value = json[tags[i]]; - - if (tag == DICOM_TAG_PATIENT_ID) - { - hasPatientId = true; - } - else if (tag == DICOM_TAG_STUDY_INSTANCE_UID) - { - hasStudyInstanceUid = true; - } - else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - hasSeriesInstanceUid = true; - } - else if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - hasSopInstanceUid = true; - } - - if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); - const DcmTagKey& tag = element->getTag(); - - result->findAndDeleteElement(tag); - - DcmElement* tmp = element.release(); - if (!result->insert(tmp, false, false).good()) - { - delete tmp; - throw OrthancException(ErrorCode_InternalError); - } - } - } - - if (!hasPatientId && - generateIdentifiers) - { - SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient)); - } - - if (!hasStudyInstanceUid && - generateIdentifiers) - { - SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study)); - } - - if (!hasSeriesInstanceUid && - generateIdentifiers) - { - SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series)); - } - - if (!hasSopInstanceUid && - generateIdentifiers) - { - SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance)); - } - - return result.release(); - } - - - DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer, - size_t size) - { - DcmInputBufferStream is; - if (size > 0) - { - is.setBuffer(buffer, size); - } - is.setEos(); - - std::auto_ptr<DcmFileFormat> result(new DcmFileFormat); - - result->transferInit(); - if (!result->read(is).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - result->loadAllDataIntoMemory(); - result->transferEnd(); - - return result.release(); - } - - - void FromDcmtkBridge::FromJson(DicomMap& target, - const Json::Value& source) - { - if (source.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - target.Clear(); - - Json::Value::Members members = source.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& value = source[members[i]]; - - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - target.SetValue(ParseTag(members[i]), value.asString(), false); - } - } - - - void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset, - Encoding source, - Encoding target) - { - // Recursive exploration of a dataset to change the encoding of - // each string-like element - - if (source == target) - { - return; - } - - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - if (element) - { - if (element->isLeaf()) - { - char *c = NULL; - if (element->isaString() && - element->getString(c).good() && - c != NULL) - { - std::string a = Toolbox::ConvertToUtf8(c, source); - std::string b = Toolbox::ConvertFromUtf8(a, target); - element->putString(b.c_str()); - } - } - else - { - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following dynamic_cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); - - for (unsigned long j = 0; j < sequence.card(); j++) - { - ChangeStringEncoding(*sequence.getItem(j), source, target); - } - } - } - } - } - - - bool FromDcmtkBridge::LookupTransferSyntax(std::string& result, - DcmFileFormat& dicom) - { - const char* value = NULL; - - if (dicom.getMetaInfo() != NULL && - dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && - value != NULL) - { - result.assign(value); - return true; - } - else - { - return false; - } - } - - -#if ORTHANC_ENABLE_LUA == 1 - void FromDcmtkBridge::ExecuteToDicom(DicomMap& target, - LuaFunctionCall& call) - { - Json::Value output; - call.ExecuteToJson(output, true /* keep strings */); - - target.Clear(); - - if (output.type() == Json::arrayValue && - output.size() == 0) - { - // This case happens for empty tables - return; - } - - if (output.type() != Json::objectValue) - { - LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table"; - throw OrthancException(ErrorCode_LuaBadOutput); - } - - Json::Value::Members members = output.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - if (output[members[i]].type() != Json::stringValue) - { - LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table mapping names of DICOM tags to strings"; - throw OrthancException(ErrorCode_LuaBadOutput); - } - - DicomTag tag(ParseTag(members[i])); - target.SetValue(tag, output[members[i]].asString(), false); - } - } -#endif - - - void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, - DcmItem& dataset) - { - ExtractDicomSummary(target, dataset, - ORTHANC_MAXIMUM_TAG_LENGTH, - GetDefaultDicomEncoding()); - } - - - void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset) - { - ExtractDicomAsJson(target, dataset, - DicomToJsonFormat_Full, - DicomToJsonFlags_Default, - ORTHANC_MAXIMUM_TAG_LENGTH, - GetDefaultDicomEncoding()); - } - - - void FromDcmtkBridge::InitializeCodecs() - { -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - LOG(WARNING) << "Registering JPEG Lossless codecs in DCMTK"; - DJLSDecoderRegistration::registerCodecs(); -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - LOG(WARNING) << "Registering JPEG codecs in DCMTK"; - DJDecoderRegistration::registerCodecs(); -#endif - } - - - void FromDcmtkBridge::FinalizeCodecs() - { -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - // Unregister JPEG-LS codecs - DJLSDecoderRegistration::cleanup(); -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - // Unregister JPEG codecs - DJDecoderRegistration::cleanup(); -#endif - } -}
--- a/OrthancServer/FromDcmtkBridge.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "ServerEnumerations.h" - -#include "../Core/DicomFormat/DicomElement.h" -#include "../Core/DicomFormat/DicomMap.h" - -#include <dcmtk/dcmdata/dcdatset.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcpixseq.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <json/json.h> - -#if !defined(ORTHANC_ENABLE_LUA) -# error The macro ORTHANC_ENABLE_LUA must be defined -#endif - -#if ORTHANC_ENABLE_DCMTK != 1 -# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 -#endif - -#if ORTHANC_BUILD_UNIT_TESTS == 1 -# include <gtest/gtest_prod.h> -#endif - -#if ORTHANC_ENABLE_LUA == 1 -# include "../Core/Lua/LuaFunctionCall.h" -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined -#endif - - -namespace Orthanc -{ - class FromDcmtkBridge : public boost::noncopyable - { -#if ORTHANC_BUILD_UNIT_TESTS == 1 - FRIEND_TEST(FromDcmtkBridge, FromJson); -#endif - - friend class ParsedDicomFile; - - private: - FromDcmtkBridge(); // Pure static class - - static void ExtractDicomSummary(DicomMap& target, - DcmItem& dataset, - unsigned int maxStringLength, - Encoding defaultEncoding); - - static void DatasetToJson(Json::Value& parent, - DcmItem& item, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding); - - static void ElementToJson(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding dicomEncoding); - - static void ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding defaultEncoding); - - static void ChangeStringEncoding(DcmItem& dataset, - Encoding source, - Encoding target); - - public: - static void InitializeDictionary(bool loadPrivateDictionary); - - static void RegisterDictionaryTag(const DicomTag& tag, - ValueRepresentation vr, - const std::string& name, - unsigned int minMultiplicity, - unsigned int maxMultiplicity, - const std::string& privateCreator); - - static Encoding DetectEncoding(DcmItem& dataset, - Encoding defaultEncoding); - - static DicomTag Convert(const DcmTag& tag); - - static DicomTag GetTag(const DcmElement& element); - - static bool IsUnknownTag(const DicomTag& tag); - - static DicomValue* ConvertLeafElement(DcmElement& element, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding); - - static void ExtractHeaderAsJson(Json::Value& target, - DcmMetaInfo& header, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); - - static std::string GetTagName(const DicomTag& tag, - const std::string& privateCreator); - - static std::string GetTagName(const DcmElement& element); - - static std::string GetTagName(const DicomElement& element) - { - return GetTagName(element.GetTag(), ""); - } - - static DicomTag ParseTag(const char* name); - - static DicomTag ParseTag(const std::string& name) - { - return ParseTag(name.c_str()); - } - - static bool HasTag(const DicomMap& fields, - const std::string& tagName) - { - return fields.HasTag(ParseTag(tagName)); - } - - static const DicomValue& GetValue(const DicomMap& fields, - const std::string& tagName) - { - return fields.GetValue(ParseTag(tagName)); - } - - static void SetValue(DicomMap& target, - const std::string& tagName, - DicomValue* value) - { - target.SetValue(ParseTag(tagName), value); - } - - static void ToJson(Json::Value& result, - const DicomMap& values, - bool simplify); - - static std::string GenerateUniqueIdentifier(ResourceType level); - - static bool SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet); - - static ValueRepresentation Convert(DcmEVR vr); - - static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); - - static DcmElement* CreateElementForTag(const DicomTag& tag); - - static void FillElementWithString(DcmElement& element, - const DicomTag& tag, - const std::string& utf8alue, // Encoded using UTF-8 - bool decodeDataUriScheme, - Encoding dicomEncoding); - - static DcmElement* FromJson(const DicomTag& tag, - const Json::Value& element, // Encoded using UTF-8 - bool decodeDataUriScheme, - Encoding dicomEncoding); - - static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset); - - static Encoding ExtractEncoding(const Json::Value& json, - Encoding defaultEncoding); - - static DcmDataset* FromJson(const Json::Value& json, // Encoded using UTF-8 - bool generateIdentifiers, - bool decodeDataUriScheme, - Encoding defaultEncoding); - - static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer, - size_t size); - - static void FromJson(DicomMap& values, - const Json::Value& result); - - static bool LookupTransferSyntax(std::string& result, - DcmFileFormat& dicom); - -#if ORTHANC_ENABLE_LUA == 1 - static void ExecuteToDicom(DicomMap& target, - LuaFunctionCall& call); -#endif - - static void ExtractDicomSummary(DicomMap& target, - DcmItem& dataset); - - static void ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset); - - static void InitializeCodecs(); - - static void FinalizeCodecs(); - }; -}
--- a/OrthancServer/Internals/CommandDispatcher.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,934 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "CommandDispatcher.h" - -#include "FindScp.h" -#include "StoreScp.h" -#include "MoveScp.h" -#include "../../Core/Toolbox.h" -#include "../../Core/Logging.h" - -#include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ -#include <boost/lexical_cast.hpp> - -static OFBool opt_rejectWithoutImplementationUID = OFFalse; - - - -static DUL_PRESENTATIONCONTEXT * -findPresentationContextID(LST_HEAD * head, - T_ASC_PresentationContextID presentationContextID) -{ - DUL_PRESENTATIONCONTEXT *pc; - LST_HEAD **l; - OFBool found = OFFalse; - - if (head == NULL) - return NULL; - - l = &head; - if (*l == NULL) - return NULL; - - pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); - (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); - - while (pc && !found) { - if (pc->presentationContextID == presentationContextID) { - found = OFTrue; - } else { - pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); - } - } - return pc; -} - - -/** accept all presenstation contexts for unknown SOP classes, - * i.e. UIDs appearing in the list of abstract syntaxes - * where no corresponding name is defined in the UID dictionary. - * @param params pointer to association parameters structure - * @param transferSyntax transfer syntax to accept - * @param acceptedRole SCU/SCP role to accept - */ -static OFCondition acceptUnknownContextsWithTransferSyntax( - T_ASC_Parameters * params, - const char* transferSyntax, - T_ASC_SC_ROLE acceptedRole) -{ - OFCondition cond = EC_Normal; - int n, i, k; - DUL_PRESENTATIONCONTEXT *dpc; - T_ASC_PresentationContext pc; - OFBool accepted = OFFalse; - OFBool abstractOK = OFFalse; - - n = ASC_countPresentationContexts(params); - for (i = 0; i < n; i++) - { - cond = ASC_getPresentationContext(params, i, &pc); - if (cond.bad()) return cond; - abstractOK = OFFalse; - accepted = OFFalse; - - if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) - { - abstractOK = OFTrue; - - /* check the transfer syntax */ - for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) - { - if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) - { - accepted = OFTrue; - } - } - } - - if (accepted) - { - cond = ASC_acceptPresentationContext( - params, pc.presentationContextID, - transferSyntax, acceptedRole); - if (cond.bad()) return cond; - } else { - T_ASC_P_ResultReason reason; - - /* do not refuse if already accepted */ - dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, - pc.presentationContextID); - if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) - { - - if (abstractOK) { - reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; - } else { - reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; - } - /* - * If previously this presentation context was refused - * because of bad transfer syntax let it stay that way. - */ - if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) - reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; - - cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); - if (cond.bad()) return cond; - } - } - } - return EC_Normal; -} - - -/** accept all presenstation contexts for unknown SOP classes, - * i.e. UIDs appearing in the list of abstract syntaxes - * where no corresponding name is defined in the UID dictionary. - * This method is passed a list of "preferred" transfer syntaxes. - * @param params pointer to association parameters structure - * @param transferSyntax transfer syntax to accept - * @param acceptedRole SCU/SCP role to accept - */ -static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( - T_ASC_Parameters * params, - const char* transferSyntaxes[], int transferSyntaxCount, - T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT) -{ - OFCondition cond = EC_Normal; - /* - ** Accept in the order "least wanted" to "most wanted" transfer - ** syntax. Accepting a transfer syntax will override previously - ** accepted transfer syntaxes. - */ - for (int i = transferSyntaxCount - 1; i >= 0; i--) - { - cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); - if (cond.bad()) return cond; - } - return cond; -} - - - -namespace Orthanc -{ - namespace Internals - { - /** - * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0 - * (dcmAllStorageSOPClassUIDs). - * - * an array of const strings containing all known Storage SOP - * Classes that fit into the conventional - * PATIENT-STUDY-SERIES-INSTANCE information model, - * i.e. everything a Storage SCP might want to store in a PACS. - * Special cases such as hanging protocol storage or the Storage - * SOP Class are not included in this list. - * - * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED - * ONES AND IS LARGER THAN 64 ENTRIES. - */ - - const char* orthancStorageSOPClassUIDs[] = - { - UID_AmbulatoryECGWaveformStorage, - UID_ArterialPulseWaveformStorage, - UID_AutorefractionMeasurementsStorage, - UID_BasicStructuredDisplayStorage, - UID_BasicTextSRStorage, - UID_BasicVoiceAudioWaveformStorage, - UID_BlendingSoftcopyPresentationStateStorage, - UID_BreastTomosynthesisImageStorage, - UID_CardiacElectrophysiologyWaveformStorage, - UID_ChestCADSRStorage, - UID_ColonCADSRStorage, - UID_ColorSoftcopyPresentationStateStorage, - UID_ComprehensiveSRStorage, - UID_ComputedRadiographyImageStorage, - UID_CTImageStorage, - UID_DeformableSpatialRegistrationStorage, - UID_DigitalIntraOralXRayImageStorageForPresentation, - UID_DigitalIntraOralXRayImageStorageForProcessing, - UID_DigitalMammographyXRayImageStorageForPresentation, - UID_DigitalMammographyXRayImageStorageForProcessing, - UID_DigitalXRayImageStorageForPresentation, - UID_DigitalXRayImageStorageForProcessing, - UID_EncapsulatedCDAStorage, - UID_EncapsulatedPDFStorage, - UID_EnhancedCTImageStorage, - UID_EnhancedMRColorImageStorage, - UID_EnhancedMRImageStorage, - UID_EnhancedPETImageStorage, - UID_EnhancedSRStorage, - UID_EnhancedUSVolumeStorage, - UID_EnhancedXAImageStorage, - UID_EnhancedXRFImageStorage, - UID_GeneralAudioWaveformStorage, - UID_GeneralECGWaveformStorage, - UID_GenericImplantTemplateStorage, - UID_GrayscaleSoftcopyPresentationStateStorage, - UID_HemodynamicWaveformStorage, - UID_ImplantAssemblyTemplateStorage, - UID_ImplantationPlanSRDocumentStorage, - UID_ImplantTemplateGroupStorage, - UID_IntraocularLensCalculationsStorage, - UID_KeratometryMeasurementsStorage, - UID_KeyObjectSelectionDocumentStorage, - UID_LensometryMeasurementsStorage, - UID_MacularGridThicknessAndVolumeReportStorage, - UID_MammographyCADSRStorage, - UID_MRImageStorage, - UID_MRSpectroscopyStorage, - UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage, - UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage, - UID_MultiframeSingleBitSecondaryCaptureImageStorage, - UID_MultiframeTrueColorSecondaryCaptureImageStorage, - UID_NuclearMedicineImageStorage, - UID_OphthalmicAxialMeasurementsStorage, - UID_OphthalmicPhotography16BitImageStorage, - UID_OphthalmicPhotography8BitImageStorage, - UID_OphthalmicTomographyImageStorage, - UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage, - UID_PositronEmissionTomographyImageStorage, - UID_ProcedureLogStorage, - UID_PseudoColorSoftcopyPresentationStateStorage, - UID_RawDataStorage, - UID_RealWorldValueMappingStorage, - UID_RespiratoryWaveformStorage, - UID_RTBeamsTreatmentRecordStorage, - UID_RTBrachyTreatmentRecordStorage, - UID_RTDoseStorage, - UID_RTImageStorage, - UID_RTIonBeamsTreatmentRecordStorage, - UID_RTIonPlanStorage, - UID_RTPlanStorage, - UID_RTStructureSetStorage, - UID_RTTreatmentSummaryRecordStorage, - UID_SecondaryCaptureImageStorage, - UID_SegmentationStorage, - UID_SpatialFiducialsStorage, - UID_SpatialRegistrationStorage, - UID_SpectaclePrescriptionReportStorage, - UID_StereometricRelationshipStorage, - UID_SubjectiveRefractionMeasurementsStorage, - UID_SurfaceSegmentationStorage, - UID_TwelveLeadECGWaveformStorage, - UID_UltrasoundImageStorage, - UID_UltrasoundMultiframeImageStorage, - UID_VideoEndoscopicImageStorage, - UID_VideoMicroscopicImageStorage, - UID_VideoPhotographicImageStorage, - UID_VisualAcuityMeasurementsStorage, - UID_VLEndoscopicImageStorage, - UID_VLMicroscopicImageStorage, - UID_VLPhotographicImageStorage, - UID_VLSlideCoordinatesMicroscopicImageStorage, - UID_VLWholeSlideMicroscopyImageStorage, - UID_XAXRFGrayscaleSoftcopyPresentationStateStorage, - UID_XRay3DAngiographicImageStorage, - UID_XRay3DCraniofacialImageStorage, - UID_XRayAngiographicImageStorage, - UID_XRayRadiationDoseSRStorage, - UID_XRayRadiofluoroscopicImageStorage, - // retired - UID_RETIRED_HardcopyColorImageStorage, - UID_RETIRED_HardcopyGrayscaleImageStorage, - UID_RETIRED_NuclearMedicineImageStorage, - UID_RETIRED_StandaloneCurveStorage, - UID_RETIRED_StandaloneModalityLUTStorage, - UID_RETIRED_StandaloneOverlayStorage, - UID_RETIRED_StandalonePETCurveStorage, - UID_RETIRED_StandaloneVOILUTStorage, - UID_RETIRED_StoredPrintStorage, - UID_RETIRED_UltrasoundImageStorage, - UID_RETIRED_UltrasoundMultiframeImageStorage, - UID_RETIRED_VLImageStorage, - UID_RETIRED_VLMultiFrameImageStorage, - UID_RETIRED_XRayAngiographicBiPlaneImageStorage, - // draft - UID_DRAFT_SRAudioStorage, - UID_DRAFT_SRComprehensiveStorage, - UID_DRAFT_SRDetailStorage, - UID_DRAFT_SRTextStorage, - UID_DRAFT_WaveformStorage, - UID_DRAFT_RTBeamsDeliveryInstructionStorage, - NULL - }; - - const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1; - - - - OFCondition AssociationCleanup(T_ASC_Association *assoc) - { - OFCondition cond = ASC_dropSCPAssociation(assoc); - if (cond.bad()) - { - LOG(FATAL) << cond.text(); - return cond; - } - - cond = ASC_destroyAssociation(&assoc); - if (cond.bad()) - { - LOG(FATAL) << cond.text(); - return cond; - } - - return cond; - } - - - - CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) - { - DcmAssociationConfiguration asccfg; - char buf[BUFSIZ]; - T_ASC_Association *assoc; - OFCondition cond; - OFString sprofile; - OFString temp_str; - - std::vector<const char*> knownAbstractSyntaxes; - - // For C-STORE - if (server.HasStoreRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); - } - - // For C-FIND - if (server.HasFindRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - } - - if (server.HasWorklistRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); - } - - // For C-MOVE - if (server.HasMoveRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); - } - - cond = ASC_receiveAssociation(net, &assoc, - /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, - NULL, NULL, - /*opt_secureConnection*/ OFFalse, - DUL_NOBLOCK, 1); - - if (cond == DUL_NOASSOCIATIONREQUEST) - { - // Timeout - AssociationCleanup(assoc); - return NULL; - } - - // if some kind of error occured, take care of it - if (cond.bad()) - { - LOG(ERROR) << "Receiving Association failed: " << cond.text(); - // no matter what kind of error occurred, we need to do a cleanup - AssociationCleanup(assoc); - return NULL; - } - - // Retrieve the AET and the IP address of the remote modality - std::string remoteAet; - std::string remoteIp; - std::string calledAet; - - { - DIC_AE remoteAet_C; - DIC_AE calledAet_C; - DIC_AE remoteIp_C; - DIC_AE calledIP_C; - if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() || - ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad()) - { - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C)); - remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C)); - calledAet = (/*OFSTRING_GUARD*/(calledAet_C)); - } - - LOG(INFO) << "Association Received from AET " << remoteAet - << " on IP " << remoteIp; - - - std::vector<const char*> transferSyntaxes; - - // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 - transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); - transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); - transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); - - // New transfer syntaxes supported since Orthanc 0.7.2 - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) - { - transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) - { - transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); - transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); - transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) - { - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) - { - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) - { - transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); - transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) - { - transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); - transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) - { - transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); - } - - /* accept the Verification SOP Class if presented */ - cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - - /* the array of Storage SOP Class UIDs comes from dcuid.h */ - cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) - { - /* - * Promiscous mode is enabled: Accept everything not known not - * to be a storage SOP class. - **/ - cond = acceptUnknownContextsWithPreferredTransferSyntaxes( - assoc->params, &transferSyntaxes[0], transferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - } - - /* set our app title */ - ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); - - /* acknowledge or reject this association */ - cond = ASC_getApplicationContextName(assoc->params, buf); - if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0) - { - /* reject: the application context name is not supported */ - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED - }; - - LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf; - cond = ASC_rejectAssociation(assoc, &rej); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - } - AssociationCleanup(assoc); - return NULL; - } - - /* check the AETs */ - if (!server.IsMyAETitle(calledAet)) - { - LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")"; - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (server.HasApplicationEntityFilter() && - !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet)) - { - LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp; - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (opt_rejectWithoutImplementationUID && - strlen(assoc->params->theirImplementationClassUID) == 0) - { - /* reject: the no implementation Class UID provided */ - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - - LOG(INFO) << "Association Rejected: No Implementation Class UID provided"; - cond = ASC_rejectAssociation(assoc, &rej); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - } - AssociationCleanup(assoc); - return NULL; - } - - { - cond = ASC_acknowledgeAssociation(assoc); - if (cond.bad()) - { - LOG(ERROR) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"; - if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) - LOG(INFO) << " (but no valid presentation contexts)"; - } - - IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; - return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter); - } - - - CommandDispatcher::CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - IApplicationEntityFilter* filter) : - server_(server), - assoc_(assoc), - remoteIp_(remoteIp), - remoteAet_(remoteAet), - calledAet_(calledAet), - filter_(filter) - { - associationTimeout_ = server.GetAssociationTimeout(); - elapsedTimeSinceLastCommand_ = 0; - } - - - CommandDispatcher::~CommandDispatcher() - { - try - { - AssociationCleanup(assoc_); - } - catch (...) - { - LOG(ERROR) << "Some association was not cleanly aborted"; - } - } - - - bool CommandDispatcher::Step() - /* - * This function receives DIMSE commmands over the network connection - * and handles these commands correspondingly. Note that in case of - * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed. - */ - { - bool finished = false; - - // receive a DIMSE command over the network, with a timeout of 1 second - DcmDataset *statusDetail = NULL; - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message msg; - - OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail); - elapsedTimeSinceLastCommand_++; - - // if the command which was received has extra status - // detail information, dump this information - if (statusDetail != NULL) - { - //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail)); - delete statusDetail; - } - - if (cond == DIMSE_OUTOFRESOURCES) - { - finished = true; - } - else if (cond == DIMSE_NODATAAVAILABLE) - { - // Timeout due to DIMSE_NONBLOCKING - if (associationTimeout_ != 0 && - elapsedTimeSinceLastCommand_ >= associationTimeout_) - { - // This timeout is actually a association timeout - finished = true; - } - } - else if (cond == EC_Normal) - { - // Reset the association timeout counter - elapsedTimeSinceLastCommand_ = 0; - - // Convert the type of request to Orthanc's internal type - bool supported = false; - DicomRequestType request; - switch (msg.CommandField) - { - case DIMSE_C_ECHO_RQ: - request = DicomRequestType_Echo; - supported = true; - break; - - case DIMSE_C_STORE_RQ: - request = DicomRequestType_Store; - supported = true; - break; - - case DIMSE_C_MOVE_RQ: - request = DicomRequestType_Move; - supported = true; - break; - - case DIMSE_C_FIND_RQ: - request = DicomRequestType_Find; - supported = true; - break; - - default: - // we cannot handle this kind of message - cond = DIMSE_BADCOMMANDTYPE; - LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; - break; - } - - - // Check whether this request is allowed by the security filter - if (supported && - filter_ != NULL && - !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request)) - { - LOG(WARNING) << "Rejected " << EnumerationToString(request) - << " request from remote DICOM modality with AET \"" - << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\""; - cond = DIMSE_ILLEGALASSOCIATION; - supported = false; - finished = true; - } - - // in case we received a supported message, process this command - if (supported) - { - // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer - cond = DIMSE_BADCOMMANDTYPE; - - switch (request) - { - case DicomRequestType_Echo: - cond = EchoScp(assoc_, &msg, presID); - break; - - case DicomRequestType_Store: - if (server_.HasStoreRequestHandlerFactory()) // Should always be true - { - std::auto_ptr<IStoreRequestHandler> handler - (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); - - if (handler.get() != NULL) - { - cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_); - } - } - break; - - case DicomRequestType_Move: - if (server_.HasMoveRequestHandlerFactory()) // Should always be true - { - std::auto_ptr<IMoveRequestHandler> handler - (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); - - if (handler.get() != NULL) - { - cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_); - } - } - break; - - case DicomRequestType_Find: - if (server_.HasFindRequestHandlerFactory() || // Should always be true - server_.HasWorklistRequestHandlerFactory()) - { - std::auto_ptr<IFindRequestHandler> findHandler; - if (server_.HasFindRequestHandlerFactory()) - { - findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); - } - - std::auto_ptr<IWorklistRequestHandler> worklistHandler; - if (server_.HasWorklistRequestHandlerFactory()) - { - worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler()); - } - - cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(), - findHandler.get(), worklistHandler.get(), - remoteIp_, remoteAet_, calledAet_); - } - break; - - default: - // Should never happen - break; - } - } - } - else - { - // Bad status, which indicates the closing of the connection by - // the peer or a network error - finished = true; - - LOG(INFO) << cond.text(); - } - - if (finished) - { - if (cond == DUL_PEERREQUESTEDRELEASE) - { - LOG(INFO) << "Association Release"; - ASC_acknowledgeRelease(assoc_); - } - else if (cond == DUL_PEERABORTEDASSOCIATION) - { - LOG(INFO) << "Association Aborted"; - } - else - { - OFString temp_str; - LOG(INFO) << "DIMSE failure (aborting association): " << cond.text(); - /* some kind of error so abort the association */ - ASC_abortAssociation(assoc_); - } - } - - return !finished; - } - - - OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) - { - OFString temp_str; - LOG(INFO) << "Received Echo Request"; - //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); - - /* the echo succeeded !! */ - OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); - if (cond.bad()) - { - LOG(ERROR) << "Echo SCP Failed: " << cond.text(); - } - return cond; - } - } -}
--- a/OrthancServer/Internals/CommandDispatcher.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/DicomServer.h" -#include "../../Core/MultiThreading/IRunnableBySteps.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition AssociationCleanup(T_ASC_Association *assoc); - - class CommandDispatcher : public IRunnableBySteps - { - private: - uint32_t associationTimeout_; - uint32_t elapsedTimeSinceLastCommand_; - const DicomServer& server_; - T_ASC_Association* assoc_; - std::string remoteIp_; - std::string remoteAet_; - std::string calledAet_; - IApplicationEntityFilter* filter_; - - public: - CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - IApplicationEntityFilter* filter); - - virtual ~CommandDispatcher(); - - virtual bool Step(); - }; - - OFCondition EchoScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID); - - CommandDispatcher* AcceptAssociation(const DicomServer& server, - T_ASC_Network *net); - } -}
--- a/OrthancServer/Internals/DicomFrameIndex.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,439 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomFrameIndex.h" - -#include "../../Core/OrthancException.h" -#include "../../Core/DicomFormat/DicomImageInformation.h" -#include "../FromDcmtkBridge.h" -#include "../../Core/Endianness.h" -#include "DicomImageDecoder.h" - -#include <boost/lexical_cast.hpp> - -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcpxitem.h> -#include <dcmtk/dcmdata/dcpixseq.h> - -namespace Orthanc -{ - class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex - { - private: - DcmPixelSequence* pixelSequence_; - std::vector<DcmPixelItem*> startFragment_; - std::vector<unsigned int> countFragments_; - std::vector<unsigned int> frameSize_; - - void GetOffsetTable(std::vector<uint32_t>& table) - { - DcmPixelItem* item = NULL; - if (!pixelSequence_->getItem(item, 0).good() || - item == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - uint32_t length = item->getLength(); - if (length == 0) - { - table.clear(); - return; - } - - if (length % 4 != 0) - { - // Error: Each fragment is index with 4 bytes (uint32_t) - throw OrthancException(ErrorCode_BadFileFormat); - } - - uint8_t* content = NULL; - if (!item->getUint8Array(content).good() || - content == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - table.resize(length / 4); - - // The offset table is always in little endian in the DICOM - // file. Swap it to host endianness if needed. - const uint32_t* offset = reinterpret_cast<const uint32_t*>(content); - for (size_t i = 0; i < table.size(); i++, offset++) - { - table[i] = le32toh(*offset); - } - } - - - public: - FragmentIndex(DcmPixelSequence* pixelSequence, - unsigned int countFrames) : - pixelSequence_(pixelSequence) - { - assert(pixelSequence != NULL); - - startFragment_.resize(countFrames); - countFragments_.resize(countFrames); - frameSize_.resize(countFrames); - - // The first fragment corresponds to the offset table - unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card()); - if (countFragments < countFrames + 1) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (countFragments == countFrames + 1) - { - // Simple case: There is one fragment per frame. - - DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table - if (fragment == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - for (unsigned int i = 0; i < countFrames; i++) - { - fragment = pixelSequence_->nextInContainer(fragment); - startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment); - frameSize_[i] = fragment->getLength(); - countFragments_[i] = 1; - } - - return; - } - - // Parse the offset table - std::vector<uint32_t> offsetOfFrame; - GetOffsetTable(offsetOfFrame); - - if (offsetOfFrame.size() != countFrames || - offsetOfFrame[0] != 0) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - - // Loop over the fragments (ignoring the offset table). This is - // an alternative, faster implementation to DCMTK's - // "DcmCodec::determineStartFragment()". - DcmObject* fragment = pixelSequence_->nextInContainer(NULL); - if (fragment == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table - if (fragment == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - uint32_t offset = 0; - unsigned int currentFrame = 0; - startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment); - - unsigned int currentFragment = 1; - while (fragment != NULL) - { - if (currentFrame + 1 < countFrames && - offset == offsetOfFrame[currentFrame + 1]) - { - currentFrame += 1; - startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment); - } - - frameSize_[currentFrame] += fragment->getLength(); - countFragments_[currentFrame]++; - - // 8 bytes = overhead for the item tag and length field - offset += fragment->getLength() + 8; - - currentFragment++; - fragment = pixelSequence_->nextInContainer(fragment); - } - - if (currentFragment != countFragments || - currentFrame + 1 != countFrames || - fragment != NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - assert(startFragment_.size() == countFragments_.size() && - startFragment_.size() == frameSize_.size()); - } - - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const - { - if (index >= startFragment_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - frame.resize(frameSize_[index]); - if (frame.size() == 0) - { - return; - } - - uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]); - - size_t offset = 0; - DcmPixelItem* fragment = startFragment_[index]; - for (unsigned int i = 0; i < countFragments_[index]; i++) - { - uint8_t* content = NULL; - if (!fragment->getUint8Array(content).good() || - content == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - assert(offset + fragment->getLength() <= frame.size()); - - memcpy(target + offset, content, fragment->getLength()); - offset += fragment->getLength(); - - fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); - } - } - }; - - - - class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex - { - private: - uint8_t* pixelData_; - size_t frameSize_; - - public: - UncompressedIndex(DcmDataset& dataset, - unsigned int countFrames, - size_t frameSize) : - pixelData_(NULL), - frameSize_(frameSize) - { - size_t size = 0; - - DcmElement* e; - if (dataset.findAndGetElement(DCM_PixelData, e).good() && - e != NULL) - { - size = e->getLength(); - - if (size > 0) - { - pixelData_ = NULL; - if (!e->getUint8Array(pixelData_).good() || - pixelData_ == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - } - - if (size < frameSize_ * countFrames) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const - { - frame.resize(frameSize_); - if (frameSize_ > 0) - { - memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); - } - } - }; - - - class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex - { - private: - std::string pixelData_; - size_t frameSize_; - - public: - PsmctRle1Index(DcmDataset& dataset, - unsigned int countFrames, - size_t frameSize) : - frameSize_(frameSize) - { - if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) || - pixelData_.size() < frameSize * countFrames) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const - { - frame.resize(frameSize_); - if (frameSize_ > 0) - { - memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); - } - } - }; - - - - bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom) - { - // Retrieve the transfer syntax from the DICOM header - const char* value = NULL; - if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() || - value == NULL) - { - return false; - } - - const std::string transferSyntax(value); - - // Video standards supported in DICOM 2016a - // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html - if (transferSyntax == "1.2.840.10008.1.2.4.100" || // MPEG2 MP@ML option of ISO/IEC MPEG2 - transferSyntax == "1.2.840.10008.1.2.4.101" || // MPEG2 MP@HL option of ISO/IEC MPEG2 - transferSyntax == "1.2.840.10008.1.2.4.102" || // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.103" || // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.104" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.105" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 - transferSyntax == "1.2.840.10008.1.2.4.106") // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264 - { - return true; - } - - return false; - } - - - unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom) - { - // Assume 1 frame for video transfer syntaxes - if (IsVideo(dicom)) - { - return 1; - } - - const char* tmp = NULL; - if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() || - tmp == NULL) - { - return 1; - } - - int count = -1; - try - { - count = boost::lexical_cast<int>(tmp); - } - catch (boost::bad_lexical_cast&) - { - } - - if (count < 0) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - return count; - } - } - - - DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom) - { - countFrames_ = GetFramesCount(dicom); - if (countFrames_ == 0) - { - // The image has no frame. No index is to be built. - return; - } - - DcmDataset& dataset = *dicom.getDataset(); - - // Test whether this image is composed of a sequence of fragments - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); - if (pixelSequence != NULL) - { - index_.reset(new FragmentIndex(pixelSequence, countFrames_)); - return; - } - - // Extract information about the image structure - DicomMap tags; - FromDcmtkBridge::ExtractDicomSummary(tags, dataset); - - DicomImageInformation information(tags); - - // Access to the raw pixel data - if (DicomImageDecoder::IsPsmctRle1(dataset)) - { - index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize())); - } - else - { - index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize())); - } - } - - - void DicomFrameIndex::GetRawFrame(std::string& frame, - unsigned int index) const - { - if (index >= countFrames_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else if (index_.get() != NULL) - { - return index_->GetRawFrame(frame, index); - } - else - { - frame.clear(); - } - } -}
--- a/OrthancServer/Internals/DicomFrameIndex.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../../Core/Enumerations.h" - -#include <dcmtk/dcmdata/dcdatset.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <vector> -#include <stdint.h> -#include <boost/noncopyable.hpp> -#include <memory> - -namespace Orthanc -{ - class DicomFrameIndex - { - private: - class IIndex : public boost::noncopyable - { - public: - virtual ~IIndex() - { - } - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const = 0; - }; - - class FragmentIndex; - class UncompressedIndex; - class PsmctRle1Index; - - std::auto_ptr<IIndex> index_; - unsigned int countFrames_; - - public: - DicomFrameIndex(DcmFileFormat& dicom); - - unsigned int GetFramesCount() const - { - return countFrames_; - } - - void GetRawFrame(std::string& frame, - unsigned int index) const; - - static bool IsVideo(DcmFileFormat& dicom); - - static unsigned int GetFramesCount(DcmFileFormat& dicom); - }; -}
--- a/OrthancServer/Internals/DicomImageDecoder.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,817 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "../PrecompiledHeadersServer.h" -#include "DicomImageDecoder.h" - - -/*========================================================================= - - This file is based on portions of the following project - (cf. function "DecodePsmctRle1()"): - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - - Copyright (c) 2006-2011 Mathieu Malaterre - Copyright (c) 1993-2005 CREATIS - (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - =========================================================================*/ - - -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" -#include "../../Core/Images/Image.h" -#include "../../Core/Images/ImageProcessing.h" -#include "../../Core/DicomFormat/DicomIntegerPixelAccessor.h" -#include "../ToDcmtkBridge.h" -#include "../FromDcmtkBridge.h" -#include "../ParsedDicomFile.h" - -#if ORTHANC_ENABLE_PNG == 1 -# include "../../Core/Images/PngWriter.h" -#endif - -#if ORTHANC_ENABLE_JPEG == 1 -# include "../../Core/Images/JpegWriter.h" -#endif - -#include <boost/lexical_cast.hpp> - -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcrleccd.h> -#include <dcmtk/dcmdata/dcrlecp.h> - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 -# include <dcmtk/dcmjpls/djcodecd.h> -# include <dcmtk/dcmjpls/djcparam.h> -# include <dcmtk/dcmjpeg/djrplol.h> -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 -# include <dcmtk/dcmjpeg/djcodecd.h> -# include <dcmtk/dcmjpeg/djcparam.h> -# include <dcmtk/dcmjpeg/djdecbas.h> -# include <dcmtk/dcmjpeg/djdecext.h> -# include <dcmtk/dcmjpeg/djdeclol.h> -# include <dcmtk/dcmjpeg/djdecpro.h> -# include <dcmtk/dcmjpeg/djdecsps.h> -# include <dcmtk/dcmjpeg/djdecsv1.h> -#endif - -#if DCMTK_VERSION_NUMBER <= 360 -# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax -# define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax -# define EXS_JPEGProcess6_8 EXS_JPEGProcess6_8TransferSyntax -# define EXS_JPEGProcess10_12 EXS_JPEGProcess10_12TransferSyntax -# define EXS_JPEGProcess14 EXS_JPEGProcess14TransferSyntax -# define EXS_JPEGProcess14SV1 EXS_JPEGProcess14SV1TransferSyntax -#endif - -namespace Orthanc -{ - static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); - static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); - - - bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset) - { - DcmElement* e; - char* c; - - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || - e == NULL || - !e->isaString() || - !e->getString(c).good() || - c == NULL || - strcmp("PMSCT_RLE1", c)) - { - return false; - } - else - { - return true; - } - } - - - bool DicomImageDecoder::DecodePsmctRle1(std::string& output, - DcmDataset& dataset) - { - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!IsPsmctRle1(dataset)) - { - return false; - } - - // OK, this is a custom RLE encoding from Philips. Get the pixel - // data from the appropriate private DICOM tag. - Uint8* pixData = NULL; - DcmElement* e; - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || - e == NULL || - e->getUint8Array(pixData) != EC_Normal) - { - return false; - } - - // The "unsigned" below IS VERY IMPORTANT - const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData); - const size_t length = e->getLength(); - - /** - * The code below is an adaptation of a sample code for GDCM by - * Mathieu Malaterre (under a BSD license). - * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html - **/ - - // RLE pass - std::vector<uint8_t> temp; - temp.reserve(length); - for (size_t i = 0; i < length; i++) - { - if (inbuffer[i] == 0xa5) - { - temp.push_back(inbuffer[i+2]); - for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) - { - temp.push_back(inbuffer[i+2]); - } - i += 2; - } - else - { - temp.push_back(inbuffer[i]); - } - } - - // Delta encoding pass - uint16_t delta = 0; - output.clear(); - output.reserve(temp.size()); - for (size_t i = 0; i < temp.size(); i++) - { - uint16_t value; - - if (temp[i] == 0x5a) - { - uint16_t v1 = temp[i + 1]; - uint16_t v2 = temp[i + 2]; - value = (v2 << 8) + v1; - i += 2; - } - else - { - value = delta + (int8_t) temp[i]; - } - - output.push_back(value & 0xff); - output.push_back(value >> 8); - delta = value; - } - - if (output.size() % 2) - { - output.resize(output.size() - 1); - } - - return true; - } - - - class DicomImageDecoder::ImageSource - { - private: - std::string psmct_; - std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_; - - public: - void Setup(DcmDataset& dataset, - unsigned int frame) - { - psmct_.clear(); - slowAccessor_.reset(NULL); - - // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data - - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, dataset); - - /** - * Create an accessor to the raw values of the DICOM image. - **/ - - DcmElement* e; - if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && - e != NULL) - { - Uint8* pixData = NULL; - if (e->getUint8Array(pixData) == EC_Normal) - { - slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); - } - } - else if (DecodePsmctRle1(psmct_, dataset)) - { - LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; - Uint8* pixData = NULL; - if (psmct_.size() > 0) - { - pixData = reinterpret_cast<Uint8*>(&psmct_[0]); - } - - slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); - } - - if (slowAccessor_.get() == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - slowAccessor_->SetCurrentFrame(frame); - } - - unsigned int GetWidth() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetWidth(); - } - - unsigned int GetHeight() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetHeight(); - } - - unsigned int GetChannelCount() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetChannelCount(); - } - - const DicomIntegerPixelAccessor& GetAccessor() const - { - assert(slowAccessor_.get() != NULL); - return *slowAccessor_; - } - - unsigned int GetSize() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetSize(); - } - }; - - - ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset, - bool ignorePhotometricInterpretation) - { - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, dataset); - - DicomImageInformation info(m); - PixelFormat format; - - if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation)) - { - LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() - << "bpp, " << info.GetChannelCount() << " channels, " - << (info.IsSigned() ? "signed" : "unsigned") - << (info.IsPlanar() ? ", planar, " : ", non-planar, ") - << EnumerationToString(info.GetPhotometricInterpretation()) - << " photometric interpretation"; - throw OrthancException(ErrorCode_NotImplemented); - } - - return new Image(format, info.GetWidth(), info.GetHeight(), false); - } - - - template <typename PixelType> - static void CopyPixels(ImageAccessor& target, - const DicomIntegerPixelAccessor& source) - { - const PixelType minValue = std::numeric_limits<PixelType>::min(); - const PixelType maxValue = std::numeric_limits<PixelType>::max(); - - for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) - { - PixelType* pixel = reinterpret_cast<PixelType*>(target.GetRow(y)); - for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) - { - for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) - { - int32_t v = source.GetValue(x, y, c); - if (v < static_cast<int32_t>(minValue)) - { - *pixel = minValue; - } - else if (v > static_cast<int32_t>(maxValue)) - { - *pixel = maxValue; - } - else - { - *pixel = static_cast<PixelType>(v); - } - } - } - } - } - - - ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, - unsigned int frame) - { - ImageSource source; - source.Setup(dataset, frame); - - - /** - * Resize the target image. - **/ - - std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false)); - - if (source.GetWidth() != target->GetWidth() || - source.GetHeight() != target->GetHeight()) - { - throw OrthancException(ErrorCode_InternalError); - } - - - /** - * If the format of the DICOM buffer is natively supported, use a - * direct access to copy its values. - **/ - - const DicomImageInformation& info = source.GetAccessor().GetInformation(); - - bool fastVersionSuccess = false; - PixelFormat sourceFormat; - if (!info.IsPlanar() && - info.ExtractPixelFormat(sourceFormat, false)) - { - try - { - size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat); - if ((frame + 1) * frameSize <= source.GetSize()) - { - const uint8_t* buffer = reinterpret_cast<const uint8_t*>(source.GetAccessor().GetPixelData()); - - ImageAccessor sourceImage; - sourceImage.AssignReadOnly(sourceFormat, - info.GetWidth(), - info.GetHeight(), - info.GetWidth() * GetBytesPerPixel(sourceFormat), - buffer + frame * frameSize); - - ImageProcessing::Convert(*target, sourceImage); - ImageProcessing::ShiftRight(*target, info.GetShift()); - fastVersionSuccess = true; - } - } - catch (OrthancException&) - { - // Unsupported conversion, use the slow version - } - } - - /** - * Slow version : loop over the DICOM buffer, storing its value - * into the target image. - **/ - - if (!fastVersionSuccess) - { - switch (target->GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_RGBA32: - case PixelFormat_Grayscale8: - CopyPixels<uint8_t>(*target, source.GetAccessor()); - break; - - case PixelFormat_Grayscale16: - CopyPixels<uint16_t>(*target, source.GetAccessor()); - break; - - case PixelFormat_SignedGrayscale16: - CopyPixels<int16_t>(*target, source.GetAccessor()); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - return target.release(); - } - - - ImageAccessor* DicomImageDecoder::ApplyCodec(const DcmCodec& codec, - const DcmCodecParameter& parameters, - DcmDataset& dataset, - unsigned int frame) - { - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); - if (pixelSequence == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true)); - - Uint32 startFragment = 0; // Default - OFString decompressedColorModel; // Out - DJ_RPLossless representationParameter; - OFCondition c = codec.decodeFrame(&representationParameter, - pixelSequence, ¶meters, - &dataset, frame, startFragment, target->GetBuffer(), - target->GetSize(), decompressedColorModel); - - if (c.good()) - { - return target.release(); - } - else - { - LOG(ERROR) << "Cannot decode an image"; - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, - unsigned int frame) - { - DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - E_TransferSyntax syntax = dataset.getOriginalXfer(); - - /** - * Deal with uncompressed, raw images. - * http://support.dcmtk.org/docs/dcxfer_8h-source.html - **/ - if (syntax == EXS_Unknown || - syntax == EXS_LittleEndianImplicit || - syntax == EXS_BigEndianImplicit || - syntax == EXS_LittleEndianExplicit || - syntax == EXS_BigEndianExplicit) - { - return DecodeUncompressedImage(dataset, frame); - } - - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - /** - * Deal with JPEG-LS images. - **/ - - if (syntax == EXS_JPEGLSLossless || - syntax == EXS_JPEGLSLossy) - { - DJLSCodecParameter parameters; - std::auto_ptr<DJLSDecoderBase> decoder; - - switch (syntax) - { - case EXS_JPEGLSLossless: - LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image"; - decoder.reset(new DJLSLosslessDecoder); - break; - - case EXS_JPEGLSLossy: - LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image"; - decoder.reset(new DJLSNearLosslessDecoder); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - return ApplyCodec(*decoder, parameters, dataset, frame); - } -#endif - - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - /** - * Deal with JPEG images. - **/ - - if (syntax == EXS_JPEGProcess1 || // DJDecoderBaseline - syntax == EXS_JPEGProcess2_4 || // DJDecoderExtended - syntax == EXS_JPEGProcess6_8 || // DJDecoderSpectralSelection (retired) - syntax == EXS_JPEGProcess10_12 || // DJDecoderProgressive (retired) - syntax == EXS_JPEGProcess14 || // DJDecoderLossless - syntax == EXS_JPEGProcess14SV1) // DJDecoderP14SV1 - { - // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d - DJCodecParameter parameters( - ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression - EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr - EUC_default, // Mode for UID creation, unused for decompression - EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation - std::auto_ptr<DJCodecDecoder> decoder; - - switch (syntax) - { - case EXS_JPEGProcess1: - LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image"; - decoder.reset(new DJDecoderBaseline); - break; - - case EXS_JPEGProcess2_4 : - LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image"; - decoder.reset(new DJDecoderExtended); - break; - - case EXS_JPEGProcess6_8: // Retired - LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image"; - decoder.reset(new DJDecoderSpectralSelection); - break; - - case EXS_JPEGProcess10_12: // Retired - LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image"; - decoder.reset(new DJDecoderProgressive); - break; - - case EXS_JPEGProcess14: - LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image"; - decoder.reset(new DJDecoderLossless); - break; - - case EXS_JPEGProcess14SV1: - LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image"; - decoder.reset(new DJDecoderP14SV1); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - return ApplyCodec(*decoder, parameters, dataset, frame); - } -#endif - - - if (syntax == EXS_RLELossless) - { - LOG(INFO) << "Decoding a RLE lossless DICOM image"; - DcmRLECodecParameter parameters; - DcmRLECodecDecoder decoder; - return ApplyCodec(decoder, parameters, dataset, frame); - } - - - /** - * This DICOM image format is not natively supported by - * Orthanc. As a last resort, try and decode it through DCMTK by - * converting its transfer syntax to Little Endian. This will - * result in higher memory consumption. This is actually the - * second example of the following page: - * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples - **/ - - { - LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian"; - - std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone())); - converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); - - if (converted->canWriteXfer(EXS_LittleEndianExplicit)) - { - return DecodeUncompressedImage(*converted, frame); - } - } - - LOG(ERROR) << "Cannot decode a DICOM image with the built-in decoder"; - throw OrthancException(ErrorCode_BadFileFormat); - } - - - static bool IsColorImage(PixelFormat format) - { - return (format == PixelFormat_RGB24 || - format == PixelFormat_RGBA32); - } - - - bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, - PixelFormat format, - bool allowColorConversion) - { - // If specified, prevent the conversion between color and - // grayscale images - bool isSourceColor = IsColorImage(image->GetFormat()); - bool isTargetColor = IsColorImage(format); - - if (!allowColorConversion) - { - if (isSourceColor ^ isTargetColor) - { - return false; - } - } - - if (image->GetFormat() != format) - { - // A conversion is required - std::auto_ptr<ImageAccessor> target(new Image(format, image->GetWidth(), image->GetHeight(), false)); - ImageProcessing::Convert(*target, *image); - image = target; - } - - return true; - } - - - bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image) - { - switch (image->GetFormat()) - { - case PixelFormat_RGB24: - { - // Directly return color images without modification (RGB) - return true; - } - - case PixelFormat_Grayscale8: - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - // Grayscale image: Stretch its dynamics to the [0,255] range - int64_t a, b; - ImageProcessing::GetMinMaxValue(a, b, *image); - - if (a == b) - { - ImageProcessing::Set(*image, 0); - } - else - { - ImageProcessing::ShiftScale(*image, static_cast<float>(-a), 255.0f / static_cast<float>(b - a)); - } - - // If the source image is not grayscale 8bpp, convert it - if (image->GetFormat() != PixelFormat_Grayscale8) - { - std::auto_ptr<ImageAccessor> target(new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); - ImageProcessing::Convert(*target, *image); - image = target; - } - - return true; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, - ImageExtractionMode mode, - bool invert) - { - if (image.get() == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - bool ok = false; - - switch (mode) - { - case ImageExtractionMode_UInt8: - ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false); - break; - - case ImageExtractionMode_UInt16: - ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false); - break; - - case ImageExtractionMode_Int16: - ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false); - break; - - case ImageExtractionMode_Preview: - ok = PreviewDecodedImage(image); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (ok) - { - assert(image.get() != NULL); - - if (invert) - { - Orthanc::ImageProcessing::Invert(*image); - } - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - } - - -#if ORTHANC_ENABLE_PNG == 1 - void DicomImageDecoder::ExtractPngImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, - ImageExtractionMode mode, - bool invert) - { - ApplyExtractionMode(image, mode, invert); - - PngWriter writer; - writer.WriteToMemory(result, *image); - } -#endif - - -#if ORTHANC_ENABLE_JPEG == 1 - void DicomImageDecoder::ExtractJpegImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, - ImageExtractionMode mode, - bool invert, - uint8_t quality) - { - if (mode != ImageExtractionMode_UInt8 && - mode != ImageExtractionMode_Preview) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - ApplyExtractionMode(image, mode, invert); - - JpegWriter writer; - writer.SetQuality(quality); - writer.WriteToMemory(result, *image); - } -#endif -}
--- a/OrthancServer/Internals/DicomImageDecoder.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../ParsedDicomFile.h" - -#include <memory> - -#if !defined(ORTHANC_ENABLE_JPEG) -# error The macro ORTHANC_ENABLE_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_PNG) -# error The macro ORTHANC_ENABLE_PNG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined -#endif - - -class DcmDataset; -class DcmCodec; -class DcmCodecParameter; - -namespace Orthanc -{ - class DicomImageDecoder : public boost::noncopyable - { - private: - class ImageSource; - - DicomImageDecoder() // This is a fully abstract class, no constructor - { - } - - static ImageAccessor* CreateImage(DcmDataset& dataset, - bool ignorePhotometricInterpretation); - - static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset, - unsigned int frame); - - static ImageAccessor* ApplyCodec(const DcmCodec& codec, - const DcmCodecParameter& parameters, - DcmDataset& dataset, - unsigned int frame); - - static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image, - PixelFormat format, - bool allowColorConversion); - - static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image); - - static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image, - ImageExtractionMode mode, - bool invert); - - public: - static bool IsPsmctRle1(DcmDataset& dataset); - - static bool DecodePsmctRle1(std::string& output, - DcmDataset& dataset); - - static ImageAccessor *Decode(ParsedDicomFile& dicom, - unsigned int frame); - -#if ORTHANC_ENABLE_PNG == 1 - static void ExtractPngImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, - ImageExtractionMode mode, - bool invert); -#endif - -#if ORTHANC_ENABLE_JPEG == 1 - static void ExtractJpegImage(std::string& result, - std::auto_ptr<ImageAccessor>& image, - ImageExtractionMode mode, - bool invert, - uint8_t quality); -#endif - }; -}
--- a/OrthancServer/Internals/FindScp.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,349 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - - -#include "../PrecompiledHeadersServer.h" -#include "FindScp.h" - -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" -#include "../OrthancInitialization.h" - -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcdeftag.h> - - - -/** - * The function below is extracted from DCMTK 3.6.0, cf. file - * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc". - **/ - -static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, - const DcmTagKey &sequenceTagKey) -// Date : May 3, 2005 -// Author : Thomas Wilkens -// Task : This function performs a check on a sequence attribute in the given dataset. At two different places -// in the definition of the DICOM worklist management service, a sequence attribute with a return type -// of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes -// specifies that in case a sequence item is present, then these two attributes must be existent and -// must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.) -// In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass -// and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what -// this function does. -// Parameters : dataset - [in] Dataset in which the consistency of the sequence attribute shall be checked. -// sequenceTagKey - [in] DcmTagKey of the sequence attribute which shall be checked. -// Return Value : none. -{ - DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL; - - // in case the sequence attribute contains exactly one item with an empty - // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item - if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() && - ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 && - ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() && - referencedSOPClassUIDAttribute->getLength() == 0 && - ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() && - referencedSOPInstanceUIDAttribute->getLength() == 0 ) - { - DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) ); - delete item; - } -} - - - -namespace Orthanc -{ - namespace - { - struct FindScpData - { - DicomServer::IRemoteModalities* modalities_; - IFindRequestHandler* findHandler_; - IWorklistRequestHandler* worklistHandler_; - DicomFindAnswers answers_; - DcmDataset* lastRequest_; - const std::string* remoteIp_; - const std::string* remoteAet_; - const std::string* calledAet_; - - FindScpData() : answers_(false) - { - } - }; - - - - static void FixWorklistQuery(ParsedDicomFile& query) - { - // TODO: Check out - // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()" - // in DCMTK 3.6.0 - - DcmDataset* dataset = query.GetDcmtkObject().getDataset(); - HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence); - HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence); - } - - - - void FindScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_FindRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_FindRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_FindRSP)); - *statusDetail = NULL; - - std::string sopClassUid(request->AffectedSOPClassUID); - - FindScpData& data = *reinterpret_cast<FindScpData*>(callbackData); - if (data.lastRequest_ == NULL) - { - bool ok = false; - - try - { - RemoteModalityParameters modality; - - /** - * Ensure that the remote modality is known to Orthanc for C-FIND requests. - **/ - - assert(data.modalities_ != NULL); - if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_)) - { - LOG(ERROR) << "Modality with AET \"" << *data.remoteAet_ - << "\" is not defined in the \"DicomModalities\" configuration option"; - throw OrthancException(ErrorCode_UnknownModality); - } - - - if (sopClassUid == UID_FINDModalityWorklistInformationModel) - { - data.answers_.SetWorklist(true); - - if (data.worklistHandler_ != NULL) - { - ParsedDicomFile query(*requestIdentifiers); - FixWorklistQuery(query); - - data.worklistHandler_->Handle(data.answers_, query, - *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, modality.GetManufacturer()); - ok = true; - } - else - { - LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request"; - } - } - else - { - data.answers_.SetWorklist(false); - - if (data.findHandler_ != NULL) - { - std::list<DicomTag> sequencesToReturn; - - for (unsigned long i = 0; i < requestIdentifiers->card(); i++) - { - DcmElement* element = requestIdentifiers->getElement(i); - if (element && !element->isLeaf()) - { - const DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); - - DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); - if (sequence.card() != 0) - { - LOG(WARNING) << "Orthanc only supports sequence matching on worklists, " - << "ignoring C-FIND SCU constraint on tag (" << tag.Format() - << ") " << FromDcmtkBridge::GetTagName(*element); - } - - sequencesToReturn.push_back(tag); - } - } - - DicomMap input; - FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); - - data.findHandler_->Handle(data.answers_, input, sequencesToReturn, - *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, modality.GetManufacturer()); - ok = true; - } - else - { - LOG(ERROR) << "No C-Find handler is installed, cannot handle this request"; - } - } - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); - } - - if (!ok) - { - response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; - *responseIdentifiers = NULL; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; - *responseIdentifiers = NULL; - return; - } - - if (responseCount <= static_cast<int>(data.answers_.GetSize())) - { - // There are pending results that are still to be sent - response->DimseStatus = STATUS_Pending; - *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1); - } - else if (data.answers_.IsComplete()) - { - // Success: All the results have been sent - response->DimseStatus = STATUS_Success; - *responseIdentifiers = NULL; - } - else - { - // Success, but the results were too numerous and had to be cropped - LOG(WARNING) << "Too many results for an incoming C-FIND query"; - response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest; - *responseIdentifiers = NULL; - } - } - } - - - OFCondition Internals::findScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - DicomServer::IRemoteModalities& modalities, - IFindRequestHandler* findHandler, - IWorklistRequestHandler* worklistHandler, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) - { - FindScpData data; - data.modalities_ = &modalities; - data.findHandler_ = findHandler; - data.worklistHandler_ = worklistHandler; - data.lastRequest_ = NULL; - data.remoteIp_ = &remoteIp; - data.remoteAet_ = &remoteAet; - data.calledAet_ = &calledAet; - - OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, - FindScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Find SCP Failed: " << cond.text(); - } - - return cond; - } -}
--- a/OrthancServer/Internals/FindScp.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/DicomServer.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition findScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - DicomServer::IRemoteModalities& modalities, - IFindRequestHandler* findHandler, // can be NULL - IWorklistRequestHandler* worklistHandler, // can be NULL - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet); - } -}
--- a/OrthancServer/Internals/MoveScp.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "MoveScp.h" - -#include <memory> - -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" -#include "../../Core/Logging.h" -#include "../../Core/OrthancException.h" - -#include <boost/lexical_cast.hpp> - - -namespace Orthanc -{ - namespace - { - struct MoveScpData - { - std::string target_; - IMoveRequestHandler* handler_; - DcmDataset* lastRequest_; - unsigned int subOperationCount_; - unsigned int failureCount_; - unsigned int warningCount_; - std::auto_ptr<IMoveRequestIterator> iterator_; - const std::string* remoteIp_; - const std::string* remoteAet_; - const std::string* calledAet_; - }; - - - - static uint16_t GetMessageId(const DicomMap& message) - { - /** - * Retrieve the Message ID (0000,0110) for this C-MOVE request, if - * any. If present, this Message ID will be stored in the Move - * Originator Message ID (0000,1031) field of the C-MOVE response. - * http://dicom.nema.org/dicom/2013/output/chtml/part07/chapter_E.html - **/ - - const DicomValue* value = message.TestAndGetValue(DICOM_TAG_MESSAGE_ID); - - if (value != NULL && - !value->IsNull() && - !value->IsBinary()) - { - try - { - int tmp = boost::lexical_cast<int>(value->GetContent()); - if (tmp >= 0 && tmp <= 0xffff) - { - return static_cast<uint16_t>(tmp); - } - } - catch (boost::bad_lexical_cast&) - { - LOG(WARNING) << "Cannot convert the Message ID (\"" << value->GetContent() - << "\") of an incoming C-MOVE request to an integer, assuming zero"; - } - } - - return 0; - } - - - - void MoveScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_MoveRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_MoveRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_MoveRSP)); - *statusDetail = NULL; - *responseIdentifiers = NULL; - - MoveScpData& data = *reinterpret_cast<MoveScpData*>(callbackData); - if (data.lastRequest_ == NULL) - { - DicomMap input; - FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); - - try - { - data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, GetMessageId(input))); - - if (data.iterator_.get() == NULL) - { - // Internal error! - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - data.subOperationCount_ = data.iterator_->GetSubOperationCount(); - data.failureCount_ = 0; - data.warningCount_ = 0; - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - if (data.subOperationCount_ == 0) - { - response->DimseStatus = STATUS_Success; - } - else - { - IMoveRequestIterator::Status status; - - try - { - status = data.iterator_->DoNext(); - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - if (status == IMoveRequestIterator::Status_Failure) - { - data.failureCount_++; - } - else if (status == IMoveRequestIterator::Status_Warning) - { - data.warningCount_++; - } - - if (responseCount < static_cast<int>(data.subOperationCount_)) - { - response->DimseStatus = STATUS_Pending; - } - else - { - response->DimseStatus = STATUS_Success; - } - } - - response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount; - response->NumberOfCompletedSubOperations = responseCount; - response->NumberOfFailedSubOperations = data.failureCount_; - response->NumberOfWarningSubOperations = data.warningCount_; - } - } - - - OFCondition Internals::moveScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) - { - MoveScpData data; - data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); - data.lastRequest_ = NULL; - data.handler_ = &handler; - data.remoteIp_ = &remoteIp; - data.remoteAet_ = &remoteAet; - data.calledAet_ = &calledAet; - - OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, - MoveScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Move SCP Failed: " << cond.text(); - } - - return cond; - } -}
--- a/OrthancServer/Internals/MoveScp.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/IMoveRequestHandler.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition moveScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet); - } -}
--- a/OrthancServer/Internals/StoreScp.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,298 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../PrecompiledHeadersServer.h" -#include "StoreScp.h" - -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" -#include "../../Core/OrthancException.h" -#include "../../Core/Logging.h" - -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmnet/diutil.h> - - -namespace Orthanc -{ - namespace - { - struct StoreCallbackData - { - IStoreRequestHandler* handler; - const std::string* remoteIp; - const char* remoteAET; - const char* calledAET; - const char* modality; - const char* affectedSOPInstanceUID; - uint32_t messageID; - }; - - - static void - storeScpCallback( - void *callbackData, - T_DIMSE_StoreProgress *progress, - T_DIMSE_C_StoreRQ *req, - char * /*imageFileName*/, DcmDataset **imageDataSet, - T_DIMSE_C_StoreRSP *rsp, - DcmDataset **statusDetail) - /* - * This function.is used to indicate progress when storescp receives instance data over the - * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd) - * this function will store the data set which was received over the network to a file. - * Earlier calls to this function will simply cause some information to be dumped to stdout. - * - * Parameters: - * callbackData - [in] data for this callback function - * progress - [in] The state of progress. (identifies if this is the initial or final call - * to this function, or a call in between these two calls. - * req - [in] The original store request message. - * imageFileName - [in] The path to and name of the file the information shall be written to. - * imageDataSet - [in] The data set which shall be stored in the image file - * rsp - [inout] the C-STORE-RSP message (will be sent after the call to this function) - * statusDetail - [inout] This variable can be used to capture detailed information with regard to - * the status information which is captured in the status element (0000,0900). Note - * that this function does specify any such information, the pointer will be set to NULL. - */ - { - StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData); - - DIC_UI sopClass; - DIC_UI sopInstance; - - // if this is the final call of this function, save the data which was received to a file - // (note that we could also save the image somewhere else, put it in database, etc.) - if (progress->state == DIMSE_StoreEnd) - { - OFString tmpStr; - - // do not send status detail information - *statusDetail = NULL; - - // Concerning the following line: an appropriate status code is already set in the resp structure, - // it need not be success. For example, if the caller has already detected an out of resources problem - // then the status will reflect this. The callback function is still called to allow cleanup. - //rsp->DimseStatus = STATUS_Success; - - // we want to write the received information to a file only if this information - // is present and the options opt_bitPreserving and opt_ignore are not set. - if ((imageDataSet != NULL) && (*imageDataSet != NULL)) - { - DicomMap summary; - Json::Value dicomJson; - std::string buffer; - - try - { - FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet); - FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet); - - if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) - { - LOG(ERROR) << "cannot write DICOM file to memory"; - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - } - } - catch (...) - { - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - } - - // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond - // to those mentioned in the request. If not, set the status in the response message variable. - if (rsp->DimseStatus == STATUS_Success) - { - // which SOP class and SOP instance ? - if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) - { - //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); - rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; - } - else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0) - { - rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; - } - else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0) - { - rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; - } - else - { - try - { - cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); - } - catch (OrthancException& e) - { - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - - if (e.GetErrorCode() == ErrorCode_InexistentTag) - { - summary.LogMissingTagsForStore(); - } - else - { - LOG(ERROR) << "Exception while storing DICOM: " << e.What(); - } - } - } - } - } - } - } - } - -/* - * This function processes a DIMSE C-STORE-RQ commmand that was - * received over the network connection. - * - * Parameters: - * assoc - [in] The association (network connection to another DICOM application). - * msg - [in] The DIMSE C-STORE-RQ message that was received. - * presID - [in] The ID of the presentation context which was specified in the PDV which contained - * the DIMSE command. - */ - OFCondition Internals::storeScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler, - const std::string& remoteIp) - { - OFCondition cond = EC_Normal; - T_DIMSE_C_StoreRQ *req; - - // assign the actual information of the C-STORE-RQ command to a local variable - req = &msg->msg.CStoreRQ; - - // intialize some variables - StoreCallbackData data; - data.handler = &handler; - data.remoteIp = &remoteIp; - data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); - if (data.modality == NULL) - data.modality = "UNKNOWN"; - - data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; - data.messageID = req->MessageID; - if (assoc && assoc->params) - { - data.remoteAET = assoc->params->DULparams.callingAPTitle; - data.calledAET = assoc->params->DULparams.calledAPTitle; - } - else - { - data.remoteAET = ""; - data.calledAET = ""; - } - - DcmFileFormat dcmff; - - // store SourceApplicationEntityTitle in metaheader - if (assoc && assoc->params) - { - const char *aet = assoc->params->DULparams.callingAPTitle; - if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet); - } - - // define an address where the information which will be received over the network will be stored - DcmDataset *dset = dcmff.getDataset(); - - cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, - storeScpCallback, &data, - /*opt_blockMode*/ DIMSE_BLOCKING, - /*opt_dimse_timeout*/ 0); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Store SCP Failed: " << cond.text(); - } - - // return return value - return cond; - } -}
--- a/OrthancServer/Internals/StoreScp.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../DicomProtocol/IStoreRequestHandler.h" - -#include <dcmtk/dcmnet/dimse.h> - -namespace Orthanc -{ - namespace Internals - { - OFCondition storeScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler, - const std::string& remoteIp); - } -}
--- a/OrthancServer/OrthancFindRequestHandler.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -37,7 +37,7 @@ #include "../Core/DicomFormat/DicomArray.h" #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/Logging.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "OrthancInitialization.h" #include "Search/LookupResource.h" #include "ServerToolbox.h"
--- a/OrthancServer/OrthancFindRequestHandler.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.h Tue Aug 29 21:17:35 2017 +0200 @@ -32,7 +32,7 @@ #pragma once -#include "DicomProtocol/IFindRequestHandler.h" +#include "../Core/DicomNetworking/IFindRequestHandler.h" #include "ServerContext.h"
--- a/OrthancServer/OrthancInitialization.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -42,7 +42,7 @@ #include "ServerEnumerations.h" #include "DatabaseWrapper.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include <boost/lexical_cast.hpp> #include <boost/filesystem.hpp>
--- a/OrthancServer/OrthancInitialization.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancInitialization.h Tue Aug 29 21:17:35 2017 +0200 @@ -42,8 +42,8 @@ #include "../Core/HttpServer/MongooseServer.h" #include "../Core/Images/FontRegistry.h" #include "../Core/WebServiceParameters.h" +#include "../Core/DicomNetworking/RemoteModalityParameters.h" -#include "DicomProtocol/RemoteModalityParameters.h" #include "IDatabaseWrapper.h" #include "ServerEnumerations.h"
--- a/OrthancServer/OrthancMoveRequestHandler.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -35,7 +35,7 @@ #include "OrthancMoveRequestHandler.h" #include "OrthancInitialization.h" -#include "FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/DicomFormat/DicomArray.h" #include "../Core/Logging.h"
--- a/OrthancServer/OrthancMoveRequestHandler.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.h Tue Aug 29 21:17:35 2017 +0200 @@ -32,7 +32,7 @@ #pragma once -#include "DicomProtocol/IMoveRequestHandler.h" +#include "../Core/DicomNetworking/IMoveRequestHandler.h" #include "ServerContext.h" namespace Orthanc
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -35,7 +35,7 @@ #include "OrthancRestApi.h" #include "../../Core/Logging.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../ServerContext.h" #include "../OrthancInitialization.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -35,7 +35,6 @@ #include "OrthancRestApi.h" #include "../../Core/Logging.h" -#include "../DicomModification.h" #include "../ServerContext.h" namespace Orthanc
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h Tue Aug 29 21:17:35 2017 +0200 @@ -34,7 +34,8 @@ #pragma once #include "../../Core/RestApi/RestApi.h" -#include "../DicomModification.h" +#include "../../Core/DicomParsing/DicomModification.h" +#include "../ServerEnumerations.h" #include <set>
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -34,7 +34,7 @@ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" -#include "../DicomDirWriter.h" +#include "../../Core/DicomParsing/DicomDirWriter.h" #include "../../Core/FileStorage/StorageAccessor.h" #include "../../Core/Compression/HierarchicalZipWriter.h" #include "../../Core/HttpServer/FilesystemHttpSender.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -37,7 +37,7 @@ #include "../OrthancInitialization.h" #include "../../Core/HttpClient.h" #include "../../Core/Logging.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../Scheduler/ServerJob.h" #include "../Scheduler/StoreScuCommand.h" #include "../Scheduler/StorePeerCommand.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -36,8 +36,8 @@ #include "../../Core/HttpServer/HttpContentNegociation.h" #include "../../Core/Logging.h" -#include "../FromDcmtkBridge.h" -#include "../Internals/DicomImageDecoder.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" #include "../OrthancInitialization.h" #include "../Search/LookupResource.h" #include "../ServerContext.h"
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -35,7 +35,7 @@ #include "OrthancRestApi.h" #include "../OrthancInitialization.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Plugins/Engine/PluginsManager.h" #include "../../Plugins/Engine/OrthancPlugins.h" #include "../ServerContext.h"
--- a/OrthancServer/ParsedDicomFile.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1476 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - -Copyright (c) 2006-2011 Mathieu Malaterre -Copyright (c) 1993-2005 CREATIS -(CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "PrecompiledHeadersServer.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "ParsedDicomFile.h" - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" -#include "Internals/DicomFrameIndex.h" -#include "../Core/Logging.h" -#include "../Core/OrthancException.h" -#include "../Core/Toolbox.h" -#include "../Core/SystemToolbox.h" - -#if ORTHANC_ENABLE_JPEG == 1 -# include "../Core/Images/JpegReader.h" -#endif - -#if ORTHANC_ENABLE_PNG == 1 -# include "../Core/Images/PngReader.h" -#endif - -#include <list> -#include <limits> - -#include <boost/lexical_cast.hpp> - -#include <dcmtk/dcmdata/dcchrstr.h> -#include <dcmtk/dcmdata/dcdicent.h> -#include <dcmtk/dcmdata/dcdict.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcdeftag.h> - -#include <dcmtk/dcmdata/dcvrae.h> -#include <dcmtk/dcmdata/dcvras.h> -#include <dcmtk/dcmdata/dcvrcs.h> -#include <dcmtk/dcmdata/dcvrda.h> -#include <dcmtk/dcmdata/dcvrds.h> -#include <dcmtk/dcmdata/dcvrdt.h> -#include <dcmtk/dcmdata/dcvrfd.h> -#include <dcmtk/dcmdata/dcvrfl.h> -#include <dcmtk/dcmdata/dcvris.h> -#include <dcmtk/dcmdata/dcvrlo.h> -#include <dcmtk/dcmdata/dcvrlt.h> -#include <dcmtk/dcmdata/dcvrpn.h> -#include <dcmtk/dcmdata/dcvrsh.h> -#include <dcmtk/dcmdata/dcvrsl.h> -#include <dcmtk/dcmdata/dcvrss.h> -#include <dcmtk/dcmdata/dcvrst.h> -#include <dcmtk/dcmdata/dcvrtm.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcvrul.h> -#include <dcmtk/dcmdata/dcvrus.h> -#include <dcmtk/dcmdata/dcvrut.h> -#include <dcmtk/dcmdata/dcpixel.h> -#include <dcmtk/dcmdata/dcpixseq.h> -#include <dcmtk/dcmdata/dcpxitem.h> - - -#include <boost/math/special_functions/round.hpp> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <boost/algorithm/string/predicate.hpp> - - -#if DCMTK_VERSION_NUMBER <= 360 -# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax -#endif - - -static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; - - - -namespace Orthanc -{ - struct ParsedDicomFile::PImpl - { - std::auto_ptr<DcmFileFormat> file_; - std::auto_ptr<DicomFrameIndex> frameIndex_; - }; - - - static void SendPathValueForDictionary(RestApiOutput& output, - DcmItem& dicom) - { - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < dicom.card(); i++) - { - DcmElement* element = dicom.getElement(i); - if (element) - { - char buf[16]; - sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); - v.append(buf); - } - } - - output.AnswerJson(v); - } - - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - static void ParseTagAndGroup(DcmTagKey& key, - const std::string& tag) - { - DicomTag t = FromDcmtkBridge::ParseTag(tag); - key = DcmTagKey(t.GetGroup(), t.GetElement()); - } - - - static void SendSequence(RestApiOutput& output, - DcmSequenceOfItems& sequence) - { - // This element is a sequence - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < sequence.card(); i++) - { - v.append(boost::lexical_cast<std::string>(i)); - } - - output.AnswerJson(v); - } - - - static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, - E_TransferSyntax transferSyntax) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - return pixelSequence->card(); - } - else - { - return 1; - } - } - - - namespace - { - class DicomFieldStream : public IHttpStreamAnswer - { - private: - DcmElement& element_; - uint32_t length_; - uint32_t offset_; - std::string chunk_; - size_t chunkSize_; - - public: - DicomFieldStream(DcmElement& element, - E_TransferSyntax transferSyntax) : - element_(element), - length_(element.getLength(transferSyntax)), - offset_(0), - chunkSize_(0) - { - static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB - chunk_.resize(CHUNK_SIZE); - } - - virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, - bool /*deflateAllowed*/) - { - // No support for compression - return HttpCompression_None; - } - - virtual bool HasContentFilename(std::string& filename) - { - return false; - } - - virtual std::string GetContentType() - { - return ""; - } - - virtual uint64_t GetContentLength() - { - return length_; - } - - virtual bool ReadNextChunk() - { - assert(offset_ <= length_); - - if (offset_ == length_) - { - return false; - } - else - { - if (length_ - offset_ < chunk_.size()) - { - chunkSize_ = length_ - offset_; - } - else - { - chunkSize_ = chunk_.size(); - } - - OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); - - offset_ += chunkSize_; - - if (!cond.good()) - { - LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); - throw OrthancException(ErrorCode_InternalError); - } - - return true; - } - } - - virtual const char *GetChunkContent() - { - return chunk_.c_str(); - } - - virtual size_t GetChunkSize() - { - return chunkSize_; - } - }; - } - - - static bool AnswerPixelData(RestApiOutput& output, - DcmItem& dicom, - E_TransferSyntax transferSyntax, - const std::string* blockUri) - { - DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - DcmElement *element = NULL; - if (!dicom.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - try - { - DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); - if (blockUri == NULL) - { - // The user asks how many blocks are present in this pixel data - unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); - - Json::Value result(Json::arrayValue); - for (unsigned int i = 0; i < blocks; i++) - { - result.append(boost::lexical_cast<std::string>(i)); - } - - output.AnswerJson(result); - return true; - } - - - unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); - - if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - // This is the case for JPEG transfer syntaxes - if (block < pixelSequence->card()) - { - DcmPixelItem* pixelItem = NULL; - if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) - { - if (pixelItem->getLength() == 0) - { - output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); - return true; - } - - Uint8* buffer = NULL; - if (pixelItem->getUint8Array(buffer).good() && buffer) - { - output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); - return true; - } - } - } - } - else - { - // This is the case for raw, uncompressed image buffers - assert(*blockUri == "0"); - DicomFieldStream stream(*element, transferSyntax); - output.AnswerStream(stream); - } - } - } - catch (boost::bad_lexical_cast&) - { - // The URI entered by the user is not a number - } - catch (std::bad_cast&) - { - // This should never happen - } - - return false; - } - - - - static void SendPathValueForLeaf(RestApiOutput& output, - const std::string& tag, - DcmItem& dicom, - E_TransferSyntax transferSyntax) - { - DcmTagKey k; - ParseTagAndGroup(k, tag); - - DcmSequenceOfItems* sequence = NULL; - if (dicom.findAndGetSequence(k, sequence).good() && - sequence != NULL && - sequence->getVR() == EVR_SQ) - { - SendSequence(output, *sequence); - return; - } - - DcmElement* element = NULL; - if (dicom.findAndGetElement(k, element).good() && - element != NULL && - //element->getVR() != EVR_UNKNOWN && // This would forbid private tags - element->getVR() != EVR_SQ) - { - DicomFieldStream stream(*element, transferSyntax); - output.AnswerStream(stream); - } - } - - void ParsedDicomFile::SendPathValue(RestApiOutput& output, - const UriComponents& uri) - { - DcmItem* dicom = pimpl_->file_->getDataset(); - E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); - - // Special case: Accessing the pixel data - if (uri.size() == 1 || - uri.size() == 2) - { - DcmTagKey tag; - ParseTagAndGroup(tag, uri[0]); - - if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && - tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) - { - AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); - return; - } - } - - // Go down in the tag hierarchy according to the URI - for (size_t pos = 0; pos < uri.size() / 2; pos++) - { - size_t index; - try - { - index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); - } - catch (boost::bad_lexical_cast&) - { - return; - } - - DcmTagKey k; - DcmItem *child = NULL; - ParseTagAndGroup(k, uri[2 * pos]); - if (!dicom->findAndGetSequenceItem(k, child, index).good() || - child == NULL) - { - return; - } - - dicom = child; - } - - // We have reached the end of the URI - if (uri.size() % 2 == 0) - { - SendPathValueForDictionary(output, *dicom); - } - else - { - SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); - } - } - - - void ParsedDicomFile::Remove(const DicomTag& tag) - { - InvalidateCache(); - - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = pimpl_->file_->getDataset()->remove(key); - if (element != NULL) - { - delete element; - } - } - - - void ParsedDicomFile::Clear(const DicomTag& tag, - bool onlyIfExists) - { - InvalidateCache(); - - DcmItem* dicom = pimpl_->file_->getDataset(); - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - - if (onlyIfExists && - !dicom->tagExists(key)) - { - // The tag is non-existing, do not clear it - } - else - { - if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - } - - - void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep) - { - InvalidateCache(); - - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - // Loop over the dataset to detect its private tags - typedef std::list<DcmElement*> Tags; - Tags privateTags; - - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - DcmTag tag(element->getTag()); - - // Is this a private tag? - if (tag.isPrivate()) - { - bool remove = true; - - // Check whether this private tag is to be kept - if (toKeep != NULL) - { - DicomTag tmp = FromDcmtkBridge::Convert(tag); - if (toKeep->find(tmp) != toKeep->end()) - { - remove = false; // Keep it - } - } - - if (remove) - { - privateTags.push_back(element); - } - } - } - - // Loop over the detected private tags to remove them - for (Tags::iterator it = privateTags.begin(); - it != privateTags.end(); ++it) - { - DcmElement* tmp = dataset.remove(*it); - if (tmp != NULL) - { - delete tmp; - } - } - } - - - static void InsertInternal(DcmDataset& dicom, - DcmElement* element) - { - OFCondition cond = dicom.insert(element, false, false); - if (!cond.good()) - { - // This field already exists - delete element; - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::Insert(const DicomTag& tag, - const Json::Value& value, - bool decodeDataUriScheme) - { - if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag))) - { - throw OrthancException(ErrorCode_AlreadyExistingTag); - } - - if (decodeDataUriScheme && - value.type() == Json::stringValue && - (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || - tag == DICOM_TAG_PIXEL_DATA)) - { - if (EmbedContentInternal(value.asString())) - { - return; - } - } - - InvalidateCache(); - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); - InsertInternal(*pimpl_->file_->getDataset(), element.release()); - } - - - static bool CanReplaceProceed(DcmDataset& dicom, - const DcmTagKey& tag, - DicomReplaceMode mode) - { - if (dicom.findAndDeleteElement(tag).good()) - { - // This tag was existing, it has been deleted - return true; - } - else - { - // This tag was absent, act wrt. the specified "mode" - switch (mode) - { - case DicomReplaceMode_InsertIfAbsent: - return true; - - case DicomReplaceMode_ThrowIfAbsent: - throw OrthancException(ErrorCode_InexistentItem); - - case DicomReplaceMode_IgnoreIfAbsent: - return false; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - } - - - void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme) - { - if (tag != DICOM_TAG_SOP_CLASS_UID && - tag != DICOM_TAG_SOP_INSTANCE_UID) - { - return; - } - - std::string binary; - const std::string* decoded = &utf8Value; - - if (decodeDataUriScheme && - boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) - { - std::string mime; - if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - decoded = &binary; - } - else - { - Encoding encoding = GetEncoding(); - if (GetEncoding() != Encoding_Utf8) - { - binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); - decoded = &binary; - } - } - - /** - * dcmodify will automatically correct 'Media Storage SOP Class - * UID' and 'Media Storage SOP Instance UID' in the metaheader, if - * you make changes to the related tags in the dataset ('SOP Class - * UID' and 'SOP Instance UID') via insert or modify mode - * options. You can disable this behaviour by using the -nmu - * option. - **/ - - if (tag == DICOM_TAG_SOP_CLASS_UID) - { - ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded); - } - - if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme, - DicomReplaceMode mode) - { - InvalidateCache(); - - DcmDataset& dicom = *pimpl_->file_->getDataset(); - if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) - { - // Either the tag was previously existing (and now removed), or - // the replace mode was set to "InsertIfAbsent" - - if (decodeDataUriScheme && - (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || - tag == DICOM_TAG_PIXEL_DATA)) - { - if (EmbedContentInternal(utf8Value)) - { - return; - } - } - - std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); - FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding()); - - InsertInternal(dicom, element.release()); - UpdateStorageUid(tag, utf8Value, false); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const Json::Value& value, - bool decodeDataUriScheme, - DicomReplaceMode mode) - { - InvalidateCache(); - - DcmDataset& dicom = *pimpl_->file_->getDataset(); - if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) - { - // Either the tag was previously existing (and now removed), or - // the replace mode was set to "InsertIfAbsent" - - if (decodeDataUriScheme && - value.type() == Json::stringValue && - (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || - tag == DICOM_TAG_PIXEL_DATA)) - { - if (EmbedContentInternal(value.asString())) - { - return; - } - } - - InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); - - if (tag == DICOM_TAG_SOP_CLASS_UID || - tag == DICOM_TAG_SOP_INSTANCE_UID) - { - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - UpdateStorageUid(tag, value.asString(), decodeDataUriScheme); - } - } - } - - - void ParsedDicomFile::Answer(RestApiOutput& output) - { - std::string serialized; - if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset())) - { - output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); - } - } - - - - bool ParsedDicomFile::GetTagValue(std::string& value, - const DicomTag& tag) - { - DcmTagKey k(tag.GetGroup(), tag.GetElement()); - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - if (tag.IsPrivate() || - FromDcmtkBridge::IsUnknownTag(tag) || - tag == DICOM_TAG_PIXEL_DATA || - tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) - { - const Uint8* data = NULL; // This is freed in the destructor of the dataset - long unsigned int count = 0; - - if (dataset.findAndGetUint8Array(k, data, &count).good()) - { - if (count > 0) - { - assert(data != NULL); - value.assign(reinterpret_cast<const char*>(data), count); - } - else - { - value.clear(); - } - - return true; - } - else - { - return false; - } - } - else - { - DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement - (*element, DicomToJsonFlags_Default, - ORTHANC_MAXIMUM_TAG_LENGTH, GetEncoding())); - - if (v.get() == NULL || - v->IsNull()) - { - value = ""; - } - else - { - // TODO v->IsBinary() - value = v->GetContent(); - } - - return true; - } - } - - - DicomInstanceHasher ParsedDicomFile::GetHasher() - { - std::string patientId, studyUid, seriesUid, instanceUid; - - if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) || - !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || - !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || - !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); - } - - - void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) - { - FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset()); - } - - - void ParsedDicomFile::SaveToFile(const std::string& path) - { - // TODO Avoid using a temporary memory buffer, write directly on disk - std::string content; - SaveToMemoryBuffer(content); - SystemToolbox::WriteFile(content, path); - } - - - ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat); - - if (createIdentifiers) - { - ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); - ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); - ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); - ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - } - - - void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source, - Encoding defaultEncoding) - { - pimpl_->file_.reset(new DcmFileFormat); - - const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET); - if (tmp != NULL) - { - Encoding encoding; - if (tmp->IsNull() || - tmp->IsBinary() || - !GetDicomEncoding(encoding, tmp->GetContent().c_str())) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - SetEncoding(encoding); - } - } - else - { - SetEncoding(defaultEncoding); - } - - for (DicomMap::Map::const_iterator - it = source.map_.begin(); it != source.map_.end(); ++it) - { - if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET && - !it->second->IsNull()) - { - ReplacePlainString(it->first, it->second->GetContent()); - } - } - } - - - ParsedDicomFile::ParsedDicomFile(const DicomMap& map, - Encoding defaultEncoding) : - pimpl_(new PImpl) - { - CreateFromDicomMap(map, defaultEncoding); - } - - - ParsedDicomFile::ParsedDicomFile(const DicomMap& map) : - pimpl_(new PImpl) - { - CreateFromDicomMap(map, GetDefaultDicomEncoding()); - } - - - ParsedDicomFile::ParsedDicomFile(const void* content, - size_t size) : pimpl_(new PImpl) - { - pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size)); - } - - ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl) - { - if (content.size() == 0) - { - pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0)); - } - else - { - pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size())); - } - } - - - ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) : - pimpl_(new PImpl) - { - pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone())); - - // Create a new instance-level identifier - ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - - - ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat(&dicom)); - } - - - ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat(dicom)); - } - - - ParsedDicomFile::~ParsedDicomFile() - { - delete pimpl_; - } - - - DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const - { - return *pimpl_->file_.get(); - } - - - ParsedDicomFile* ParsedDicomFile::Clone() - { - return new ParsedDicomFile(*this); - } - - - bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme) - { - std::string mime, content; - if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme)) - { - return false; - } - - Toolbox::ToLowerCase(mime); - - if (mime == "image/png") - { -#if ORTHANC_ENABLE_PNG == 1 - EmbedImage(mime, content); -#else - LOG(ERROR) << "Orthanc was compiled without support of PNG"; - throw OrthancException(ErrorCode_NotImplemented); -#endif - } - else if (mime == "image/jpeg") - { -#if ORTHANC_ENABLE_JPEG == 1 - EmbedImage(mime, content); -#else - LOG(ERROR) << "Orthanc was compiled without support of JPEG"; - throw OrthancException(ErrorCode_NotImplemented); -#endif - } - else if (mime == "application/pdf") - { - EmbedPdf(content); - } - else - { - LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime; - throw OrthancException(ErrorCode_NotImplemented); - } - - return true; - } - - - void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) - { - if (!EmbedContentInternal(dataUriScheme)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - -#if (ORTHANC_ENABLE_JPEG == 1 && \ - ORTHANC_ENABLE_PNG == 1) - void ParsedDicomFile::EmbedImage(const std::string& mime, - const std::string& content) - { - if (mime == "image/png") - { - PngReader reader; - reader.ReadFromMemory(content); - EmbedImage(reader); - } - else if (mime == "image/jpeg") - { - JpegReader reader; - reader.ReadFromMemory(content); - EmbedImage(reader); - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - } -#endif - - - void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor) - { - if (accessor.GetFormat() != PixelFormat_Grayscale8 && - accessor.GetFormat() != PixelFormat_Grayscale16 && - accessor.GetFormat() != PixelFormat_SignedGrayscale16 && - accessor.GetFormat() != PixelFormat_RGB24 && - accessor.GetFormat() != PixelFormat_RGBA32) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - InvalidateCache(); - - if (accessor.GetFormat() == PixelFormat_RGBA32) - { - LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; - } - - // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html - - Remove(DICOM_TAG_PIXEL_DATA); - ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth())); - ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight())); - ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); - ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1"); - - if (accessor.GetFormat() == PixelFormat_SignedGrayscale16) - { - ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1"); - } - else - { - ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels - } - - ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved - ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); - - unsigned int bytesPerPixel = 0; - - switch (accessor.GetFormat()) - { - case PixelFormat_Grayscale8: - ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); - ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); - ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); - bytesPerPixel = 1; - break; - - case PixelFormat_RGB24: - case PixelFormat_RGBA32: - ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); - ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); - ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); - ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); - ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); - bytesPerPixel = 3; - break; - - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16"); - ReplacePlainString(DICOM_TAG_BITS_STORED, "16"); - ReplacePlainString(DICOM_TAG_HIGH_BIT, "15"); - bytesPerPixel = 2; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - assert(bytesPerPixel != 0); - - DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key)); - - unsigned int pitch = accessor.GetWidth() * bytesPerPixel; - Uint8* target = NULL; - pixels->createUint8Array(accessor.GetHeight() * pitch, target); - - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - switch (accessor.GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_Grayscale8: - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch); - target += pitch; - break; - } - - case PixelFormat_RGBA32: - { - // The alpha channel is not supported by the DICOM standard - const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)); - for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4) - { - target[0] = source[0]; - target[1] = source[1]; - target[2] = source[2]; - } - - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - Encoding ParsedDicomFile::GetEncoding() const - { - return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(), - GetDefaultDicomEncoding()); - } - - - void ParsedDicomFile::SetEncoding(Encoding encoding) - { - if (encoding == Encoding_Windows1251) - { - // This Cyrillic codepage is not officially supported by the - // DICOM standard. Do not set the SpecificCharacterSet tag. - return; - } - - std::string s = GetDicomSpecificCharacterSet(encoding); - ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s); - } - - void ParsedDicomFile::DatasetToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), - format, flags, maxStringLength, GetDefaultDicomEncoding()); - } - - - void ParsedDicomFile::DatasetToJson(Json::Value& target) - { - FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset()); - } - - - void ParsedDicomFile::HeaderToJson(Json::Value& target, - DicomToJsonFormat format) - { - FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0); - } - - - bool ParsedDicomFile::HasTag(const DicomTag& tag) const - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - return pimpl_->file_->getDataset()->tagExists(key); - } - - - void ParsedDicomFile::EmbedPdf(const std::string& pdf) - { - if (pdf.size() < 5 || // (*) - strncmp("%PDF-", pdf.c_str(), 5) != 0) - { - LOG(ERROR) << "Not a PDF file"; - throw OrthancException(ErrorCode_BadFileFormat); - } - - InvalidateCache(); - - ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); - ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT"); - ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); - ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf"); - //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); - - std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); - - size_t s = pdf.size(); - if (s & 1) - { - // The size of the buffer must be even - s += 1; - } - - Uint8* bytes = NULL; - OFCondition result = element->createUint8Array(s, bytes); - if (!result.good() || bytes == NULL) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) - bytes[s - 1] = 0; - - memcpy(bytes, pdf.c_str(), pdf.size()); - - DcmPolymorphOBOW* obj = element.release(); - result = pimpl_->file_->getDataset()->insert(obj); - - if (!result.good()) - { - delete obj; - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - - bool ParsedDicomFile::ExtractPdf(std::string& pdf) - { - std::string sop, mime; - - if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || - !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || - sop != UID_EncapsulatedPDFStorage || - mime != "application/pdf") - { - return false; - } - - if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) - { - return false; - } - - // Strip the possible pad byte at the end of file, because the - // encapsulated documents must always have an even length. The PDF - // format expects files to end with %%EOF followed by CR/LF. If - // the last character of the file is not a CR or LF, we assume it - // is a pad byte and remove it. - if (pdf.size() > 0) - { - char last = *pdf.rbegin(); - - if (last != 10 && last != 13) - { - pdf.resize(pdf.size() - 1); - } - } - - return true; - } - - - ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, - DicomFromJsonFlags flags) - { - const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; - const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; - - std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers)); - result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding())); - - const Json::Value::Members tags = json.getMemberNames(); - - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); - const Json::Value& value = json[tags[i]]; - - if (tag == DICOM_TAG_PIXEL_DATA || - tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) - { - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result->EmbedContent(value.asString()); - } - } - else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent); - } - } - - return result.release(); - } - - - void ParsedDicomFile::GetRawFrame(std::string& target, - std::string& mime, - unsigned int frameId) - { - if (pimpl_->frameIndex_.get() == NULL) - { - pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_)); - } - - pimpl_->frameIndex_->GetRawFrame(target, frameId); - - E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer(); - switch (transferSyntax) - { - case EXS_JPEGProcess1: - mime = "image/jpeg"; - break; - - case EXS_JPEG2000LosslessOnly: - case EXS_JPEG2000: - mime = "image/jp2"; - break; - - default: - mime = "application/octet-stream"; - break; - } - } - - - void ParsedDicomFile::InvalidateCache() - { - pimpl_->frameIndex_.reset(NULL); - } - - - unsigned int ParsedDicomFile::GetFramesCount() const - { - return DicomFrameIndex::GetFramesCount(*pimpl_->file_); - } - - - void ParsedDicomFile::ChangeEncoding(Encoding target) - { - Encoding source = GetEncoding(); - - if (source != target) // Avoid unnecessary conversion - { - ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target)); - FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target); - } - } - - - void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const - { - FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset()); - } - - - void ParsedDicomFile::ExtractDicomAsJson(Json::Value& target) const - { - FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset()); - } - - - bool ParsedDicomFile::LookupTransferSyntax(std::string& result) - { - return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_); - } - - - bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const - { - DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), - DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); - - DcmDataset& dataset = *pimpl_->file_->getDataset(); - - const char *c = NULL; - if (dataset.findAndGetString(k, c).good() && - c != NULL) - { - result = StringToPhotometricInterpretation(c); - return true; - } - else - { - return false; - } - } -}
--- a/OrthancServer/ParsedDicomFile.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "../Core/DicomFormat/DicomInstanceHasher.h" -#include "../Core/Images/ImageAccessor.h" -#include "../Core/IDynamicObject.h" -#include "../Core/RestApi/RestApiOutput.h" -#include "../Core/Toolbox.h" -#include "ServerEnumerations.h" - -#if !defined(ORTHANC_ENABLE_JPEG) -# error Macro ORTHANC_ENABLE_JPEG must be defined to use this file -#endif - -#if !defined(ORTHANC_ENABLE_PNG) -# error Macro ORTHANC_ENABLE_PNG must be defined to use this file -#endif - -class DcmDataset; -class DcmFileFormat; - -namespace Orthanc -{ - class ParsedDicomFile : public IDynamicObject - { - private: - struct PImpl; - PImpl* pimpl_; - - ParsedDicomFile(ParsedDicomFile& other); - - void CreateFromDicomMap(const DicomMap& source, - Encoding defaultEncoding); - - void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep); - - void UpdateStorageUid(const DicomTag& tag, - const std::string& value, - bool decodeDataUriScheme); - - void InvalidateCache(); - - bool EmbedContentInternal(const std::string& dataUriScheme); - - public: - ParsedDicomFile(bool createIdentifiers); // Create a minimal DICOM instance - - ParsedDicomFile(const DicomMap& map, - Encoding defaultEncoding); - - ParsedDicomFile(const DicomMap& map); - - ParsedDicomFile(const void* content, - size_t size); - - ParsedDicomFile(const std::string& content); - - ParsedDicomFile(DcmDataset& dicom); - - ParsedDicomFile(DcmFileFormat& dicom); - - ~ParsedDicomFile(); - - DcmFileFormat& GetDcmtkObject() const; - - ParsedDicomFile* Clone(); - - void SendPathValue(RestApiOutput& output, - const UriComponents& uri); - - void Answer(RestApiOutput& output); - - void Remove(const DicomTag& tag); - - // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) - void Clear(const DicomTag& tag, - bool onlyIfExists); - - void Replace(const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme, - DicomReplaceMode mode); - - void Replace(const DicomTag& tag, - const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeDataUriScheme, - DicomReplaceMode mode); - - void Insert(const DicomTag& tag, - const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeDataUriScheme); - - void ReplacePlainString(const DicomTag& tag, - const std::string& utf8Value) - { - Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent); - } - - void RemovePrivateTags() - { - RemovePrivateTagsInternal(NULL); - } - - void RemovePrivateTags(const std::set<DicomTag>& toKeep) - { - RemovePrivateTagsInternal(&toKeep); - } - - // WARNING: This function handles the decoding of strings to UTF8 - bool GetTagValue(std::string& value, - const DicomTag& tag); - - DicomInstanceHasher GetHasher(); - - void SaveToMemoryBuffer(std::string& buffer); - - void SaveToFile(const std::string& path); - - void EmbedContent(const std::string& dataUriScheme); - - void EmbedImage(const ImageAccessor& accessor); - -#if (ORTHANC_ENABLE_JPEG == 1 && \ - ORTHANC_ENABLE_PNG == 1) - void EmbedImage(const std::string& mime, - const std::string& content); -#endif - - Encoding GetEncoding() const; - - // WARNING: This function only sets the encoding, it will not - // convert the encoding of the tags. Use "ChangeEncoding()" if need be. - void SetEncoding(Encoding encoding); - - void DatasetToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); - - // This version uses the default parameters for - // FileContentType_DicomAsJson - void DatasetToJson(Json::Value& target); - - void HeaderToJson(Json::Value& target, - DicomToJsonFormat format); - - bool HasTag(const DicomTag& tag) const; - - void EmbedPdf(const std::string& pdf); - - bool ExtractPdf(std::string& pdf); - - void GetRawFrame(std::string& target, // OUT - std::string& mime, // OUT - unsigned int frameId); // IN - - unsigned int GetFramesCount() const; - - static ParsedDicomFile* CreateFromJson(const Json::Value& value, - DicomFromJsonFlags flags); - - void ChangeEncoding(Encoding target); - - void ExtractDicomSummary(DicomMap& target) const; - - void ExtractDicomAsJson(Json::Value& target) const; - - bool LookupTransferSyntax(std::string& result); - - bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; - }; -}
--- a/OrthancServer/PrecompiledHeadersServer.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/PrecompiledHeadersServer.h Tue Aug 29 21:17:35 2017 +0200 @@ -37,42 +37,5 @@ #if ORTHANC_USE_PRECOMPILED_HEADERS == 1 -// DCMTK -#include <dcmtk/dcmdata/dcchrstr.h> -#include <dcmtk/dcmdata/dcdeftag.h> -#include <dcmtk/dcmdata/dcdicent.h> -#include <dcmtk/dcmdata/dcdict.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcistrmf.h> -#include <dcmtk/dcmdata/dcmetinf.h> -#include <dcmtk/dcmdata/dcostrmb.h> -#include <dcmtk/dcmdata/dcpixel.h> -#include <dcmtk/dcmdata/dcpixseq.h> -#include <dcmtk/dcmdata/dcpxitem.h> -#include <dcmtk/dcmdata/dcuid.h> -#include <dcmtk/dcmdata/dcvrae.h> -#include <dcmtk/dcmdata/dcvras.h> -#include <dcmtk/dcmdata/dcvrcs.h> -#include <dcmtk/dcmdata/dcvrda.h> -#include <dcmtk/dcmdata/dcvrds.h> -#include <dcmtk/dcmdata/dcvrdt.h> -#include <dcmtk/dcmdata/dcvrfd.h> -#include <dcmtk/dcmdata/dcvrfl.h> -#include <dcmtk/dcmdata/dcvris.h> -#include <dcmtk/dcmdata/dcvrlo.h> -#include <dcmtk/dcmdata/dcvrlt.h> -#include <dcmtk/dcmdata/dcvrpn.h> -#include <dcmtk/dcmdata/dcvrsh.h> -#include <dcmtk/dcmdata/dcvrsl.h> -#include <dcmtk/dcmdata/dcvrss.h> -#include <dcmtk/dcmdata/dcvrst.h> -#include <dcmtk/dcmdata/dcvrtm.h> -#include <dcmtk/dcmdata/dcvrui.h> -#include <dcmtk/dcmdata/dcvrul.h> -#include <dcmtk/dcmdata/dcvrus.h> -#include <dcmtk/dcmdata/dcvrut.h> -#include <dcmtk/dcmnet/dcasccfg.h> -#include <dcmtk/dcmnet/diutil.h> #endif
--- a/OrthancServer/QueryRetrieveHandler.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/QueryRetrieveHandler.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -35,7 +35,7 @@ #include "QueryRetrieveHandler.h" #include "OrthancInitialization.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" namespace Orthanc
--- a/OrthancServer/Scheduler/ModifyInstanceCommand.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h Tue Aug 29 21:17:35 2017 +0200 @@ -35,7 +35,7 @@ #include "IServerCommand.h" #include "../ServerContext.h" -#include "../DicomModification.h" +#include "../../Core/DicomParsing/DicomModification.h" namespace Orthanc {
--- a/OrthancServer/Search/HierarchicalMatcher.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -36,8 +36,8 @@ #include "../../Core/Logging.h" #include "../../Core/OrthancException.h" -#include "../FromDcmtkBridge.h" -#include "../ToDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/ToDcmtkBridge.h" #include "../OrthancInitialization.h" #include <dcmtk/dcmdata/dcfilefo.h>
--- a/OrthancServer/Search/HierarchicalMatcher.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/Search/HierarchicalMatcher.h Tue Aug 29 21:17:35 2017 +0200 @@ -33,9 +33,8 @@ #pragma once -#include "../../Core/DicomFormat/DicomMap.h" #include "IFindConstraint.h" -#include "../ParsedDicomFile.h" +#include "../../Core/DicomParsing/ParsedDicomFile.h" class DcmItem;
--- a/OrthancServer/Search/IFindConstraint.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/Search/IFindConstraint.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -39,7 +39,7 @@ #include "ValueConstraint.h" #include "WildcardConstraint.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/OrthancException.h" namespace Orthanc
--- a/OrthancServer/Search/LookupIdentifierQuery.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/Search/LookupIdentifierQuery.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -36,7 +36,7 @@ #include "../../Core/OrthancException.h" #include "SetOfResources.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include <cassert>
--- a/OrthancServer/Search/LookupResource.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/Search/LookupResource.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -37,7 +37,7 @@ #include "../../Core/OrthancException.h" #include "../../Core/FileStorage/StorageAccessor.h" #include "../ServerToolbox.h" -#include "../FromDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" namespace Orthanc
--- a/OrthancServer/ServerContext.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/ServerContext.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -38,7 +38,7 @@ #include "../Core/HttpServer/FilesystemHttpSender.h" #include "../Core/HttpServer/HttpStreamTranscoder.h" #include "../Core/Logging.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "ServerToolbox.h" #include "OrthancInitialization.h"
--- a/OrthancServer/ServerContext.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/ServerContext.h Tue Aug 29 21:17:35 2017 +0200 @@ -41,10 +41,10 @@ #include "../Core/RestApi/RestApiOutput.h" #include "../Plugins/Engine/OrthancPlugins.h" #include "DicomInstanceToStore.h" -#include "DicomProtocol/ReusableDicomUserConnection.h" +#include "../Core/DicomNetworking/ReusableDicomUserConnection.h" #include "IServerListener.h" #include "LuaScripting.h" -#include "ParsedDicomFile.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "Scheduler/ServerScheduler.h" #include "ServerIndex.h" #include "OrthancHttpHandler.h"
--- a/OrthancServer/ServerEnumerations.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -317,197 +317,6 @@ } } - - const char* EnumerationToString(ModalityManufacturer manufacturer) - { - switch (manufacturer) - { - case ModalityManufacturer_Generic: - return "Generic"; - - case ModalityManufacturer_GenericNoWildcardInDates: - return "GenericNoWildcardInDates"; - - case ModalityManufacturer_GenericNoUniversalWildcard: - return "GenericNoUniversalWildcard"; - - case ModalityManufacturer_StoreScp: - return "StoreScp"; - - case ModalityManufacturer_ClearCanvas: - return "ClearCanvas"; - - case ModalityManufacturer_Dcm4Chee: - return "Dcm4Chee"; - - case ModalityManufacturer_Vitrea: - return "Vitrea"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(DicomRequestType type) - { - switch (type) - { - case DicomRequestType_Echo: - return "Echo"; - break; - - case DicomRequestType_Find: - return "Find"; - break; - - case DicomRequestType_Get: - return "Get"; - break; - - case DicomRequestType_Move: - return "Move"; - break; - - case DicomRequestType_Store: - return "Store"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - - ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) - { - ModalityManufacturer result; - bool obsolete = false; - - if (manufacturer == "Generic") - { - return ModalityManufacturer_Generic; - } - else if (manufacturer == "GenericNoWildcardInDates") - { - return ModalityManufacturer_GenericNoWildcardInDates; - } - else if (manufacturer == "GenericNoUniversalWildcard") - { - return ModalityManufacturer_GenericNoUniversalWildcard; - } - else if (manufacturer == "ClearCanvas") - { - return ModalityManufacturer_ClearCanvas; - } - else if (manufacturer == "StoreScp") - { - return ModalityManufacturer_StoreScp; - } - else if (manufacturer == "Dcm4Chee") - { - return ModalityManufacturer_Dcm4Chee; - } - else if (manufacturer == "Vitrea") - { - return ModalityManufacturer_Vitrea; - } - else if (manufacturer == "AgfaImpax" || - manufacturer == "SyngoVia") - { - result = ModalityManufacturer_GenericNoWildcardInDates; - obsolete = true; - } - else if (manufacturer == "EFilm2" || - manufacturer == "MedInria") - { - result = ModalityManufacturer_Generic; - obsolete = true; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (obsolete) - { - LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since " - << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc " - << "releases, you should replace it by \"" - << EnumerationToString(result) - << "\" in your configuration file."; - } - - return result; - } - - - const char* EnumerationToString(TransferSyntax syntax) - { - switch (syntax) - { - case TransferSyntax_Deflated: - return "Deflated"; - - case TransferSyntax_Jpeg: - return "JPEG"; - - case TransferSyntax_Jpeg2000: - return "JPEG2000"; - - case TransferSyntax_JpegLossless: - return "JPEG Lossless"; - - case TransferSyntax_Jpip: - return "JPIP"; - - case TransferSyntax_Mpeg2: - return "MPEG2"; - - case TransferSyntax_Rle: - return "RLE"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(DicomVersion version) - { - switch (version) - { - case DicomVersion_2008: - return "2008"; - break; - - case DicomVersion_2017c: - return "2017c"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - DicomVersion StringToDicomVersion(const std::string& version) - { - if (version == "2008") - { - return DicomVersion_2008; - } - else if (version == "2017c") - { - return DicomVersion_2017c; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - bool IsUserMetadata(MetadataType metadata) {
--- a/OrthancServer/ServerEnumerations.h Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/ServerEnumerations.h Tue Aug 29 21:17:35 2017 +0200 @@ -56,75 +56,6 @@ StoreStatus_FilteredOut // Removed by NewInstanceFilter }; - enum ModalityManufacturer - { - ModalityManufacturer_Generic, - ModalityManufacturer_GenericNoWildcardInDates, - ModalityManufacturer_GenericNoUniversalWildcard, - ModalityManufacturer_StoreScp, - ModalityManufacturer_ClearCanvas, - ModalityManufacturer_Dcm4Chee, - ModalityManufacturer_Vitrea - }; - - enum DicomRequestType - { - DicomRequestType_Echo, - DicomRequestType_Find, - DicomRequestType_Get, - DicomRequestType_Move, - DicomRequestType_Store - }; - - enum DicomReplaceMode - { - DicomReplaceMode_InsertIfAbsent, - DicomReplaceMode_ThrowIfAbsent, - DicomReplaceMode_IgnoreIfAbsent - }; - - enum TransferSyntax - { - TransferSyntax_Deflated, - TransferSyntax_Jpeg, - TransferSyntax_Jpeg2000, - TransferSyntax_JpegLossless, - TransferSyntax_Jpip, - TransferSyntax_Mpeg2, - TransferSyntax_Rle - }; - - enum DicomToJsonFormat - { - DicomToJsonFormat_Full, - DicomToJsonFormat_Short, - DicomToJsonFormat_Human - }; - - enum DicomToJsonFlags - { - DicomToJsonFlags_IncludeBinary = (1 << 0), - DicomToJsonFlags_IncludePrivateTags = (1 << 1), - DicomToJsonFlags_IncludeUnknownTags = (1 << 2), - DicomToJsonFlags_IncludePixelData = (1 << 3), - DicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), - DicomToJsonFlags_ConvertBinaryToNull = (1 << 5), - - // Some predefined combinations - DicomToJsonFlags_None = 0, - DicomToJsonFlags_Default = (DicomToJsonFlags_IncludeBinary | - DicomToJsonFlags_IncludePixelData | - DicomToJsonFlags_IncludePrivateTags | - DicomToJsonFlags_IncludeUnknownTags | - DicomToJsonFlags_ConvertBinaryToNull) - }; - - enum DicomFromJsonFlags - { - DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0), - DicomFromJsonFlags_GenerateIdentifiers = (1 << 1) - }; - enum IdentifierConstraintType { IdentifierConstraintType_Equal, @@ -133,12 +64,6 @@ IdentifierConstraintType_Wildcard /* Case sensitive, "*" or "?" are the only allowed wildcards */ }; - enum DicomVersion - { - DicomVersion_2008, - DicomVersion_2017c - }; - /** * WARNING: Do not change the explicit values in the enumerations @@ -228,17 +153,5 @@ const char* EnumerationToString(ChangeType type); - const char* EnumerationToString(ModalityManufacturer manufacturer); - - const char* EnumerationToString(DicomRequestType type); - - const char* EnumerationToString(TransferSyntax syntax); - - const char* EnumerationToString(DicomVersion version); - - ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); - - DicomVersion StringToDicomVersion(const std::string& version); - bool IsUserMetadata(MetadataType type); }
--- a/OrthancServer/ServerIndex.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/ServerIndex.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -41,13 +41,13 @@ #include "ServerIndexChange.h" #include "EmbeddedResources.h" #include "OrthancInitialization.h" -#include "ParsedDicomFile.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "ServerToolbox.h" #include "../Core/Toolbox.h" #include "../Core/Logging.h" #include "../Core/DicomFormat/DicomArray.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include "ServerContext.h" #include "DicomInstanceToStore.h" #include "Search/LookupResource.h"
--- a/OrthancServer/ToDcmtkBridge.cpp Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "PrecompiledHeadersServer.h" -#include "ToDcmtkBridge.h" - -#include <memory> -#include <dcmtk/dcmnet/diutil.h> - -#include "../Core/OrthancException.h" - - -namespace Orthanc -{ - DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr) - { - switch (vr) - { - case ValueRepresentation_ApplicationEntity: - return EVR_AE; - - case ValueRepresentation_AgeString: - return EVR_AS; - - case ValueRepresentation_AttributeTag: - return EVR_AT; - - case ValueRepresentation_CodeString: - return EVR_CS; - - case ValueRepresentation_Date: - return EVR_DA; - - case ValueRepresentation_DecimalString: - return EVR_DS; - - case ValueRepresentation_DateTime: - return EVR_DT; - - case ValueRepresentation_FloatingPointSingle: - return EVR_FL; - - case ValueRepresentation_FloatingPointDouble: - return EVR_FD; - - case ValueRepresentation_IntegerString: - return EVR_IS; - - case ValueRepresentation_LongString: - return EVR_LO; - - case ValueRepresentation_LongText: - return EVR_LT; - - case ValueRepresentation_OtherByte: - return EVR_OB; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_OtherDouble: - return EVR_OD;*/ - - case ValueRepresentation_OtherFloat: - return EVR_OF; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_OtherLong: - return EVR_OL;*/ - - case ValueRepresentation_OtherWord: - return EVR_OW; - - case ValueRepresentation_PersonName: - return EVR_PN; - - case ValueRepresentation_ShortString: - return EVR_SH; - - case ValueRepresentation_SignedLong: - return EVR_SL; - - case ValueRepresentation_Sequence: - return EVR_SQ; - - case ValueRepresentation_SignedShort: - return EVR_SS; - - case ValueRepresentation_ShortText: - return EVR_ST; - - case ValueRepresentation_Time: - return EVR_TM; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_UnlimitedCharacters: - return EVR_UC;*/ - - case ValueRepresentation_UniqueIdentifier: - return EVR_UI; - - case ValueRepresentation_UnsignedLong: - return EVR_UL; - - case ValueRepresentation_Unknown: - return EVR_UN; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_UniversalResource: - return EVR_UR;*/ - - case ValueRepresentation_UnsignedShort: - return EVR_US; - - case ValueRepresentation_UnlimitedText: - return EVR_UT; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -}
--- a/OrthancServer/ToDcmtkBridge.h Tue Aug 29 19:59:01 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017 Osimis, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK != 1 -# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 -#endif - -#include "../Core/DicomFormat/DicomMap.h" -#include <dcmtk/dcmdata/dcdatset.h> - -namespace Orthanc -{ - class ToDcmtkBridge - { - public: - static DcmTagKey Convert(const DicomTag& tag) - { - return DcmTagKey(tag.GetGroup(), tag.GetElement()); - } - - static DcmEVR Convert(ValueRepresentation vr); - }; -}
--- a/OrthancServer/main.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/OrthancServer/main.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -41,15 +41,15 @@ #include "../Core/HttpServer/FilesystemHttpHandler.h" #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" -#include "DicomProtocol/DicomServer.h" -#include "DicomProtocol/ReusableDicomUserConnection.h" +#include "../Core/DicomNetworking/DicomServer.h" +#include "../Core/DicomNetworking/ReusableDicomUserConnection.h" #include "OrthancInitialization.h" #include "ServerContext.h" #include "OrthancFindRequestHandler.h" #include "OrthancMoveRequestHandler.h" #include "ServerToolbox.h" #include "../Plugins/Engine/OrthancPlugins.h" -#include "FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" using namespace Orthanc;
--- a/Plugins/Engine/OrthancPlugins.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -45,13 +45,13 @@ #include "../../Core/Logging.h" #include "../../Core/OrthancException.h" #include "../../Core/Toolbox.h" -#include "../../OrthancServer/FromDcmtkBridge.h" -#include "../../OrthancServer/ToDcmtkBridge.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/DicomParsing/ToDcmtkBridge.h" #include "../../OrthancServer/OrthancInitialization.h" #include "../../OrthancServer/ServerContext.h" #include "../../OrthancServer/ServerToolbox.h" #include "../../OrthancServer/Search/HierarchicalMatcher.h" -#include "../../OrthancServer/Internals/DicomImageDecoder.h" +#include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" #include "../../Core/Compression/ZlibCompressor.h" #include "../../Core/Compression/GzipCompressor.h" #include "../../Core/Images/Image.h"
--- a/Plugins/Engine/OrthancPlugins.h Tue Aug 29 19:59:01 2017 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Tue Aug 29 21:17:35 2017 +0200 @@ -53,14 +53,14 @@ #else +#include "../../Core/DicomNetworking/IFindRequestHandlerFactory.h" +#include "../../Core/DicomNetworking/IMoveRequestHandlerFactory.h" +#include "../../Core/DicomNetworking/IWorklistRequestHandlerFactory.h" #include "../../Core/FileStorage/IStorageArea.h" #include "../../Core/HttpServer/IHttpHandler.h" #include "../../Core/HttpServer/IIncomingHttpRequestFilter.h" +#include "../../OrthancServer/IDicomImageDecoder.h" #include "../../OrthancServer/IServerListener.h" -#include "../../OrthancServer/IDicomImageDecoder.h" -#include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h" -#include "../../OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h" -#include "../../OrthancServer/DicomProtocol/IMoveRequestHandlerFactory.h" #include "OrthancPluginDatabase.h" #include "PluginsManager.h"
--- a/UnitTestsSources/DicomMapTests.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/UnitTestsSources/DicomMapTests.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -36,7 +36,7 @@ #include "../Core/OrthancException.h" #include "../Core/DicomFormat/DicomMap.h" -#include "../OrthancServer/FromDcmtkBridge.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" #include <memory>
--- a/UnitTestsSources/FromDcmtkTests.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -34,9 +34,9 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../OrthancServer/FromDcmtkBridge.h" -#include "../OrthancServer/ToDcmtkBridge.h" -#include "../OrthancServer/DicomModification.h" +#include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Core/DicomParsing/ToDcmtkBridge.h" +#include "../Core/DicomParsing/DicomModification.h" #include "../OrthancServer/ServerToolbox.h" #include "../Core/OrthancException.h" #include "../Core/Images/ImageBuffer.h" @@ -46,8 +46,8 @@ #include "../Core/Images/ImageProcessing.h" #include "../Core/Endianness.h" #include "../Resources/EncodingTests.h" -#include "../OrthancServer/DicomProtocol/DicomFindAnswers.h" -#include "../OrthancServer/Internals/DicomImageDecoder.h" +#include "../Core/DicomNetworking/DicomFindAnswers.h" +#include "../Core/DicomParsing/Internals/DicomImageDecoder.h" #include "../Plugins/Engine/PluginsEnumerations.h" #include <dcmtk/dcmdata/dcelem.h>
--- a/UnitTestsSources/JpegLosslessTests.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/UnitTestsSources/JpegLosslessTests.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -34,13 +34,13 @@ #include "PrecompiledHeadersUnitTests.h" #include "gtest/gtest.h" -#include "../OrthancServer/Internals/DicomImageDecoder.h" +#include "../Core/DicomParsing/Internals/DicomImageDecoder.h" #if ORTHANC_ENABLE_JPEG_LOSSLESS == 1 #include <dcmtk/dcmdata/dcfilefo.h> -#include "../OrthancServer/ParsedDicomFile.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" #include "../Core/OrthancException.h" #include "../Core/Images/ImageBuffer.h" #include "../Core/Images/PngWriter.h"
--- a/UnitTestsSources/MultiThreadingTests.cpp Tue Aug 29 19:59:01 2017 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Tue Aug 29 21:17:35 2017 +0200 @@ -129,7 +129,7 @@ -#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h" +#include "../Core/DicomNetworking/ReusableDicomUserConnection.h" TEST(ReusableDicomUserConnection, DISABLED_Basic) {