# HG changeset patch # User Sebastien Jodogne # Date 1382697758 -7200 # Node ID b58d65608949caa99fe8f21788241b4c318a5c0d # Parent 5651d2c6f6fdd6405794f8703dc6bb9c18474fcb# Parent 5c24273d54f9a844df6f9e046849f730541ea0bc integration find-move-scp -> mainline diff -r 5651d2c6f6fd -r b58d65608949 CMakeLists.txt --- a/CMakeLists.txt Fri Oct 25 10:36:06 2013 +0200 +++ b/CMakeLists.txt Fri Oct 25 12:42:38 2013 +0200 @@ -209,6 +209,8 @@ OrthancServer/ServerContext.cpp OrthancServer/ServerEnumerations.cpp OrthancServer/ServerToolbox.cpp + OrthancServer/OrthancFindRequestHandler.cpp + OrthancServer/OrthancMoveRequestHandler.cpp ) # Ensure autogenerated code is built before building ServerLibrary diff -r 5651d2c6f6fd -r b58d65608949 Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/DicomFormat/DicomMap.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -280,4 +280,110 @@ SetValue(tag, source.GetValue(tag)); } } + + + bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) + { + DicomTag *tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < size; i++) + { + if (tags[i] == tag) + { + return true; + } + } + + return false; + } + + bool DicomMap::IsMainDicomTag(const DicomTag& tag) + { + return (IsMainDicomTag(tag, ResourceType_Patient) || + IsMainDicomTag(tag, ResourceType_Study) || + IsMainDicomTag(tag, ResourceType_Series) || + IsMainDicomTag(tag, ResourceType_Instance)); + } + + + void DicomMap::GetMainDicomTagsInternal(std::set& result, ResourceType level) + { + DicomTag *tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = patientTags; + size = sizeof(patientTags) / sizeof(DicomTag); + break; + + case ResourceType_Study: + tags = studyTags; + size = sizeof(studyTags) / sizeof(DicomTag); + break; + + case ResourceType_Series: + tags = seriesTags; + size = sizeof(seriesTags) / sizeof(DicomTag); + break; + + case ResourceType_Instance: + tags = instanceTags; + size = sizeof(instanceTags) / sizeof(DicomTag); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + for (size_t i = 0; i < size; i++) + { + result.insert(tags[i]); + } + } + + + void DicomMap::GetMainDicomTags(std::set& result, ResourceType level) + { + result.clear(); + GetMainDicomTagsInternal(result, level); + } + + + void DicomMap::GetMainDicomTags(std::set& result) + { + result.clear(); + GetMainDicomTagsInternal(result, ResourceType_Patient); + GetMainDicomTagsInternal(result, ResourceType_Study); + GetMainDicomTagsInternal(result, ResourceType_Series); + GetMainDicomTagsInternal(result, ResourceType_Instance); + } } diff -r 5651d2c6f6fd -r b58d65608949 Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/DicomFormat/DicomMap.h Fri Oct 25 12:42:38 2013 +0200 @@ -35,7 +35,9 @@ #include "DicomTag.h" #include "DicomValue.h" #include "DicomString.h" +#include "../Enumerations.h" +#include #include #include @@ -63,6 +65,8 @@ void ExtractTags(DicomMap& source, const DicomTag* tags, size_t count) const; + + static void GetMainDicomTagsInternal(std::set& result, ResourceType level); public: DicomMap() @@ -148,5 +152,13 @@ void CopyTagIfExists(const DicomMap& source, const DicomTag& tag); + + static bool IsMainDicomTag(const DicomTag& tag, ResourceType level); + + static bool IsMainDicomTag(const DicomTag& tag); + + static void GetMainDicomTags(std::set& result, ResourceType level); + + static void GetMainDicomTags(std::set& result); }; } diff -r 5651d2c6f6fd -r b58d65608949 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/DicomFormat/DicomTag.h Fri Oct 25 12:42:38 2013 +0200 @@ -110,4 +110,9 @@ // DICOM tags used for fMRI (thanks to Will Ryder) static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100); + + // Tags for C-FIND and C-MOVE + static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); + static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052); + static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061); } diff -r 5651d2c6f6fd -r b58d65608949 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/Enumerations.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -33,6 +33,7 @@ #include "Enumerations.h" #include "OrthancException.h" +#include "Toolbox.h" namespace Orthanc { @@ -222,4 +223,54 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + const char* EnumerationToString(ResourceType type) + { + switch (type) + { + case ResourceType_Patient: + return "Patient"; + + case ResourceType_Study: + return "Study"; + + case ResourceType_Series: + return "Series"; + + case ResourceType_Instance: + return "Instance"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + + ResourceType StringToResourceType(const char* type) + { + std::string s(type); + Toolbox::ToUpperCase(s); + + if (s == "PATIENT") + { + return ResourceType_Patient; + } + else if (s == "STUDY") + { + return ResourceType_Study; + } + else if (s == "SERIES") + { + return ResourceType_Series; + } + else if (s == "INSTANCE" || s == "IMAGE") + { + return ResourceType_Instance; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } } diff -r 5651d2c6f6fd -r b58d65608949 Core/Enumerations.h --- a/Core/Enumerations.h Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/Enumerations.h Fri Oct 25 12:42:38 2013 +0200 @@ -228,9 +228,20 @@ FileContentType_Json = 2 }; + enum ResourceType + { + ResourceType_Patient = 1, + ResourceType_Study = 2, + ResourceType_Series = 3, + ResourceType_Instance = 4 + }; const char* EnumerationToString(HttpMethod method); const char* EnumerationToString(HttpStatus status); + + const char* EnumerationToString(ResourceType type); + + ResourceType StringToResourceType(const char* type); } diff -r 5651d2c6f6fd -r b58d65608949 Core/Toolbox.cpp --- a/Core/Toolbox.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/Toolbox.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -42,6 +42,7 @@ #include #include #include +#include #if defined(_WIN32) #include @@ -726,4 +727,60 @@ throw OrthancException(ErrorCode_NotImplemented); } } + + + std::string Toolbox::WildcardToRegularExpression(const std::string& source) + { + // TODO - Speed up this with a regular expression + + std::string result = source; + + // Escape all special characters + boost::replace_all(result, "\\", "\\\\"); + boost::replace_all(result, "^", "\\^"); + boost::replace_all(result, ".", "\\."); + boost::replace_all(result, "$", "\\$"); + boost::replace_all(result, "|", "\\|"); + boost::replace_all(result, "(", "\\("); + boost::replace_all(result, ")", "\\)"); + boost::replace_all(result, "[", "\\["); + boost::replace_all(result, "]", "\\]"); + boost::replace_all(result, "+", "\\+"); + boost::replace_all(result, "/", "\\/"); + boost::replace_all(result, "{", "\\{"); + boost::replace_all(result, "}", "\\}"); + + // Convert wildcards '*' and '?' to their regex equivalents + boost::replace_all(result, "?", "."); + boost::replace_all(result, "*", ".*"); + + return result; + } + + + + void Toolbox::TokenizeString(std::vector& result, + const std::string& value, + char separator) + { + result.clear(); + + std::string currentItem; + + for (size_t i = 0; i < value.size(); i++) + { + if (value[i] == separator) + { + result.push_back(currentItem); + currentItem.clear(); + } + else + { + currentItem.push_back(value[i]); + } + } + + result.push_back(currentItem); + } } + diff -r 5651d2c6f6fd -r b58d65608949 Core/Toolbox.h --- a/Core/Toolbox.h Fri Oct 25 10:36:06 2013 +0200 +++ b/Core/Toolbox.h Fri Oct 25 12:42:38 2013 +0200 @@ -108,5 +108,11 @@ void UrlDecode(std::string& s); Endianness DetectEndianness(); + + std::string WildcardToRegularExpression(const std::string& s); + + void TokenizeString(std::vector& result, + const std::string& source, + char separator); } } diff -r 5651d2c6f6fd -r b58d65608949 NEWS --- a/NEWS Fri Oct 25 10:36:06 2013 +0200 +++ b/NEWS Fri Oct 25 12:42:38 2013 +0200 @@ -1,6 +1,7 @@ Pending changes in the mainline =============================== +* DICOM Query/Retrieve is supported * Possibility to keep the PatientID during an anonymization * Check whether "unzip", "tar" and/or "7-zip" are installed from CMake diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/DatabaseWrapper.cpp --- a/OrthancServer/DatabaseWrapper.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/DatabaseWrapper.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -807,7 +807,7 @@ db_.Execute(query); } - // Sanity check of the version of the database + // Check the version of the database std::string version = GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "Unknown"); bool ok = false; try @@ -817,7 +817,7 @@ // This version of Orthanc is only compatible with version 3 of // the DB schema (since Orthanc 0.3.2) - ok = (v == 3); + ok = (v == 3); } catch (boost::bad_lexical_cast&) { diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/DatabaseWrapper.h diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/DicomProtocol/IApplicationEntityFilter.h --- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Fri Oct 25 12:42:38 2013 +0200 @@ -32,6 +32,8 @@ #pragma once +#include "../ServerEnumerations.h" + #include namespace Orthanc @@ -43,7 +45,11 @@ { } - virtual bool IsAllowed(const std::string& callingIp, - const std::string& callingAet) = 0; + virtual bool IsAllowedConnection(const std::string& callingIp, + const std::string& callingAet) = 0; + + virtual bool IsAllowedRequest(const std::string& callingIp, + const std::string& callingAet, + DicomRequestType type) = 0; }; } diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/DicomProtocol/IFindRequestHandler.h --- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Fri Oct 25 12:42:38 2013 +0200 @@ -47,7 +47,7 @@ { } - virtual void Handle(const DicomMap& input, - DicomFindAnswers& answers) = 0; + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input) = 0; }; } diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/Internals/CommandDispatcher.cpp --- a/OrthancServer/Internals/CommandDispatcher.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -326,6 +326,9 @@ AssociationCleanup(assoc); return NULL; } + + std::string callingIP; + std::string callingTitle; /* check the AETs */ { @@ -347,8 +350,8 @@ return NULL; } - std::string callingIP(/*OFSTRING_GUARD*/(callingIP_C)); - std::string callingTitle(/*OFSTRING_GUARD*/(callingTitle_C)); + callingIP = std::string(/*OFSTRING_GUARD*/(callingIP_C)); + callingTitle = std::string(/*OFSTRING_GUARD*/(callingTitle_C)); std::string calledTitle(/*OFSTRING_GUARD*/(calledTitle_C)); Toolbox::ToUpperCase(callingIP); Toolbox::ToUpperCase(callingTitle); @@ -369,7 +372,7 @@ } if (server.HasApplicationEntityFilter() && - !server.GetApplicationEntityFilter().IsAllowed(callingIP, callingTitle)) + !server.GetApplicationEntityFilter().IsAllowedConnection(callingIP, callingTitle)) { T_ASC_RejectParameters rej = { @@ -416,7 +419,8 @@ LOG(INFO) << " (but no valid presentation contexts)"; } - return new CommandDispatcher(server, assoc); + IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; + return new CommandDispatcher(server, assoc, callingIP, callingTitle, filter); } bool CommandDispatcher::Step() @@ -463,56 +467,94 @@ // Reset the client timeout counter elapsedTimeSinceLastCommand_ = 0; - // in case we received a valid message, process this command - // note that storescp can only process a C-ECHO-RQ and a C-STORE-RQ + // Convert the type of request to Orthanc's internal type + bool supported = false; + DicomRequestType request; switch (msg.CommandField) { - case DIMSE_C_ECHO_RQ: - // process C-ECHO-Request - cond = EchoScp(assoc_, &msg, presID); - break; + 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; - case DIMSE_C_STORE_RQ: - // process C-STORE-Request - if (server_.HasStoreRequestHandlerFactory()) - { - std::auto_ptr handler - (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); - cond = Internals::storeScp(assoc_, &msg, presID, *handler); - } - else - cond = DIMSE_BADCOMMANDTYPE; // Should never happen - break; + default: + // we cannot handle this kind of message + cond = DIMSE_BADCOMMANDTYPE; + LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; + break; + } + - case DIMSE_C_MOVE_RQ: - // process C-MOVE-Request - if (server_.HasMoveRequestHandlerFactory()) + // Check whether this request is allowed by the security filter + if (supported && + filter_ != NULL && + !filter_->IsAllowedRequest(callingIP_, callingAETitle_, request)) + { + LOG(ERROR) << EnumerationToString(request) + << " requests are disallowed for the AET \"" + << callingAETitle_ << "\""; + cond = DIMSE_BADCOMMANDTYPE; + supported = false; + } + + // 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) { - std::auto_ptr handler - (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); - cond = Internals::moveScp(assoc_, &msg, presID, *handler); - } - else - cond = DIMSE_BADCOMMANDTYPE; // Should never happen - break; + case DicomRequestType_Echo: + cond = EchoScp(assoc_, &msg, presID); + break; + + case DicomRequestType_Store: + if (server_.HasStoreRequestHandlerFactory()) // Should always be true + { + std::auto_ptr handler + (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); + cond = Internals::storeScp(assoc_, &msg, presID, *handler); + } + break; - case DIMSE_C_FIND_RQ: - // process C-FIND-Request - if (server_.HasFindRequestHandlerFactory()) - { - std::auto_ptr handler - (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); - cond = Internals::findScp(assoc_, &msg, presID, *handler); + case DicomRequestType_Move: + if (server_.HasMoveRequestHandlerFactory()) // Should always be true + { + std::auto_ptr handler + (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); + cond = Internals::moveScp(assoc_, &msg, presID, *handler); + } + break; + + case DicomRequestType_Find: + if (server_.HasFindRequestHandlerFactory()) // Should always be true + { + std::auto_ptr handler + (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); + cond = Internals::findScp(assoc_, &msg, presID, *handler); + } + break; + + default: + // Should never happen + break; } - else - cond = DIMSE_BADCOMMANDTYPE; // Should never happen - break; - - default: - // we cannot handle this kind of message - cond = DIMSE_BADCOMMANDTYPE; - LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; - break; } } else diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/Internals/CommandDispatcher.h --- a/OrthancServer/Internals/CommandDispatcher.h Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.h Fri Oct 25 12:42:38 2013 +0200 @@ -50,12 +50,21 @@ uint32_t elapsedTimeSinceLastCommand_; const DicomServer& server_; T_ASC_Association* assoc_; + std::string callingIP_; + std::string callingAETitle_; + IApplicationEntityFilter* filter_; public: CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc) : + T_ASC_Association* assoc, + const std::string& callingIP, + const std::string& callingAETitle, + IApplicationEntityFilter* filter) : server_(server), - assoc_(assoc) + assoc_(assoc), + callingIP_(callingIP), + callingAETitle_(callingAETitle), + filter_(filter) { clientTimeout_ = server.GetClientTimeout(); elapsedTimeSinceLastCommand_ = 0; diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/Internals/FindScp.cpp --- a/OrthancServer/Internals/FindScp.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -74,7 +74,7 @@ try { - data.handler_->Handle(data.input_, data.answers_); + data.handler_->Handle(data.answers_, data.input_); } catch (OrthancException& e) { diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/Internals/MoveScp.cpp --- a/OrthancServer/Internals/MoveScp.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/Internals/MoveScp.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -82,6 +82,13 @@ try { data.iterator_.reset(data.handler_->Handle(data.target_, data.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; diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancFindRequestHandler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -0,0 +1,440 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * 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 . + **/ + +#include "OrthancFindRequestHandler.h" + +#include +#include + +#include "../Core/DicomFormat/DicomArray.h" +#include "ServerToolbox.h" +#include "OrthancInitialization.h" + +namespace Orthanc +{ + static bool IsWildcard(const std::string& constraint) + { + return (constraint.find('-') != std::string::npos || + constraint.find('*') != std::string::npos || + constraint.find('\\') != std::string::npos || + constraint.find('?') != std::string::npos); + } + + static std::string ToLowerCase(const std::string& s) + { + std::string result = s; + Toolbox::ToLowerCase(result); + return result; + } + + static bool ApplyRangeConstraint(const std::string& value, + const std::string& constraint) + { + size_t separator = constraint.find('-'); + std::string lower = ToLowerCase(constraint.substr(0, separator)); + std::string upper = ToLowerCase(constraint.substr(separator + 1)); + std::string v = ToLowerCase(value); + + if (lower.size() == 0 && upper.size() == 0) + { + return false; + } + + if (lower.size() == 0) + { + return v <= upper; + } + + if (upper.size() == 0) + { + return v >= lower; + } + + return (v >= lower && v <= upper); + } + + + static bool ApplyListConstraint(const std::string& value, + const std::string& constraint) + { + std::string v1 = ToLowerCase(value); + + std::vector items; + Toolbox::TokenizeString(items, constraint, '\\'); + + for (size_t i = 0; i < items.size(); i++) + { + Toolbox::ToLowerCase(items[i]); + if (items[i] == v1) + { + return true; + } + } + + return false; + } + + + static bool Matches(const std::string& value, + const std::string& constraint) + { + // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained + // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html + + if (constraint.find('-') != std::string::npos) + { + return ApplyRangeConstraint(value, constraint); + } + + if (constraint.find('\\') != std::string::npos) + { + return ApplyListConstraint(value, constraint); + } + + if (constraint.find('*') != std::string::npos || + constraint.find('?') != std::string::npos) + { + // TODO - Cache the constructed regular expression + boost::regex pattern(Toolbox::WildcardToRegularExpression(constraint), + boost::regex::icase /* case insensitive search */); + return boost::regex_match(value, pattern); + } + else + { + return ToLowerCase(value) == ToLowerCase(constraint); + } + } + + + static bool LookupOneInstance(std::string& result, + ServerIndex& index, + const std::string& id, + ResourceType type) + { + if (type == ResourceType_Instance) + { + result = id; + return true; + } + + std::string childId; + + { + std::list children; + index.GetChildInstances(children, id); + + if (children.size() == 0) + { + return false; + } + + childId = children.front(); + } + + return LookupOneInstance(result, index, childId, GetChildResourceType(type)); + } + + + static bool Matches(const Json::Value& resource, + const DicomArray& query) + { + for (size_t i = 0; i < query.GetSize(); i++) + { + if (query.GetElement(i).GetValue().IsNull() || + query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL || + query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET || + query.GetElement(i).GetTag() == DICOM_TAG_MODALITIES_IN_STUDY) + { + continue; + } + + std::string tag = query.GetElement(i).GetTag().Format(); + std::string value; + if (resource.isMember(tag)) + { + value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); + } + + if (!Matches(value, query.GetElement(i).GetValue().AsString())) + { + return false; + } + } + + return true; + } + + + static void AddAnswer(DicomFindAnswers& answers, + const Json::Value& resource, + const DicomArray& query) + { + DicomMap result; + + for (size_t i = 0; i < query.GetSize(); i++) + { + if (query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL && + query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + std::string tag = query.GetElement(i).GetTag().Format(); + std::string value; + if (resource.isMember(tag)) + { + value = resource.get(tag, Json::arrayValue).get("Value", "").asString(); + result.SetValue(query.GetElement(i).GetTag(), value); + } + } + } + + answers.Add(result); + } + + + static bool ApplyModalitiesInStudyFilter(std::list& filteredStudies, + const std::list& studies, + const DicomMap& input, + ServerIndex& index) + { + filteredStudies.clear(); + + const DicomValue& v = input.GetValue(DICOM_TAG_MODALITIES_IN_STUDY); + if (v.IsNull()) + { + return false; + } + + // Move the allowed modalities into a "std::set" + std::vector tmp; + Toolbox::TokenizeString(tmp, v.AsString(), '\\'); + + std::set modalities; + for (size_t i = 0; i < tmp.size(); i++) + { + modalities.insert(tmp[i]); + } + + // Loop over the studies + for (std::list::const_iterator + it = studies.begin(); it != studies.end(); it++) + { + try + { + // We are considering a single study. Check whether one of + // its child series matches one of the modalities. + Json::Value study; + if (index.LookupResource(study, *it, ResourceType_Study)) + { + // Loop over the series of the considered study. + for (Json::Value::ArrayIndex j = 0; j < study["Series"].size(); j++) // (*) + { + Json::Value series; + if (index.LookupResource(series, study["Series"][j].asString(), ResourceType_Series)) + { + // Get the modality of this series + if (series["MainDicomTags"].isMember("Modality")) + { + std::string modality = series["MainDicomTags"]["Modality"].asString(); + if (modalities.find(modality) != modalities.end()) + { + // This series of the considered study matches one + // of the required modalities. Take the study into + // consideration for future filtering. + filteredStudies.push_back(*it); + + // We have finished considering this study. Break the study loop at (*). + break; + } + } + } + } + } + } + catch (OrthancException&) + { + // This resource has probably been deleted during the find request + } + } + + return true; + } + + + static bool LookupCandidateResourcesInternal(/* out */ std::list& resources, + /* in */ ServerIndex& index, + /* in */ ResourceType level, + /* in */ const DicomMap& query, + /* in */ DicomTag tag) + { + if (query.HasTag(tag)) + { + const DicomValue& value = query.GetValue(tag); + if (!value.IsNull()) + { + std::string str = query.GetValue(tag).AsString(); + if (!IsWildcard(str)) + { + index.LookupTagValue(resources, tag, str/*, level*/); + return true; + } + } + } + + return false; + } + + + static void LookupCandidateResources(/* out */ std::list& resources, + /* in */ ServerIndex& index, + /* in */ ResourceType level, + /* in */ const DicomMap& query) + { + // TODO : Speed up using full querying against the MainDicomTags. + + resources.clear(); + + bool done = false; + + switch (level) + { + case ResourceType_Patient: + done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_PATIENT_ID); + break; + + case ResourceType_Study: + done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_STUDY_INSTANCE_UID); + break; + + case ResourceType_Series: + done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SERIES_INSTANCE_UID); + break; + + case ResourceType_Instance: + done = LookupCandidateResourcesInternal(resources, index, level, query, DICOM_TAG_SOP_INSTANCE_UID); + break; + + default: + break; + } + + if (!done) + { + Json::Value allResources; + index.GetAllUuids(allResources, level); + assert(allResources.type() == Json::arrayValue); + + for (Json::Value::ArrayIndex i = 0; i < allResources.size(); i++) + { + resources.push_back(allResources[i].asString()); + } + } + } + + + void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, + const DicomMap& input) + { + /** + * Retrieve the query level. + **/ + + const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + if (levelTmp == NULL) + { + throw OrthancException(ErrorCode_BadRequest); + } + + ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); + + if (level != ResourceType_Patient && + level != ResourceType_Study && + level != ResourceType_Series) + { + throw OrthancException(ErrorCode_NotImplemented); + } + + + /** + * Retrieve the candidate resources for this query level. Whenever + * possible, we avoid returning ALL the resources for this query + * level, as it would imply reading the JSON file on the harddisk + * for each of them. + **/ + + std::list resources; + LookupCandidateResources(resources, context_.GetIndex(), level, input); + + + /** + * Apply filtering on modalities for studies, if asked (this is an + * extension to standard DICOM) + * http://www.medicalconnections.co.uk/kb/Filtering_on_and_Retrieving_the_Modality_in_a_C_FIND + **/ + + if (level == ResourceType_Study && + input.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) + { + std::list filtered; + if (ApplyModalitiesInStudyFilter(filtered, resources, input, context_.GetIndex())) + { + resources = filtered; + } + } + + + /** + * Loop over all the resources for this query level. + **/ + + DicomArray query(input); + for (std::list::const_iterator + resource = resources.begin(); resource != resources.end(); resource++) + { + try + { + std::string instance; + if (LookupOneInstance(instance, context_.GetIndex(), *resource, level)) + { + Json::Value info; + context_.ReadJson(info, instance); + + if (Matches(info, query)) + { + AddAnswer(answers, info, query); + } + } + } + catch (OrthancException&) + { + // This resource has probably been deleted during the find request + } + } + } +} diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancFindRequestHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancFindRequestHandler.h Fri Oct 25 12:42:38 2013 +0200 @@ -0,0 +1,54 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * 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 . + **/ + +#pragma once + +#include "DicomProtocol/IFindRequestHandler.h" + +#include "ServerContext.h" + +namespace Orthanc +{ + class OrthancFindRequestHandler : public IFindRequestHandler + { + private: + ServerContext& context_; + + public: + OrthancFindRequestHandler(ServerContext& context) : + context_(context) + { + } + + virtual void Handle(DicomFindAnswers& answers, + const DicomMap& input); + }; +} diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancInitialization.cpp --- a/OrthancServer/OrthancInitialization.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -227,11 +227,11 @@ - void GetDicomModality(const std::string& name, - std::string& aet, - std::string& address, - int& port, - ModalityManufacturer& manufacturer) + void GetDicomModalityUsingSymbolicName(const std::string& name, + std::string& aet, + std::string& address, + int& port, + ModalityManufacturer& manufacturer) { boost::mutex::scoped_lock lock(globalMutex_); @@ -464,4 +464,87 @@ target.push_back(lst[i].asString()); } } + + + void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, + const std::string& name) + { + std::string aet, address; + int port; + ModalityManufacturer manufacturer; + GetDicomModalityUsingSymbolicName(name, aet, address, port, manufacturer); + + LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; + + connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + connection.SetDistantApplicationEntityTitle(aet); + connection.SetDistantHost(address); + connection.SetDistantPort(port); + connection.SetDistantManufacturer(manufacturer); + connection.Open(); + } + + + bool LookupDicomModalityUsingAETitle(const std::string& aet, + std::string& symbolicName, + std::string& address, + int& port, + ModalityManufacturer& manufacturer) + { + std::set modalities; + GetListOfDicomModalities(modalities); + + for (std::set::const_iterator + it = modalities.begin(); it != modalities.end(); it++) + { + try + { + std::string thisAet; + GetDicomModalityUsingSymbolicName(*it, thisAet, address, port, manufacturer); + + if (aet == thisAet) + { + return true; + } + } + catch (OrthancException&) + { + } + } + + return false; + } + + + bool IsKnownAETitle(const std::string& aet) + { + std::string symbolicName, address; + int port; + ModalityManufacturer manufacturer; + + return LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer); + } + + + void ConnectToModalityUsingAETitle(DicomUserConnection& connection, + const std::string& aet) + { + std::string symbolicName, address; + int port; + ModalityManufacturer manufacturer; + + if (!LookupDicomModalityUsingAETitle(aet, symbolicName, address, port, manufacturer)) + { + throw OrthancException("Unknown modality: " + aet); + } + + LOG(WARNING) << "Connecting to remote DICOM modality: AET=" << aet << ", address=" << address << ", port=" << port; + + connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + connection.SetDistantApplicationEntityTitle(aet); + connection.SetDistantHost(address); + connection.SetDistantPort(port); + connection.SetDistantManufacturer(manufacturer); + connection.Open(); + } } diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancInitialization.h --- a/OrthancServer/OrthancInitialization.h Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/OrthancInitialization.h Fri Oct 25 12:42:38 2013 +0200 @@ -37,6 +37,7 @@ #include #include #include "../Core/HttpServer/MongooseServer.h" +#include "DicomProtocol/DicomUserConnection.h" #include "ServerEnumerations.h" namespace Orthanc @@ -54,11 +55,17 @@ bool GetGlobalBoolParameter(const std::string& parameter, bool defaultValue); - void GetDicomModality(const std::string& name, - std::string& aet, - std::string& address, - int& port, - ModalityManufacturer& manufacturer); + void GetDicomModalityUsingSymbolicName(const std::string& name, + std::string& aet, + std::string& address, + int& port, + ModalityManufacturer& manufacturer); + + bool LookupDicomModalityUsingAETitle(const std::string& aet, + std::string& symbolicName, + std::string& address, + int& port, + ModalityManufacturer& manufacturer); void GetOrthancPeer(const std::string& name, std::string& url, @@ -78,4 +85,12 @@ void GetGlobalListOfStringsParameter(std::list& target, const std::string& key); + + void ConnectToModalityUsingSymbolicName(DicomUserConnection& connection, + const std::string& name); + + void ConnectToModalityUsingAETitle(DicomUserConnection& connection, + const std::string& aet); + + bool IsKnownAETitle(const std::string& aet); } diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancMoveRequestHandler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -0,0 +1,179 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * 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 . + **/ + +#include "OrthancMoveRequestHandler.h" + +#include + +#include "DicomProtocol/DicomUserConnection.h" +#include "OrthancInitialization.h" + +namespace Orthanc +{ + namespace + { + // Anonymous namespace to avoid clashes between compilation modules + + class OrthancMoveRequestIterator : public IMoveRequestIterator + { + private: + ServerContext& context_; + std::vector instances_; + DicomUserConnection connection_; + size_t position_; + + public: + OrthancMoveRequestIterator(ServerContext& context, + const std::string& target, + const std::string& publicId) : + context_(context), + position_(0) + { + LOG(INFO) << "Sending resource " << publicId << " to modality \"" << target << "\""; + + std::list tmp; + context_.GetIndex().GetChildInstances(tmp, publicId); + + instances_.reserve(tmp.size()); + for (std::list::iterator it = tmp.begin(); it != tmp.end(); it++) + { + instances_.push_back(*it); + } + + ConnectToModalityUsingAETitle(connection_, target); + } + + virtual unsigned int GetSubOperationCount() const + { + return instances_.size(); + } + + virtual Status DoNext() + { + if (position_ >= instances_.size()) + { + return Status_Failure; + } + + const std::string& id = instances_[position_++]; + + std::string dicom; + context_.ReadFile(dicom, id, FileContentType_Dicom); + connection_.Store(dicom); + + return Status_Success; + } + }; + } + + + bool OrthancMoveRequestHandler::LookupResource(std::string& publicId, + DicomTag tag, + const DicomMap& input) + { + if (!input.HasTag(tag)) + { + return false; + } + + std::string value = input.GetValue(tag).AsString(); + + std::list ids; + context_.GetIndex().LookupTagValue(ids, tag, value); + + if (ids.size() != 1) + { + return false; + } + else + { + publicId = ids.front(); + return true; + } + } + + + IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& target, + const DicomMap& input) + { + LOG(WARNING) << "Move-SCU request received for AET \"" << target << "\""; + + + /** + * Retrieve the query level. + **/ + + const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL); + if (levelTmp == NULL) + { + throw OrthancException(ErrorCode_BadRequest); + } + + ResourceType level = StringToResourceType(levelTmp->AsString().c_str()); + + + /** + * Lookup for the resource to be sent. + **/ + + bool ok; + std::string publicId; + + switch (level) + { + case ResourceType_Patient: + ok = LookupResource(publicId, DICOM_TAG_PATIENT_ID, input); + break; + + case ResourceType_Study: + ok = LookupResource(publicId, DICOM_TAG_STUDY_INSTANCE_UID, input); + break; + + case ResourceType_Series: + ok = LookupResource(publicId, DICOM_TAG_SERIES_INSTANCE_UID, input); + break; + + case ResourceType_Instance: + ok = LookupResource(publicId, DICOM_TAG_SOP_INSTANCE_UID, input); + break; + + default: + ok = false; + } + + if (!ok) + { + throw OrthancException(ErrorCode_BadRequest); + } + + return new OrthancMoveRequestIterator(context_, target, publicId); + } +} diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancMoveRequestHandler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancMoveRequestHandler.h Fri Oct 25 12:42:38 2013 +0200 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * 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 . + **/ + +#pragma once + +#include "DicomProtocol/IMoveRequestHandler.h" +#include "ServerContext.h" + +namespace Orthanc +{ + class OrthancMoveRequestHandler : public IMoveRequestHandler + { + private: + ServerContext& context_; + + bool LookupResource(std::string& publicId, + DicomTag tag, + const DicomMap& input); + + public: + OrthancMoveRequestHandler(ServerContext& context) : + context_(context) + { + } + + virtual IMoveRequestIterator* Handle(const std::string& target, + const DicomMap& input); + }; +} diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/OrthancRestApi.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -71,21 +71,6 @@ // DICOM SCU ---------------------------------------------------------------- - static void ConnectToModality(DicomUserConnection& connection, - const std::string& name) - { - std::string aet, address; - int port; - ModalityManufacturer manufacturer; - GetDicomModality(name, aet, address, port, manufacturer); - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.SetDistantManufacturer(manufacturer); - connection.Open(); - } - static bool MergeQueryAndTemplate(DicomMap& result, const std::string& postData) { @@ -118,7 +103,7 @@ } DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); DicomFindAnswers answers; connection.FindPatient(answers, m); @@ -144,7 +129,7 @@ } DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); DicomFindAnswers answers; connection.FindStudy(answers, m); @@ -171,7 +156,7 @@ } DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); DicomFindAnswers answers; connection.FindSeries(answers, m); @@ -199,7 +184,7 @@ } DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); DicomFindAnswers answers; connection.FindInstance(answers, m); @@ -219,7 +204,7 @@ } DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); + ConnectToModalityUsingSymbolicName(connection, call.GetUriComponent("id", "")); DicomFindAnswers patients; connection.FindPatient(patients, m); @@ -350,7 +335,7 @@ } DicomUserConnection connection; - ConnectToModality(connection, remote); + ConnectToModalityUsingSymbolicName(connection, remote); for (std::list::const_iterator it = instances.begin(); it != instances.end(); it++) diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/PrepareDatabase.sql diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/ServerEnumerations.cpp --- a/OrthancServer/ServerEnumerations.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -33,6 +33,7 @@ #include "../Core/OrthancException.h" #include "../Core/EnumerationDictionary.h" +#include "../Core/Toolbox.h" #include @@ -82,27 +83,6 @@ return dictMetadataType_.Translate(str); } - const char* EnumerationToString(ResourceType type) - { - switch (type) - { - case ResourceType_Patient: - return "Patient"; - - case ResourceType_Study: - return "Study"; - - case ResourceType_Series: - return "Series"; - - case ResourceType_Instance: - return "Instance"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - std::string GetBasePath(ResourceType type, const std::string& publicId) { @@ -274,6 +254,38 @@ } + 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) { if (manufacturer == "Generic") @@ -289,6 +301,4 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } - - } diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/ServerEnumerations.h Fri Oct 25 12:42:38 2013 +0200 @@ -33,6 +33,8 @@ #include +#include "../Core/Enumerations.h" + namespace Orthanc { enum SeriesStatus @@ -57,6 +59,15 @@ ModalityManufacturer_ClearCanvas }; + enum DicomRequestType + { + DicomRequestType_Echo, + DicomRequestType_Find, + DicomRequestType_Get, + DicomRequestType_Move, + DicomRequestType_Store + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -71,14 +82,6 @@ GlobalProperty_AnonymizationSequence = 3 }; - enum ResourceType - { - ResourceType_Patient = 1, - ResourceType_Study = 2, - ResourceType_Series = 3, - ResourceType_Instance = 4 - }; - enum MetadataType { MetadataType_Instance_IndexInSeries = 1, @@ -122,8 +125,6 @@ MetadataType StringToMetadata(const std::string& str); - const char* EnumerationToString(ResourceType type); - std::string EnumerationToString(MetadataType type); const char* EnumerationToString(SeriesStatus status); @@ -134,6 +135,8 @@ const char* EnumerationToString(ModalityManufacturer manufacturer); + const char* EnumerationToString(DicomRequestType type); + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); ResourceType GetParentResourceType(ResourceType type); diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/ServerIndex.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -1473,6 +1473,29 @@ void ServerIndex::LookupTagValue(std::list& result, DicomTag tag, + const std::string& value, + ResourceType type) + { + result.clear(); + + boost::mutex::scoped_lock lock(mutex_); + + std::list id; + db_->LookupTagValue(id, tag, value); + + for (std::list::const_iterator + it = id.begin(); it != id.end(); it++) + { + if (db_->GetResourceType(*it) == type) + { + result.push_back(db_->GetPublicId(*it)); + } + } + } + + + void ServerIndex::LookupTagValue(std::list& result, + DicomTag tag, const std::string& value) { result.clear(); diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/ServerIndex.h Fri Oct 25 12:42:38 2013 +0200 @@ -189,6 +189,11 @@ void LookupTagValue(std::list& result, DicomTag tag, + const std::string& value, + ResourceType type); + + void LookupTagValue(std::list& result, + DicomTag tag, const std::string& value); void LookupTagValue(std::list& result, diff -r 5651d2c6f6fd -r b58d65608949 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/OrthancServer/main.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -41,20 +41,23 @@ #include "../Core/Lua/LuaFunctionCall.h" #include "../Core/DicomFormat/DicomArray.h" #include "DicomProtocol/DicomServer.h" +#include "DicomProtocol/DicomUserConnection.h" #include "OrthancInitialization.h" #include "ServerContext.h" +#include "OrthancFindRequestHandler.h" +#include "OrthancMoveRequestHandler.h" using namespace Orthanc; -class MyStoreRequestHandler : public IStoreRequestHandler +class OrthancStoreRequestHandler : public IStoreRequestHandler { private: ServerContext& server_; public: - MyStoreRequestHandler(ServerContext& context) : + OrthancStoreRequestHandler(ServerContext& context) : server_(context) { } @@ -72,47 +75,6 @@ }; -class MyFindRequestHandler : public IFindRequestHandler -{ -private: - ServerContext& context_; - -public: - MyFindRequestHandler(ServerContext& context) : - context_(context) - { - } - - virtual void Handle(const DicomMap& input, - DicomFindAnswers& answers) - { - LOG(WARNING) << "Find-SCU request received"; - DicomArray a(input); - a.Print(stdout); - } -}; - - -class MyMoveRequestHandler : public IMoveRequestHandler -{ -private: - ServerContext& context_; - -public: - MyMoveRequestHandler(ServerContext& context) : - context_(context) - { - } - -public: - virtual IMoveRequestIterator* Handle(const std::string& target, - const DicomMap& input) - { - LOG(WARNING) << "Move-SCU request received"; - return NULL; - } -}; - class MyDicomServerFactory : public IStoreRequestHandlerFactory, @@ -129,17 +91,17 @@ virtual IStoreRequestHandler* ConstructStoreRequestHandler() { - return new MyStoreRequestHandler(context_); + return new OrthancStoreRequestHandler(context_); } virtual IFindRequestHandler* ConstructFindRequestHandler() { - return new MyFindRequestHandler(context_); + return new OrthancFindRequestHandler(context_); } virtual IMoveRequestHandler* ConstructMoveRequestHandler() { - return new MyMoveRequestHandler(context_); + return new OrthancMoveRequestHandler(context_); } void Done() @@ -148,6 +110,38 @@ }; +class OrthancApplicationEntityFilter : public IApplicationEntityFilter +{ +public: + virtual bool IsAllowedConnection(const std::string& /*callingIp*/, + const std::string& /*callingAet*/) + { + return true; + } + + virtual bool IsAllowedRequest(const std::string& /*callingIp*/, + const std::string& callingAet, + DicomRequestType type) + { + if (type == DicomRequestType_Store) + { + // Incoming store requests are always accepted, even from unknown AET + return true; + } + + if (!IsKnownAETitle(callingAet)) + { + LOG(ERROR) << "Unkwnown remote DICOM modality AET: \"" << callingAet << "\""; + return false; + } + else + { + return true; + } + } +}; + + class MyIncomingHttpRequestFilter : public IIncomingHttpRequestFilter { private: @@ -369,16 +363,17 @@ MyDicomServerFactory serverFactory(context); - { // DICOM server DicomServer dicomServer; + OrthancApplicationEntityFilter dicomFilter; dicomServer.SetCalledApplicationEntityTitleCheck(GetGlobalBoolParameter("DicomCheckCalledAet", false)); dicomServer.SetStoreRequestHandlerFactory(serverFactory); - //dicomServer.SetMoveRequestHandlerFactory(serverFactory); - //dicomServer.SetFindRequestHandlerFactory(serverFactory); + dicomServer.SetMoveRequestHandlerFactory(serverFactory); + dicomServer.SetFindRequestHandlerFactory(serverFactory); dicomServer.SetPortNumber(GetGlobalIntegerParameter("DicomPort", 4242)); dicomServer.SetApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + dicomServer.SetApplicationEntityFilter(dicomFilter); // HTTP server MyIncomingHttpRequestFilter httpFilter(context); diff -r 5651d2c6f6fd -r b58d65608949 Resources/CMake/BoostConfiguration.cmake --- a/Resources/CMake/BoostConfiguration.cmake Fri Oct 25 10:36:06 2013 +0200 +++ b/Resources/CMake/BoostConfiguration.cmake Fri Oct 25 12:42:38 2013 +0200 @@ -1,14 +1,14 @@ if (${STATIC_BUILD}) - SET(BOOST_STATIC 1) + set(BOOST_STATIC 1) else() include(FindBoost) - SET(BOOST_STATIC 0) + set(BOOST_STATIC 0) #set(Boost_DEBUG 1) #set(Boost_USE_STATIC_LIBS ON) find_package(Boost - COMPONENTS filesystem thread system date_time) + COMPONENTS filesystem thread system date_time regex) if (NOT Boost_FOUND) message(FATAL_ERROR "Unable to locate Boost on this system") @@ -30,7 +30,7 @@ #if (${Boost_VERSION} LESS 104800) # boost::locale is only available from 1.48.00 #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version") - # SET(BOOST_STATIC 1) + # set(BOOST_STATIC 1) #endif() include_directories(${Boost_INCLUDE_DIRS}) @@ -40,12 +40,12 @@ if (BOOST_STATIC) # Parameters for Boost 1.54.0 - SET(BOOST_NAME boost_1_54_0) - SET(BOOST_BCP_SUFFIX bcpdigest-0.6.2) - SET(BOOST_MD5 "a464288a976ba133f9b325f454cb503d") - SET(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") + set(BOOST_NAME boost_1_54_0) + set(BOOST_BCP_SUFFIX bcpdigest-0.6.2) + set(BOOST_MD5 "a464288a976ba133f9b325f454cb503d") + set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") - SET(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) + set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME}) DownloadPackage( "${BOOST_MD5}" "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz" @@ -80,7 +80,10 @@ message(FATAL_ERROR "Support your platform here") endif() + aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES) + list(APPEND BOOST_SOURCES + ${BOOST_REGEX_SOURCES} ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp diff -r 5651d2c6f6fd -r b58d65608949 UnitTests/ServerIndex.cpp --- a/UnitTests/ServerIndex.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/UnitTests/ServerIndex.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -487,3 +487,11 @@ } + + +TEST(DicomMap, MainTags) +{ + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); + ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); +} diff -r 5651d2c6f6fd -r b58d65608949 UnitTests/main.cpp --- a/UnitTests/main.cpp Fri Oct 25 10:36:06 2013 +0200 +++ b/UnitTests/main.cpp Fri Oct 25 12:42:38 2013 +0200 @@ -127,6 +127,10 @@ t = FromDcmtkBridge::ParseTag("0020-e040"); ASSERT_EQ(0x0020, t.GetGroup()); ASSERT_EQ(0xe040, t.GetElement()); + + // Test ==() and !=() operators + ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020)); + ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020)); } @@ -387,6 +391,13 @@ ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries)); ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate)); + ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT")); + ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy")); + ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs")); + ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance")); + ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE")); + ASSERT_THROW(StringToResourceType("heLLo"), OrthancException); + ASSERT_EQ(2047, StringToMetadata("2047")); ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException); ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024) @@ -480,6 +491,37 @@ } +TEST(Toolbox, Wildcard) +{ + ASSERT_EQ("abcd", Toolbox::WildcardToRegularExpression("abcd")); + ASSERT_EQ("ab.*cd", Toolbox::WildcardToRegularExpression("ab*cd")); + ASSERT_EQ("ab..cd", Toolbox::WildcardToRegularExpression("ab??cd")); + ASSERT_EQ("a.*b.c.*d", Toolbox::WildcardToRegularExpression("a*b?c*d")); + ASSERT_EQ("a\\{b\\]", Toolbox::WildcardToRegularExpression("a{b]")); +} + + +TEST(Toolbox, Tokenize) +{ + std::vector t; + + Toolbox::TokenizeString(t, "", ','); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("", t[0]); + + Toolbox::TokenizeString(t, "abc", ','); + ASSERT_EQ(1, t.size()); + ASSERT_EQ("abc", t[0]); + + Toolbox::TokenizeString(t, "ab,cd,ef,", ','); + ASSERT_EQ(4, t.size()); + ASSERT_EQ("ab", t[0]); + ASSERT_EQ("cd", t[1]); + ASSERT_EQ("ef", t[2]); + ASSERT_EQ("", t[3]); +} + + int main(int argc, char **argv) { // Initialize Google's logging library.