# HG changeset patch # User Sebastien Jodogne # Date 1449481163 -3600 # Node ID 950745f3f48bc87f9c53e0138210b86ff3e7b1c3 # Parent 483f2647974357dc68f29b9686769d6479804b54# Parent 5e7feeb63d1f5a07842655a02c04500fa31a7fab integration mainline->dcmtk-3.6.1 diff -r 483f26479743 -r 950745f3f48b CMakeLists.txt diff -r 483f26479743 -r 950745f3f48b NEWS --- a/NEWS Wed Dec 02 10:05:45 2015 +0100 +++ b/NEWS Mon Dec 07 10:39:23 2015 +0100 @@ -1,11 +1,20 @@ Pending changes in the mainline =============================== +* Fix modality worklists server if some fields are null +* More tolerant "/series/.../ordered-slices" with broken series +* Promiscuous mode is now turned off by default +* Improved logging information if upgrade fails + + +Version 0.9.5 (2015/12/02) +========================== + Major ----- -* Experimental support of DICOM C-Find SCP for modality worklists through plugins -* Support of DICOM C-Find SCU for modality worklists ("/modalities/{dicom}/find-worklist") +* Experimental support of DICOM C-FIND SCP for modality worklists through plugins +* Support of DICOM C-FIND SCU for modality worklists ("/modalities/{dicom}/find-worklist") REST API -------- @@ -43,6 +52,7 @@ - "OrthancPluginDicomBufferToJson()" to convert DICOM to JSON - "OrthancPluginRegisterErrorCode()" to declare custom error codes - "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags + - "OrthancPluginLookupDictionary()" to get information about some DICOM tag - "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API - "OrthancPluginGetInstanceOrigin()" to know through which mechanism an instance was received - "OrthancPluginCreateImage()" and "OrthancPluginCreateImageAccessor()" to create images @@ -64,15 +74,16 @@ Maintenance ----------- -* Full indexation of the patient/study tags to speed up searches and C-Find +* Full indexation of the patient/study tags to speed up searches and C-FIND * Many refactorings, notably of the searching features and of the image decoding -* C-Move SCP for studies using AccessionNumber tag -* Fix issue 4 (C-Store Association not renegotiated on Specific-to-specific transfer syntax change) +* C-MOVE SCP for studies using AccessionNumber tag +* Fix issue 4 (C-STORE Association not renegotiated on Specific-to-specific transfer syntax change) * Fix formatting of multipart HTTP answers * "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos * "--errors" flag lists the error codes that could be returned by Orthanc * Under Windows, the exit status of Orthanc corresponds to the encountered error code * New "AgfaImpax", "EFilm2" and "Vitrea" modality manufacturers +* C-FIND SCP will return tags with sequence value representation * Upgrade to Boost 1.59.0 for static builds @@ -148,7 +159,7 @@ ------- * The configuration can be splitted into several files stored inside the same folder -* Custom setting of the local AET during C-Store SCU (both in Lua and in the REST API) +* Custom setting of the local AET during C-STORE SCU (both in Lua and in the REST API) * Many code refactorings Lua @@ -167,9 +178,9 @@ Fixes ----- -* Fix compatibility issues for C-Find SCU to Siemens Syngo.Via modalities SCP +* Fix compatibility issues for C-FIND SCU to Siemens Syngo.Via modalities SCP * Fix issue 15 (Lua scripts making HTTP requests) -* Fix issue 35 (Characters in PatientID string are not protected for C-Find) +* Fix issue 35 (Characters in PatientID string are not protected for C-FIND) * Fix issue 37 (Hyphens trigger range query even if datatype does not support ranges) @@ -180,7 +191,7 @@ ----- * DICOM Query/Retrieve available from Orthanc Explorer -* C-Move SCU and C-Find SCU are accessible through the REST API +* C-MOVE SCU and C-FIND SCU are accessible through the REST API * "?expand" flag for URIs "/patients", "/studies" and "/series" * "/tools/find" URI to search for DICOM resources from REST * Support of FreeBSD @@ -190,15 +201,15 @@ ----- * Speed-up in Orthanc Explorer for large amount of images -* Speed-up of the C-Find SCP server of Orthanc +* Speed-up of the C-FIND SCP server of Orthanc * Allow replacing PatientID/StudyInstanceUID/SeriesInstanceUID from Lua scripts -* Option "CaseSensitivePN" to enable case-insensitive C-Find SCP +* Option "CaseSensitivePN" to enable case-insensitive C-FIND SCP Fixes ----- * Prevent freeze on C-FIND if no DICOM tag is to be returned -* Fix slow C-Store SCP on recent versions of Linux, if +* Fix slow C-STORE SCP on recent versions of Linux, if USE_SYSTEM_DCMTK is set to OFF (http://forum.dcmtk.org/viewtopic.php?f=1&t=4009) * Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) * Fix issue 32 (Cyrillic symbols): Introduction of the "Windows1251" encoding @@ -269,7 +280,7 @@ * "/instances-tags" to get the tags of all the child instances of a patient/study/series with a single REST call (bulk tags retrieval) -* Configuration/Lua to select the accepted C-Store SCP transfer syntaxes +* Configuration/Lua to select the accepted C-STORE SCP transfer syntaxes * Fix reporting of errors in Orthanc Explorer when sending images to peers/modalities * Installation of plugin SDK in CMake @@ -380,7 +391,7 @@ Version 0.7.5 (2014/05/08) ========================== -* Dynamic negotiation of SOP classes for C-Store SCU +* Dynamic negotiation of SOP classes for C-STORE SCU * Creation of DICOM instances using the REST API * Embedding of images within DICOM instances * Adding/removal/modification of remote modalities/peers through REST @@ -427,8 +438,8 @@ ========================== * Support of Query/Retrieve from medInria -* Accept more transfer syntaxes for C-Store SCP and SCU (notably JPEG) -* Create the meta-header when receiving files through C-Store SCP +* Accept more transfer syntaxes for C-STORE SCP and SCU (notably JPEG) +* Create the meta-header when receiving files through C-STORE SCP * Fixes and improvements thanks to the static analyzer cppcheck @@ -471,7 +482,7 @@ ========================== * Detection of stable patients/studies/series -* C-Find SCU at the instance level +* C-FIND SCU at the instance level * Link from modified to original resource in Orthanc Explorer * Fix of issue #8 * Anonymization of the medical alerts tag (0010,2000) @@ -581,7 +592,7 @@ * The patient/study/series/instances are now indexed by SHA-1 digests of their DICOM Instance IDs (and not by UUIDs anymore): The same DICOM objects are thus always identified by the same Orthanc IDs -* Log of exported instances through DICOM C-Store SCU ("/exported" URI) +* Log of exported instances through DICOM C-STORE SCU ("/exported" URI) * Full refactoring of the DB schema and of the REST API * Introduction of generic classes for REST APIs (in Core/RestApi) diff -r 483f26479743 -r 950745f3f48b OrthancServer/DicomProtocol/DicomFindAnswers.cpp --- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -189,7 +189,7 @@ size_t index, bool simplify) const { - DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Simple : DicomToJsonFormat_Full); + DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full); GetAnswer(index).ToJson(target, format, DicomToJsonFlags_None, 0); } diff -r 483f26479743 -r 950745f3f48b OrthancServer/DicomProtocol/IFindRequestHandler.h --- a/OrthancServer/DicomProtocol/IFindRequestHandler.h Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/DicomProtocol/IFindRequestHandler.h Mon Dec 07 10:39:23 2015 +0100 @@ -45,6 +45,7 @@ virtual void Handle(DicomFindAnswers& answers, const DicomMap& input, + const std::list& sequencesToReturn, const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet) = 0; diff -r 483f26479743 -r 950745f3f48b OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -581,7 +581,7 @@ switch (format) { - case DicomToJsonFormat_Simple: + case DicomToJsonFormat_Human: parent[tagName] = Json::nullValue; return parent[tagName]; @@ -628,7 +628,7 @@ switch (format) { case DicomToJsonFormat_Short: - case DicomToJsonFormat_Simple: + case DicomToJsonFormat_Human: { assert(target.type() == Json::nullValue); targetValue = ⌖ @@ -880,6 +880,21 @@ 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); diff -r 483f26479743 -r 950745f3f48b OrthancServer/Internals/FindScp.cpp --- a/OrthancServer/Internals/FindScp.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/Internals/FindScp.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -148,9 +148,30 @@ { if (data.findHandler_ != NULL) { + std::list 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(*element); + if (sequence.card() != 0) + { + LOG(WARNING) << "Orthanc only supports sequence matching on worklists, " + << "ignoring C-FIND SCU constraint on tag (" << tag.Format() + << ") " << FromDcmtkBridge::GetName(tag); + } + + sequencesToReturn.push_back(tag); + } + } + DicomMap input; FromDcmtkBridge::Convert(input, *requestIdentifiers); - data.findHandler_->Handle(data.answers_, input, + data.findHandler_->Handle(data.answers_, input, sequencesToReturn, *data.remoteIp_, *data.remoteAet_, *data.calledAet_); ok = true; diff -r 483f26479743 -r 950745f3f48b OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -47,19 +47,21 @@ { static void AddAnswer(DicomFindAnswers& answers, const Json::Value& resource, - const DicomArray& query) + const DicomArray& query, + const std::list& sequencesToReturn) { DicomMap result; for (size_t i = 0; i < query.GetSize(); i++) { - // Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052)) if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL) { + // Fix issue 30 on Google Code (QR response missing "Query/Retrieve Level" (008,0052)) result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue()); } else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET) { + // Do not include the encoding, this is handled by class ParsedDicomFile } else { @@ -77,13 +79,46 @@ } } - if (result.GetSize() == 0) + if (result.GetSize() == 0 && + sequencesToReturn.empty()) { LOG(WARNING) << "The C-FIND request does not return any DICOM tag"; } + else if (sequencesToReturn.empty()) + { + answers.Add(result); + } else { - answers.Add(result); + ParsedDicomFile dicom(result); + + for (std::list::const_iterator tag = sequencesToReturn.begin(); + tag != sequencesToReturn.end(); ++tag) + { + std::cout << tag->Format(); + + const Json::Value& source = resource[tag->Format()]; + + if (source.type() == Json::objectValue && + source.isMember("Type") && + source.isMember("Value") && + source["Type"].asString() == "Sequence" && + source["Value"].type() == Json::arrayValue) + { + Json::Value content = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < source["Value"].size(); i++) + { + Json::Value item; + Toolbox::SimplifyTags(item, source["Value"][i], DicomToJsonFormat_Short); + content.append(item); + } + + dicom.Replace(*tag, content, false); + } + } + + answers.Add(dicom); } } @@ -126,6 +161,7 @@ void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, + const std::list& sequencesToReturn, const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet) @@ -181,6 +217,14 @@ } } + for (std::list::const_iterator it = sequencesToReturn.begin(); + it != sequencesToReturn.end(); ++it) + { + LOG(INFO) << " (" << it->Format() + << ") " << FromDcmtkBridge::GetName(*it) + << " : sequence tag whose content will be copied"; + } + /** * Build up the query object. @@ -255,7 +299,7 @@ } else { - AddAnswer(answers, dicom, query); + AddAnswer(answers, dicom, query, sequencesToReturn); } } } diff -r 483f26479743 -r 950745f3f48b OrthancServer/OrthancFindRequestHandler.h --- a/OrthancServer/OrthancFindRequestHandler.h Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.h Mon Dec 07 10:39:23 2015 +0100 @@ -62,6 +62,7 @@ virtual void Handle(DicomFindAnswers& answers, const DicomMap& input, + const std::list& sequencesToReturn, const std::string& remoteIp, const std::string& remoteAet, const std::string& calledAet); diff -r 483f26479743 -r 950745f3f48b OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -209,7 +209,7 @@ context.ReadJson(full, publicId); Json::Value simplified; - Toolbox::SimplifyTags(simplified, full); + Toolbox::SimplifyTags(simplified, full, DicomToJsonFormat_Human); call.GetOutput().AnswerJson(simplified); } else @@ -928,7 +928,7 @@ if (simplify) { Json::Value simplified; - Toolbox::SimplifyTags(simplified, sharedTags); + Toolbox::SimplifyTags(simplified, sharedTags, DicomToJsonFormat_Human); call.GetOutput().AnswerJson(simplified); } else @@ -995,7 +995,7 @@ if (simplify) { Json::Value simplified; - Toolbox::SimplifyTags(simplified, result); + Toolbox::SimplifyTags(simplified, result, DicomToJsonFormat_Human); call.GetOutput().AnswerJson(simplified); } else @@ -1201,7 +1201,7 @@ if (simplify) { Json::Value simplified; - Toolbox::SimplifyTags(simplified, full); + Toolbox::SimplifyTags(simplified, full, DicomToJsonFormat_Human); result[*it] = simplified; } else @@ -1295,7 +1295,7 @@ if (simplify) { Json::Value simplified; - Toolbox::SimplifyTags(simplified, header); + Toolbox::SimplifyTags(simplified, header, DicomToJsonFormat_Human); call.GetOutput().AnswerJson(simplified); } else diff -r 483f26479743 -r 950745f3f48b OrthancServer/Search/HierarchicalMatcher.cpp --- a/OrthancServer/Search/HierarchicalMatcher.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -123,12 +123,12 @@ std::auto_ptr value(FromDcmtkBridge::ConvertLeafElement (*element, DicomToJsonFlags_None, encoding)); - if (value->IsBinary() || - value->IsNull()) + if (value->IsBinary()) { throw OrthancException(ErrorCode_BadRequest); } - else if (value->GetContent().empty()) + else if (value->IsNull() || + value->GetContent().empty()) { // This is an universal matcher constraints_[tag] = NULL; diff -r 483f26479743 -r 950745f3f48b OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/ServerContext.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -189,7 +189,7 @@ resultPublicId = hasher.HashInstance(); Json::Value simplifiedTags; - Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson()); + Toolbox::SimplifyTags(simplifiedTags, dicom.GetJson(), DicomToJsonFormat_Human); // Test if the instance must be filtered out bool accepted = true; diff -r 483f26479743 -r 950745f3f48b OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/ServerEnumerations.h Mon Dec 07 10:39:23 2015 +0100 @@ -109,7 +109,7 @@ { DicomToJsonFormat_Full, DicomToJsonFormat_Short, - DicomToJsonFormat_Simple + DicomToJsonFormat_Human }; enum DicomToJsonFlags diff -r 483f26479743 -r 950745f3f48b OrthancServer/ServerToolbox.cpp --- a/OrthancServer/ServerToolbox.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/ServerToolbox.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -47,7 +47,8 @@ namespace Toolbox { void SimplifyTags(Json::Value& target, - const Json::Value& source) + const Json::Value& source, + DicomToJsonFormat format) { assert(source.isObject()); @@ -57,9 +58,23 @@ for (size_t i = 0; i < members.size(); i++) { const Json::Value& v = source[members[i]]; - const std::string& name = v["Name"].asString(); const std::string& type = v["Type"].asString(); + std::string name; + switch (format) + { + case DicomToJsonFormat_Human: + name = v["Name"].asString(); + break; + + case DicomToJsonFormat_Short: + name = members[i]; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + if (type == "String") { target[name] = v["Value"].asString(); @@ -78,7 +93,7 @@ for (Json::Value::ArrayIndex i = 0; i < array.size(); i++) { Json::Value c; - SimplifyTags(c, array[i]); + SimplifyTags(c, array[i], format); children.append(c); } @@ -307,6 +322,8 @@ tmp != level || !FindOneChildInstance(instance, database, resource, level)) { + LOG(ERROR) << "Cannot find an instance for " << EnumerationToString(level) + << " with identifier " << *it; throw OrthancException(ErrorCode_InternalError); } @@ -314,23 +331,33 @@ FileInfo attachment; if (!database.LookupAttachment(attachment, instance, FileContentType_Dicom)) { + LOG(ERROR) << "Cannot retrieve the DICOM file associated with instance " << database.GetPublicId(instance); throw OrthancException(ErrorCode_InternalError); } - // Read and parse the content of the DICOM file - StorageAccessor accessor(storageArea); + try + { + // Read and parse the content of the DICOM file + StorageAccessor accessor(storageArea); - std::string content; - accessor.Read(content, attachment); + std::string content; + accessor.Read(content, attachment); + + ParsedDicomFile dicom(content); - ParsedDicomFile dicom(content); + // Update the tags of this resource + DicomMap dicomSummary; + dicom.Convert(dicomSummary); - // Update the tags of this resource - DicomMap dicomSummary; - dicom.Convert(dicomSummary); - - database.ClearMainDicomTags(resource); - Toolbox::SetMainDicomTags(database, resource, level, dicomSummary); + database.ClearMainDicomTags(resource); + Toolbox::SetMainDicomTags(database, resource, level, dicomSummary); + } + catch (OrthancException&) + { + LOG(ERROR) << "Cannot decode the DICOM file with UUID " << attachment.GetUuid() + << " associated with instance " << database.GetPublicId(instance); + throw; + } } } } diff -r 483f26479743 -r 950745f3f48b OrthancServer/ServerToolbox.h --- a/OrthancServer/ServerToolbox.h Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/ServerToolbox.h Mon Dec 07 10:39:23 2015 +0100 @@ -42,7 +42,8 @@ namespace Toolbox { void SimplifyTags(Json::Value& target, - const Json::Value& source); + const Json::Value& source, + DicomToJsonFormat format); void LogMissingRequiredTag(const DicomMap& summary); diff -r 483f26479743 -r 950745f3f48b OrthancServer/SliceOrdering.cpp --- a/OrthancServer/SliceOrdering.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/SliceOrdering.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -320,7 +320,8 @@ if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries()) { // The current "IndexInSeries" occurs 2 times: Not a proper ordering - return false; + LOG(WARNING) << "This series contains 2 slices with the same index, trying to display it anyway"; + break; } } @@ -398,6 +399,8 @@ result["Dicom"] = tmp; + Json::Value slicesShort = Json::arrayValue; + tmp.clear(); for (size_t i = 0; i < GetInstancesCount(); i++) { @@ -406,8 +409,16 @@ { tmp.append(base + "/frames/" + boost::lexical_cast(j)); } + + Json::Value tmp2 = Json::arrayValue; + tmp2.append(GetInstanceId(i)); + tmp2.append(0); + tmp2.append(GetFramesCount(i)); + + slicesShort.append(tmp2); } result["Slices"] = tmp; + result["SlicesShort"] = slicesShort; } } diff -r 483f26479743 -r 950745f3f48b OrthancServer/main.cpp --- a/OrthancServer/main.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/OrthancServer/main.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -269,7 +269,7 @@ } } - return Configuration::GetGlobalBoolParameter(configuration, true); + return Configuration::GetGlobalBoolParameter(configuration, false); } }; @@ -835,7 +835,17 @@ LOG(WARNING) << "Upgrading the database from schema version " << currentVersion << " to " << ORTHANC_DATABASE_VERSION; - database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); + + try + { + database.Upgrade(ORTHANC_DATABASE_VERSION, storageArea); + } + catch (OrthancException&) + { + LOG(ERROR) << "Unable to run the automated upgrade, please use the replication instructions: " + << "https://orthanc.chu.ulg.ac.be/book/users/replication.html"; + throw; + } // Sanity check currentVersion = database.GetDatabaseVersion(); @@ -1099,7 +1109,25 @@ * Launch Orthanc. **/ - LOG(WARNING) << "Orthanc version: " << ORTHANC_VERSION; + { + std::string version(ORTHANC_VERSION); + + if (std::string(ORTHANC_VERSION) == "mainline") + { + try + { + boost::filesystem::path exe(Toolbox::GetPathToExecutable()); + std::time_t creation = boost::filesystem::last_write_time(exe); + boost::posix_time::ptime converted(boost::posix_time::from_time_t(creation)); + version += " (" + boost::posix_time::to_iso_string(converted) + ")"; + } + catch (...) + { + } + } + + LOG(WARNING) << "Orthanc version: " << version; + } int status = 0; try diff -r 483f26479743 -r 950745f3f48b Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -44,6 +44,7 @@ #include "../../Core/OrthancException.h" #include "../../Core/Toolbox.h" #include "../../OrthancServer/FromDcmtkBridge.h" +#include "../../OrthancServer/ToDcmtkBridge.h" #include "../../OrthancServer/OrthancInitialization.h" #include "../../OrthancServer/ServerContext.h" #include "../../OrthancServer/ServerToolbox.h" @@ -60,6 +61,8 @@ #include "PluginsEnumerations.h" #include +#include +#include namespace Orthanc { @@ -1187,7 +1190,7 @@ else { Json::Value simplified; - Toolbox::SimplifyTags(simplified, instance.GetJson()); + Toolbox::SimplifyTags(simplified, instance.GetJson(), DicomToJsonFormat_Human); s = writer.write(simplified); } @@ -1590,6 +1593,59 @@ } + + namespace + { + class DictionaryReadLocker + { + private: + const DcmDataDictionary& dictionary_; + + public: + DictionaryReadLocker() : dictionary_(dcmDataDict.rdlock()) + { + } + + ~DictionaryReadLocker() + { + dcmDataDict.unlock(); + } + + const DcmDataDictionary* operator->() + { + return &dictionary_; + } + }; + } + + + void OrthancPlugins::ApplyLookupDictionary(const void* parameters) + { + const _OrthancPluginLookupDictionary& p = + *reinterpret_cast(parameters); + + DicomTag tag(FromDcmtkBridge::ParseTag(p.name)); + DcmTagKey tag2(tag.GetGroup(), tag.GetElement()); + + DictionaryReadLocker locker; + const DcmDictEntry* entry = locker->findEntry(tag2, NULL); + + if (entry == NULL) + { + throw OrthancException(ErrorCode_UnknownDicomTag); + } + else + { + p.target->group = entry->getKey().getGroup(); + p.target->element = entry->getKey().getElement(); + p.target->vr = Plugins::Convert(entry->getEVR()); + p.target->minMultiplicity = static_cast(entry->getVMMin()); + p.target->maxMultiplicity = (entry->getVMMax() == DcmVariableVM ? 0 : static_cast(entry->getVMMax())); + } + } + + + bool OrthancPlugins::InvokeService(SharedLibrary& plugin, _OrthancPluginService service, const void* parameters) @@ -2148,6 +2204,10 @@ ComputeHash(service, parameters); return true; + case _OrthancPluginService_LookupDictionary: + ApplyLookupDictionary(parameters); + return true; + default: { // This service is unknown to the Orthanc plugin engine diff -r 483f26479743 -r 950745f3f48b Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Wed Dec 02 10:05:45 2015 +0100 +++ b/Plugins/Engine/OrthancPlugins.h Mon Dec 07 10:39:23 2015 +0100 @@ -150,6 +150,8 @@ void ApplyCreateImage(_OrthancPluginService service, const void* parameters); + void ApplyLookupDictionary(const void* parameters); + void ComputeHash(_OrthancPluginService service, const void* parameters); diff -r 483f26479743 -r 950745f3f48b Plugins/Engine/PluginsEnumerations.cpp --- a/Plugins/Engine/PluginsEnumerations.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/Plugins/Engine/PluginsEnumerations.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -226,8 +226,8 @@ case OrthancPluginDicomToJsonFormat_Short: return DicomToJsonFormat_Short; - case OrthancPluginDicomToJsonFormat_Simple: - return DicomToJsonFormat_Simple; + case OrthancPluginDicomToJsonFormat_Human: + return DicomToJsonFormat_Human; default: throw OrthancException(ErrorCode_ParameterOutOfRange); @@ -394,6 +394,95 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + OrthancPluginValueRepresentation Convert(DcmEVR vr) + { + switch (vr) + { + case EVR_AE: + return OrthancPluginValueRepresentation_AE; + + case EVR_AS: + return OrthancPluginValueRepresentation_AS; + + case EVR_AT: + return OrthancPluginValueRepresentation_AT; + + case EVR_CS: + return OrthancPluginValueRepresentation_CS; + + case EVR_DA: + return OrthancPluginValueRepresentation_DA; + + case EVR_DS: + return OrthancPluginValueRepresentation_DS; + + case EVR_DT: + return OrthancPluginValueRepresentation_DT; + + case EVR_FD: + return OrthancPluginValueRepresentation_FD; + + case EVR_FL: + return OrthancPluginValueRepresentation_FL; + + case EVR_IS: + return OrthancPluginValueRepresentation_IS; + + case EVR_LO: + return OrthancPluginValueRepresentation_LO; + + case EVR_LT: + return OrthancPluginValueRepresentation_LT; + + case EVR_OB: + return OrthancPluginValueRepresentation_OB; + + case EVR_OF: + return OrthancPluginValueRepresentation_OF; + + case EVR_OW: + return OrthancPluginValueRepresentation_OW; + + case EVR_PN: + return OrthancPluginValueRepresentation_PN; + + case EVR_SH: + return OrthancPluginValueRepresentation_SH; + + case EVR_SL: + return OrthancPluginValueRepresentation_SL; + + case EVR_SQ: + return OrthancPluginValueRepresentation_SQ; + + case EVR_SS: + return OrthancPluginValueRepresentation_SS; + + case EVR_ST: + return OrthancPluginValueRepresentation_ST; + + case EVR_TM: + return OrthancPluginValueRepresentation_TM; + + case EVR_UI: + return OrthancPluginValueRepresentation_UI; + + case EVR_UL: + return OrthancPluginValueRepresentation_UL; + + case EVR_US: + return OrthancPluginValueRepresentation_US; + + case EVR_UT: + return OrthancPluginValueRepresentation_UT; + + case EVR_UN: + default: + return OrthancPluginValueRepresentation_UN; // Unknown + } + } #endif } } diff -r 483f26479743 -r 950745f3f48b Plugins/Engine/PluginsEnumerations.h --- a/Plugins/Engine/PluginsEnumerations.h Wed Dec 02 10:05:45 2015 +0100 +++ b/Plugins/Engine/PluginsEnumerations.h Mon Dec 07 10:39:23 2015 +0100 @@ -69,6 +69,8 @@ #if !defined(ORTHANC_ENABLE_DCMTK) || ORTHANC_ENABLE_DCMTK != 0 DcmEVR Convert(OrthancPluginValueRepresentation vr); + + OrthancPluginValueRepresentation Convert(DcmEVR vr); #endif } } diff -r 483f26479743 -r 950745f3f48b Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Wed Dec 02 10:05:45 2015 +0100 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Mon Dec 07 10:39:23 2015 +0100 @@ -400,6 +400,7 @@ _OrthancPluginService_CreateDicom = 23, _OrthancPluginService_ComputeMd5 = 24, _OrthancPluginService_ComputeSha1 = 25, + _OrthancPluginService_LookupDictionary = 26, /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -682,7 +683,7 @@ { OrthancPluginDicomToJsonFormat_Full = 1, /*!< Full output, with most details */ OrthancPluginDicomToJsonFormat_Short = 2, /*!< Tags output as hexadecimal numbers */ - OrthancPluginDicomToJsonFormat_Simple = 3, /*!< Human-readable JSON */ + OrthancPluginDicomToJsonFormat_Human = 3, /*!< Human-readable JSON */ _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff } OrthancPluginDicomToJsonFormat; @@ -962,6 +963,20 @@ } OrthancPluginContext; + + /** + * @brief An entry in the dictionary of DICOM tags. + **/ + typedef struct + { + uint16_t group; /*!< The group of the tag */ + uint16_t element; /*!< The element of the tag */ + OrthancPluginValueRepresentation vr; /*!< The value representation of the tag */ + uint32_t minMultiplicity; /*!< The minimum multiplicity of the tag */ + uint32_t maxMultiplicity; /*!< The maximum multiplicity of the tag (0 means arbitrary) */ + } OrthancPluginDictionaryEntry; + + /** * @brief Free a string. @@ -4627,6 +4642,41 @@ } } + + + typedef struct + { + OrthancPluginDictionaryEntry* target; + const char* name; + } _OrthancPluginLookupDictionary; + + /** + * @brief Get information about the given DICOM tag. + * + * This functions makes a lookup in the dictionary of DICOM tags + * that are known to Orthanc, and returns information about this + * tag. The tag can be specified using its human-readable name + * (e.g. "PatientName") or a set of two hexadecimal numbers + * (e.g. "0010-0020"). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param target Where to store the information about the tag. + * @param name The name of the DICOM tag. + * @return 0 if success, other value if error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginLookupDictionary( + OrthancPluginContext* context, + OrthancPluginDictionaryEntry* target, + const char* name) + { + _OrthancPluginLookupDictionary params; + params.target = target; + params.name = name; + return context->InvokeService(context, _OrthancPluginService_LookupDictionary, ¶ms); + } + + #ifdef __cplusplus } #endif diff -r 483f26479743 -r 950745f3f48b Plugins/Samples/Basic/Plugin.c --- a/Plugins/Samples/Basic/Plugin.c Wed Dec 02 10:05:45 2015 +0100 +++ b/Plugins/Samples/Basic/Plugin.c Mon Dec 07 10:39:23 2015 +0100 @@ -327,6 +327,7 @@ OrthancPluginMemoryBuffer tmp; char info[1024], *s; int counter, i; + OrthancPluginDictionaryEntry entry; context = c; OrthancPluginLogWarning(context, "Sample plugin is initializing"); @@ -407,6 +408,9 @@ OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA, "ValidationExpiryDate", 1, 1); + OrthancPluginLookupDictionary(context, &entry, "ValidationExpiryDate"); + OrthancPluginLookupDictionary(context, &entry, "0010-0010"); + return 0; } diff -r 483f26479743 -r 950745f3f48b Resources/Configuration.json --- a/Resources/Configuration.json Wed Dec 02 10:05:45 2015 +0100 +++ b/Resources/Configuration.json Mon Dec 07 10:39:23 2015 +0100 @@ -104,7 +104,7 @@ // Whether Orthanc accepts to act as C-Store SCP for unknown storage // SOP classes (aka. "promiscuous mode") - "UnknownSopClassAccepted" : true, + "UnknownSopClassAccepted" : false, diff -r 483f26479743 -r 950745f3f48b UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Wed Dec 02 10:05:45 2015 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Dec 07 10:39:23 2015 +0100 @@ -395,7 +395,7 @@ FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0, Encoding_Ascii); Json::Value c; - Toolbox::SimplifyTags(c, b); + Toolbox::SimplifyTags(c, b, DicomToJsonFormat_Human); a[1]["PatientName"] = "Hello2"; // To remove the Data URI Scheme encoding ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a)); @@ -474,7 +474,7 @@ f.ToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0); Json::Value c; - Toolbox::SimplifyTags(c, b); + Toolbox::SimplifyTags(c, b, DicomToJsonFormat_Human); ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a)); ASSERT_NE(0, c["ReferencedStudySequence"].compare(a)); // Because Data URI Scheme decoding was enabled @@ -516,7 +516,7 @@ f.Replace(DICOM_TAG_PATIENT_NAME, s, false); Json::Value v; - f.ToJson(v, DicomToJsonFormat_Simple, DicomToJsonFlags_Default, 0); + f.ToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i])); } } @@ -709,7 +709,7 @@ (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers))); Json::Value vv; - dicom->ToJson(vv, DicomToJsonFormat_Simple, toJsonFlags, 0); + dicom->ToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0); ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid); ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid); @@ -725,7 +725,7 @@ (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers))); Json::Value vv; - dicom->ToJson(vv, DicomToJsonFormat_Simple, static_cast(DicomToJsonFlags_IncludePixelData), 0); + dicom->ToJson(vv, DicomToJsonFormat_Human, static_cast(DicomToJsonFlags_IncludePixelData), 0); std::string mime, content; Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString());