# HG changeset patch # User Sebastien Jodogne # Date 1582227407 -3600 # Node ID 9dac85e807c2a1040fd30a680d6dc7ff17590231 # Parent 340bdcc298e9f2eb286057decb89a86867072a29# Parent 453c0ece560afd86eecab0d5ff7b4020d33086e9 integration mainline->storage-commitment diff -r 340bdcc298e9 -r 9dac85e807c2 Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Mon Feb 17 17:54:40 2020 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Thu Feb 20 20:36:47 2020 +0100 @@ -1059,8 +1059,8 @@ } - void DicomMap::ExtractMainDicomTagsInternal(const DicomMap& other, - ResourceType level) + void DicomMap::MergeMainDicomTags(const DicomMap& other, + ResourceType level) { const MainDicomTag* tags = NULL; size_t size = 0; @@ -1085,10 +1085,10 @@ void DicomMap::ExtractMainDicomTags(const DicomMap& other) { Clear(); - ExtractMainDicomTagsInternal(other, ResourceType_Patient); - ExtractMainDicomTagsInternal(other, ResourceType_Study); - ExtractMainDicomTagsInternal(other, ResourceType_Series); - ExtractMainDicomTagsInternal(other, ResourceType_Instance); + MergeMainDicomTags(other, ResourceType_Patient); + MergeMainDicomTags(other, ResourceType_Study); + MergeMainDicomTags(other, ResourceType_Series); + MergeMainDicomTags(other, ResourceType_Instance); } diff -r 340bdcc298e9 -r 9dac85e807c2 Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Mon Feb 17 17:54:40 2020 +0100 +++ b/Core/DicomFormat/DicomMap.h Thu Feb 20 20:36:47 2020 +0100 @@ -63,9 +63,6 @@ static void GetMainDicomTagsInternal(std::set& result, ResourceType level); - void ExtractMainDicomTagsInternal(const DicomMap& other, - ResourceType level); - public: DicomMap() { @@ -215,6 +212,9 @@ void Merge(const DicomMap& other); + void MergeMainDicomTags(const DicomMap& other, + ResourceType level); + void ExtractMainDicomTags(const DicomMap& other); bool HasOnlyMainDicomTags() const; diff -r 340bdcc298e9 -r 9dac85e807c2 NEWS --- a/NEWS Mon Feb 17 17:54:40 2020 +0100 +++ b/NEWS Thu Feb 20 20:36:47 2020 +0100 @@ -22,12 +22,23 @@ * New sample plugin: "ConnectivityChecks" * New primitives to handle storage commitment SCP by plugins +Lua +--- + +* New events: + - "OnDeletedPatient", "OnDeletedStudy", "OnDeletedSeries", "OnDeletedInstance": + triggered when a resource is deleted + - "OnUpdatedPatient", "OnUpdatedStudy", "OnUpdatedSeries", "OnUpdatedInstance": + triggered when an attachment or a metadata is updated + Maintenance ----------- * Support of MPEG4 transfer syntaxes in C-Store SCP -* C-Find SCU at Instance level now sets the 0008,0052 tag to IMAGE per default (was INSTANCE). +* C-FIND SCU at Instance level now sets the 0008,0052 tag to IMAGE per default (was INSTANCE). Therefore, the "ClearCanvas" and "Dcm4Chee" modality manufacturer have now been deprecated. +* More strict C-FIND SCP wrt. the DICOM standard: Forbid wildcard + matching on some VRs, ignore main tags below the queried level * Fix issue #65 (Logging improvements) * Fix issue #156 (Chunked Dicom-web transfer uses 100% CPU) * Fix issue #165 (Boundary parameter in multipart Content-Type is too long) diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/LuaScripting.cpp Thu Feb 20 20:36:47 2020 +0100 @@ -259,6 +259,114 @@ }; + class LuaScripting::DeleteEvent : public LuaScripting::IEvent + { + private: + ResourceType level_; + std::string publicId_; + + public: + DeleteEvent(ResourceType level, + const std::string& publicId) : + level_(level), + publicId_(publicId) + { + } + + virtual void Apply(LuaScripting& that) + { + std::string functionName; + + switch (level_) + { + case ResourceType_Patient: + functionName = "OnDeletedPatient"; + break; + + case ResourceType_Study: + functionName = "OnDeletedStudy"; + break; + + case ResourceType_Series: + functionName = "OnDeletedSeries"; + break; + + case ResourceType_Instance: + functionName = "OnDeletedInstance"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(functionName.c_str())) + { + LuaFunctionCall call(lock.GetLua(), functionName.c_str()); + call.PushString(publicId_); + call.Execute(); + } + } + } + }; + + + class LuaScripting::UpdateEvent : public LuaScripting::IEvent + { + private: + ResourceType level_; + std::string publicId_; + + public: + UpdateEvent(ResourceType level, + const std::string& publicId) : + level_(level), + publicId_(publicId) + { + } + + virtual void Apply(LuaScripting& that) + { + std::string functionName; + + switch (level_) + { + case ResourceType_Patient: + functionName = "OnUpdatedPatient"; + break; + + case ResourceType_Study: + functionName = "OnUpdatedStudy"; + break; + + case ResourceType_Series: + functionName = "OnUpdatedSeries"; + break; + + case ResourceType_Instance: + functionName = "OnUpdatedInstance"; + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + { + LuaScripting::Lock lock(that); + + if (lock.GetLua().IsExistingFunction(functionName.c_str())) + { + LuaFunctionCall call(lock.GetLua(), functionName.c_str()); + call.PushString(publicId_); + call.Execute(); + } + } + } + }; + + ServerContext* LuaScripting::GetServerContext(lua_State *state) { const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext"); @@ -738,6 +846,15 @@ { pendingEvents_.Enqueue(new StableResourceEvent(change)); } + else if (change.GetChangeType() == ChangeType_Deleted) + { + pendingEvents_.Enqueue(new DeleteEvent(change.GetResourceType(), change.GetPublicId())); + } + else if (change.GetChangeType() == ChangeType_UpdatedAttachment || + change.GetChangeType() == ChangeType_UpdatedMetadata) + { + pendingEvents_.Enqueue(new UpdateEvent(change.GetResourceType(), change.GetPublicId())); + } } diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/LuaScripting.h --- a/OrthancServer/LuaScripting.h Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/LuaScripting.h Thu Feb 20 20:36:47 2020 +0100 @@ -59,6 +59,8 @@ class OnStoredInstanceEvent; class StableResourceEvent; class JobEvent; + class DeleteEvent; + class UpdateEvent; static ServerContext* GetServerContext(lua_State *state); diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/Search/DatabaseLookup.cpp --- a/OrthancServer/Search/DatabaseLookup.cpp Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/Search/DatabaseLookup.cpp Thu Feb 20 20:36:47 2020 +0100 @@ -198,8 +198,26 @@ AddConstraint(constraint.release()); } - else if (dicomQuery.find('*') != std::string::npos || - dicomQuery.find('?') != std::string::npos) + else if ( + /** + * New test in Orthanc 1.6.0: Wild card matching is only allowed + * for a subset of value representations: AE, CS, LO, LT, PN, + * SH, ST, UC, UR, UT. + * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part04/sect_C.2.2.2.4.html + **/ + (vr == ValueRepresentation_ApplicationEntity || // AE + vr == ValueRepresentation_CodeString || // CS + vr == ValueRepresentation_LongString || // LO + vr == ValueRepresentation_LongText || // LT + vr == ValueRepresentation_PersonName || // PN + vr == ValueRepresentation_ShortString || // SH + vr == ValueRepresentation_ShortText || // ST + vr == ValueRepresentation_UnlimitedCharacters || // UC + vr == ValueRepresentation_UniversalResource || // UR + vr == ValueRepresentation_UnlimitedText // UT + ) && + (dicomQuery.find('*') != std::string::npos || + dicomQuery.find('?') != std::string::npos)) { AddConstraint(new DicomTagConstraint (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive, mandatoryTag)); diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/ServerContext.cpp Thu Feb 20 20:36:47 2020 +0100 @@ -254,6 +254,11 @@ jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2)); saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true); metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true)); + + // New configuration options in Orthanc 1.5.1 + findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always")); + limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0); + limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0); } jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); @@ -796,44 +801,9 @@ size_t since, size_t limit) { - LookupMode mode; - unsigned int databaseLimit; + unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? + limitFindInstances_ : limitFindResults_); - { - // New configuration option in 1.5.1 - OrthancConfiguration::ReaderLock lock; - - std::string value = lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"); - - if (value == "Always") - { - mode = LookupMode_DiskOnLookupAndAnswer; - } - else if (value == "Never") - { - mode = LookupMode_DatabaseOnly; - } - else if (value == "Answers") - { - mode = LookupMode_DiskOnAnswer; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Configuration option \"StorageAccessOnFind\" " - "should be \"Always\", \"Never\" or \"Answers\": " + value); - } - - if (queryLevel == ResourceType_Instance) - { - databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0); - } - else - { - databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0); - } - } - std::vector resources, instances; { @@ -846,6 +816,11 @@ LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size(); + /** + * "resources" contains the Orthanc ID of the resource at level + * "queryLevel", "instances" contains one the Orthanc ID of one + * sample instance from this resource. + **/ assert(resources.size() == instances.size()); size_t countResults = 0; @@ -863,19 +838,54 @@ bool hasOnlyMainDicomTags; DicomMap dicom; - if (mode == LookupMode_DatabaseOnly || - mode == LookupMode_DiskOnAnswer || + if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly || + findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer || lookup.HasOnlyMainDicomTags()) { // Case (1): The main DICOM tags, as stored in the database, // are sufficient to look for match - if (!GetIndex().GetAllMainDicomTags(dicom, instances[i])) + DicomMap tmp; + if (!GetIndex().GetAllMainDicomTags(tmp, instances[i])) { // The instance has been removed during the execution of the // lookup, ignore it continue; } + +#if 1 + // New in Orthanc 1.6.0: Only keep the main DICOM tags at the + // level of interest for the query + switch (queryLevel) + { + // WARNING: Don't reorder cases below, and don't add "break" + case ResourceType_Instance: + dicom.MergeMainDicomTags(tmp, ResourceType_Instance); + + case ResourceType_Series: + dicom.MergeMainDicomTags(tmp, ResourceType_Series); + + case ResourceType_Study: + dicom.MergeMainDicomTags(tmp, ResourceType_Study); + + case ResourceType_Patient: + dicom.MergeMainDicomTags(tmp, ResourceType_Patient); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + // Special case of the "Modality" at the study level, in order + // to deal with C-FIND on "ModalitiesInStudy" (0008,0061). + // Check out integration test "test_rest_modalities_in_study". + if (queryLevel == ResourceType_Study) + { + dicom.CopyTagIfExists(tmp, DICOM_TAG_MODALITY); + } +#else + dicom.Assign(tmp); // This emulates Orthanc <= 1.5.8 +#endif hasOnlyMainDicomTags = true; } @@ -907,8 +917,8 @@ } else { - if ((mode == LookupMode_DiskOnLookupAndAnswer || - mode == LookupMode_DiskOnAnswer) && + if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer || + findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) && dicomAsJson.get() == NULL && isDicomAsJsonNeeded) { diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/ServerContext.h Thu Feb 20 20:36:47 2020 +0100 @@ -85,14 +85,6 @@ private: - enum LookupMode - { - LookupMode_DatabaseOnly, - LookupMode_DiskOnAnswer, - LookupMode_DiskOnLookupAndAnswer - }; - - class LuaServerListener : public IServerListener { private: @@ -221,6 +213,9 @@ std::string defaultLocalAet_; OrthancHttpHandler httpHandler_; bool saveJobs_; + FindStorageAccessMode findStorageAccessMode_; + unsigned int limitFindInstances_; + unsigned int limitFindResults_; std::auto_ptr metricsRegistry_; bool isHttpServerSecure_; diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/ServerEnumerations.cpp --- a/OrthancServer/ServerEnumerations.cpp Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/ServerEnumerations.cpp Thu Feb 20 20:36:47 2020 +0100 @@ -192,6 +192,30 @@ return dictContentType_.Translate(str); } + + FindStorageAccessMode StringToFindStorageAccessMode(const std::string& value) + { + if (value == "Always") + { + return FindStorageAccessMode_DiskOnLookupAndAnswer; + } + else if (value == "Never") + { + return FindStorageAccessMode_DatabaseOnly; + } + else if (value == "Answers") + { + return FindStorageAccessMode_DiskOnAnswer; + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Configuration option \"StorageAccessOnFind\" " + "should be \"Always\", \"Never\" or \"Answers\": " + value); + } + } + + std::string GetBasePath(ResourceType type, const std::string& publicId) { diff -r 340bdcc298e9 -r 9dac85e807c2 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Mon Feb 17 17:54:40 2020 +0100 +++ b/OrthancServer/ServerEnumerations.h Thu Feb 20 20:36:47 2020 +0100 @@ -83,6 +83,13 @@ }; } + enum FindStorageAccessMode + { + FindStorageAccessMode_DatabaseOnly, + FindStorageAccessMode_DiskOnAnswer, + FindStorageAccessMode_DiskOnLookupAndAnswer + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -178,6 +185,8 @@ FileContentType StringToContentType(const std::string& str); + FindStorageAccessMode StringToFindStorageAccessMode(const std::string& str); + std::string EnumerationToString(FileContentType type); std::string GetFileContentMime(FileContentType type);