# HG changeset patch # User Sebastien Jodogne # Date 1477923812 -3600 # Node ID a657f7772e6915017c4088023283fc9fc0268139 # Parent e4f8e377782f96799084e1ebc5e4691680445f57 Handling of private tags/creators in the "Dictionary" configuration option diff -r e4f8e377782f -r a657f7772e69 NEWS --- a/NEWS Thu Oct 27 12:37:30 2016 +0200 +++ b/NEWS Mon Oct 31 15:23:32 2016 +0100 @@ -4,6 +4,7 @@ General ------- +* Handling of private tags/creators in the "Dictionary" configuration option * New configuration options: "DicomScuTimeout" and "DicomScpTimeout" REST API @@ -13,6 +14,11 @@ * "Asynchronous" flag for URIs "/modalities/{...}/store" and "/peers/{...}/store" to avoid waiting for the completion of image transfers +Plugins +------- + +* New function: "OrthancPluginRegisterPrivateDictionaryTag()" to register private tags + Maintenance ----------- diff -r e4f8e377782f -r a657f7772e69 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -237,7 +237,8 @@ ValueRepresentation vr, const std::string& name, unsigned int minMultiplicity, - unsigned int maxMultiplicity) + unsigned int maxMultiplicity, + const std::string& privateCreator) { if (minMultiplicity < 1) { @@ -261,14 +262,55 @@ << name << " (multiplicity: " << minMultiplicity << "-" << (arbitrary ? "n" : boost::lexical_cast(maxMultiplicity)) << ")"; - std::auto_ptr entry(new DcmDictEntry(tag.GetGroup(), - tag.GetElement(), - evr, name.c_str(), - static_cast(minMultiplicity), - static_cast(maxMultiplicity), - NULL /* version */, - OFTrue /* doCopyString */, - NULL /* private creator */)); + std::auto_ptr entry; + if (privateCreator.empty()) + { + if (tag.GetGroup() % 2 == 1) + { + char buf[128]; + sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " + "but no private creator was associated with it", + tag.GetGroup(), tag.GetElement()); + LOG(WARNING) << buf; + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast(minMultiplicity), + static_cast(maxMultiplicity), + NULL /* version */, + OFTrue /* doCopyString */, + NULL /* private creator */)); + } + else + { + // "Private Data Elements have an odd Group Number that is not + // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or + // (FFFF,eeee)." + if (tag.GetGroup() % 2 == 0 /* even */ || + tag.GetGroup() == 0x0001 || + tag.GetGroup() == 0x0003 || + tag.GetGroup() == 0x0005 || + tag.GetGroup() == 0x0007 || + tag.GetGroup() == 0xffff) + { + char buf[128]; + sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", + tag.GetGroup(), tag.GetElement()); + LOG(ERROR) << buf; + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + entry.reset(new DcmDictEntry(tag.GetGroup(), + tag.GetElement(), + evr, name.c_str(), + static_cast(minMultiplicity), + static_cast(maxMultiplicity), + "private" /* version */, + OFTrue /* doCopyString */, + privateCreator.c_str())); + } entry->setGroupRangeRestriction(DcmDictRange_Unspecified); entry->setElementRangeRestriction(DcmDictRange_Unspecified); @@ -429,7 +471,7 @@ } /** - * Numberic types + * Numeric types **/ case EVR_SL: // signed long @@ -571,7 +613,7 @@ } // This code gives access to the name of the private tags - const std::string tagName = FromDcmtkBridge::GetName(tag); + std::string tagName = FromDcmtkBridge::GetTagName(element); switch (format) { @@ -818,19 +860,23 @@ } - std::string FromDcmtkBridge::GetName(const DicomTag& t) + + static std::string GetTagNameInternal(DcmTag& tag) { - // Some patches for important tags because of different DICOM - // dictionaries between DCMTK versions - std::string n = t.GetMainTagsName(); - if (n.size() != 0) { - return n; + // Some patches for important tags because of different DICOM + // dictionaries between DCMTK versions + DicomTag tmp(tag.getGroup(), tag.getElement()); + std::string n = tmp.GetMainTagsName(); + if (n.size() != 0) + { + return n; + } + // End of patches } - // End of patches #if 0 - DcmTagKey tag(t.GetGroup(), t.GetElement()); + // This version explicitly calls the dictionary const DcmDataDictionary& dict = dcmDataDict.rdlock(); const DcmDictEntry* entry = dict.findEntry(tag, NULL); @@ -843,7 +889,6 @@ dcmDataDict.unlock(); return s; #else - DcmTag tag(t.GetGroup(), t.GetElement()); const char* name = tag.getTagName(); if (name == NULL) { @@ -857,6 +902,31 @@ } + std::string FromDcmtkBridge::GetTagName(const DicomTag& t, + const std::string& privateCreator) + { + DcmTag tag(t.GetGroup(), t.GetElement()); + + if (!privateCreator.empty()) + { + tag.setPrivateCreator(privateCreator.c_str()); + } + + return GetTagNameInternal(tag); + } + + + std::string FromDcmtkBridge::GetTagName(const DcmElement& element) + { + // Copy the tag to ensure const-correctness of DcmElement. Note + // that the private creator information is also copied. + DcmTag tag(element.getTag()); + + return GetTagNameInternal(tag); + } + + + DicomTag FromDcmtkBridge::ParseTag(const char* name) { if (strlen(name) == 9 && @@ -941,23 +1011,26 @@ for (DicomMap::Map::const_iterator it = values.map_.begin(); it != values.map_.end(); ++it) { + // TODO Inject PrivateCreator if some is available in the DicomMap? + const std::string tagName = GetTagName(it->first, ""); + if (simplify) { if (it->second->IsNull()) { - result[GetName(it->first)] = Json::nullValue; + result[tagName] = Json::nullValue; } else { // TODO IsBinary - result[GetName(it->first)] = it->second->GetContent(); + result[tagName] = it->second->GetContent(); } } else { Json::Value value = Json::objectValue; - value["Name"] = GetName(it->first); + value["Name"] = tagName; if (it->second->IsNull()) { diff -r e4f8e377782f -r a657f7772e69 OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Mon Oct 31 15:23:32 2016 +0100 @@ -34,6 +34,7 @@ #include "ServerEnumerations.h" +#include "../Core/DicomFormat/DicomElement.h" #include "../Core/DicomFormat/DicomMap.h" #include @@ -53,7 +54,8 @@ ValueRepresentation vr, const std::string& name, unsigned int minMultiplicity, - unsigned int maxMultiplicity); + unsigned int maxMultiplicity, + const std::string& privateCreator); static Encoding DetectEncoding(DcmItem& dataset, Encoding defaultEncoding); @@ -94,7 +96,15 @@ DicomToJsonFlags flags, unsigned int maxStringLength); - static std::string GetName(const DicomTag& tag); + static std::string GetTagName(const DicomTag& tag, + const std::string& privateCreator); + + static std::string GetTagName(const DcmElement& element); + + static std::string GetTagName(const DicomElement& element) + { + return GetTagName(element.GetTag(), ""); + } static DicomTag ParseTag(const char* name); diff -r e4f8e377782f -r a657f7772e69 OrthancServer/Internals/FindScp.cpp --- a/OrthancServer/Internals/FindScp.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/Internals/FindScp.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -171,7 +171,7 @@ { LOG(WARNING) << "Orthanc only supports sequence matching on worklists, " << "ignoring C-FIND SCU constraint on tag (" << tag.Format() - << ") " << FromDcmtkBridge::GetName(tag); + << ") " << FromDcmtkBridge::GetTagName(*element); } sequencesToReturn.push_back(tag); diff -r e4f8e377782f -r a657f7772e69 OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -582,7 +582,7 @@ if (!query.GetElement(i).GetValue().IsNull()) { LOG(INFO) << " " << query.GetElement(i).GetTag() - << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) + << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) << " = " << query.GetElement(i).GetValue().GetContent(); } } @@ -591,7 +591,7 @@ it != sequencesToReturn.end(); ++it) { LOG(INFO) << " (" << it->Format() - << ") " << FromDcmtkBridge::GetName(*it) + << ") " << FromDcmtkBridge::GetTagName(*it, "") << " : sequence tag whose content will be copied"; } @@ -604,16 +604,17 @@ for (size_t i = 0; i < query.GetSize(); i++) { - const DicomTag tag = query.GetElement(i).GetTag(); + const DicomElement& element = query.GetElement(i); + const DicomTag tag = element.GetTag(); - if (query.GetElement(i).GetValue().IsNull() || + if (element.GetValue().IsNull() || tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL || tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) { continue; } - std::string value = query.GetElement(i).GetValue().GetContent(); + std::string value = element.GetValue().GetContent(); if (value.size() == 0) { // An empty string corresponds to a "*" wildcard constraint, so we ignore it @@ -637,7 +638,7 @@ else { LOG(INFO) << "Because of a patch for the manufacturer of the remote modality, " - << "ignoring constraint on tag (" << tag.Format() << ") " << FromDcmtkBridge::GetName(tag); + << "ignoring constraint on tag (" << tag.Format() << ") " << FromDcmtkBridge::GetTagName(element); } } diff -r e4f8e377782f -r a657f7772e69 OrthancServer/OrthancInitialization.cpp --- a/OrthancServer/OrthancInitialization.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -394,11 +394,12 @@ const Json::Value& content = configuration["Dictionary"][tags[i]]; if (content.type() != Json::arrayValue || content.size() < 2 || - content.size() > 4 || + content.size() > 5 || content[0].type() != Json::stringValue || content[1].type() != Json::stringValue || (content.size() >= 3 && content[2].type() != Json::intValue) || - (content.size() >= 4 && content[3].type() != Json::intValue)) + (content.size() >= 4 && content[3].type() != Json::intValue) || + (content.size() >= 5 && content[4].type() != Json::stringValue)) { throw OrthancException(ErrorCode_BadFileFormat); } @@ -408,8 +409,9 @@ std::string name = content[1].asString(); unsigned int minMultiplicity = (content.size() >= 2) ? content[2].asUInt() : 1; unsigned int maxMultiplicity = (content.size() >= 3) ? content[3].asUInt() : 1; + std::string privateCreator = (content.size() >= 4) ? content[4].asString() : ""; - FromDcmtkBridge::RegisterDictionaryTag(tag, vr, name, minMultiplicity, maxMultiplicity); + FromDcmtkBridge::RegisterDictionaryTag(tag, vr, name, minMultiplicity, maxMultiplicity, privateCreator); } } diff -r e4f8e377782f -r a657f7772e69 OrthancServer/OrthancMoveRequestHandler.cpp --- a/OrthancServer/OrthancMoveRequestHandler.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/OrthancMoveRequestHandler.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -181,7 +181,7 @@ if (!query.GetElement(i).GetValue().IsNull()) { LOG(INFO) << " " << query.GetElement(i).GetTag() - << " " << FromDcmtkBridge::GetName(query.GetElement(i).GetTag()) + << " " << FromDcmtkBridge::GetTagName(query.GetElement(i)) << " = " << query.GetElement(i).GetValue().GetContent(); } } diff -r e4f8e377782f -r a657f7772e69 OrthancServer/Search/LookupIdentifierQuery.cpp --- a/OrthancServer/Search/LookupIdentifierQuery.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/OrthancServer/Search/LookupIdentifierQuery.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -264,7 +264,7 @@ for (size_t j = 0; j < (*it)->GetSize(); j++) { const Constraint& c = (*it)->GetConstraint(j); - s << FromDcmtkBridge::GetName(c.GetTag()); + s << FromDcmtkBridge::GetTagName(c.GetTag(), ""); switch (c.GetType()) { diff -r e4f8e377782f -r a657f7772e69 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -563,8 +563,8 @@ case _OrthancPluginService_GetFindQueryTagName: { - const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag(); - *operation.resultString = CopyString(FromDcmtkBridge::GetName(tag)); + const DicomElement& element = currentQuery_->GetElement(operation.index); + *operation.resultString = CopyString(FromDcmtkBridge::GetTagName(element)); break; } @@ -2758,7 +2758,17 @@ *reinterpret_cast(parameters); FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element), Plugins::Convert(p.vr), p.name, - p.minMultiplicity, p.maxMultiplicity); + p.minMultiplicity, p.maxMultiplicity, ""); + return true; + } + + case _OrthancPluginService_RegisterPrivateDictionaryTag: + { + const _OrthancPluginRegisterPrivateDictionaryTag& p = + *reinterpret_cast(parameters); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(p.group, p.element), + Plugins::Convert(p.vr), p.name, + p.minMultiplicity, p.maxMultiplicity, p.privateCreator); return true; } diff -r e4f8e377782f -r a657f7772e69 Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Thu Oct 27 12:37:30 2016 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Mon Oct 31 15:23:32 2016 +0100 @@ -116,7 +116,7 @@ #endif #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 -#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 1 +#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 2 #define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 @@ -407,6 +407,7 @@ _OrthancPluginService_LookupDictionary = 26, _OrthancPluginService_CallHttpClient2 = 27, _OrthancPluginService_GenerateUuid = 28, + _OrthancPluginService_RegisterPrivateDictionaryTag = 29, /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -4091,9 +4092,9 @@ /** * @brief Register a new tag into the DICOM dictionary. * - * This function declares a new tag in the dictionary of DICOM tags - * that are known to Orthanc. This function should be used in the - * OrthancPluginInitialize() callback. + * This function declares a new public tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param group The group of the tag. @@ -4104,6 +4105,7 @@ * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means * an arbitrary multiplicity ("n"). * @return 0 if success, other value if error. + * @see OrthancPluginRegisterPrivateDictionaryTag() * @ingroup Toolbox **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDictionaryTag( @@ -4128,6 +4130,60 @@ + typedef struct + { + uint16_t group; + uint16_t element; + OrthancPluginValueRepresentation vr; + const char* name; + uint32_t minMultiplicity; + uint32_t maxMultiplicity; + const char* privateCreator; + } _OrthancPluginRegisterPrivateDictionaryTag; + + /** + * @brief Register a new private tag into the DICOM dictionary. + * + * This function declares a new private tag in the dictionary of + * DICOM tags that are known to Orthanc. This function should be + * used in the OrthancPluginInitialize() callback. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param group The group of the tag. + * @param element The element of the tag. + * @param vr The value representation of the tag. + * @param name The nickname of the tag. + * @param minMultiplicity The minimum multiplicity of the tag (must be above 0). + * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means + * an arbitrary multiplicity ("n"). + * @param privateCreator The private creator of this private tag. + * @return 0 if success, other value if error. + * @see OrthancPluginRegisterDictionaryTag() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterPrivateDictionaryTag( + OrthancPluginContext* context, + uint16_t group, + uint16_t element, + OrthancPluginValueRepresentation vr, + const char* name, + uint32_t minMultiplicity, + uint32_t maxMultiplicity, + const char* privateCreator) + { + _OrthancPluginRegisterPrivateDictionaryTag params; + params.group = group; + params.element = element; + params.vr = vr; + params.name = name; + params.minMultiplicity = minMultiplicity; + params.maxMultiplicity = maxMultiplicity; + params.privateCreator = privateCreator; + + return context->InvokeService(context, _OrthancPluginService_RegisterPrivateDictionaryTag, ¶ms); + } + + typedef struct { diff -r e4f8e377782f -r a657f7772e69 Resources/Configuration.json --- a/Resources/Configuration.json Thu Oct 27 12:37:30 2016 +0200 +++ b/Resources/Configuration.json Mon Oct 31 15:23:32 2016 +0100 @@ -321,9 +321,11 @@ // to Orthanc. Each line must contain the tag (formatted as 2 // hexadecimal numbers), the value representation (2 upcase // characters), a nickname for the tag, possibly the minimum - // multiplicity (> 0 with defaults to 1), and possibly the maximum - // multiplicity (0 means arbitrary multiplicity, defaults to 1). + // multiplicity (> 0 with defaults to 1), possibly the maximum + // multiplicity (0 means arbitrary multiplicity, defaults to 1), and + // possibly the Private Creator (for private tags). "Dictionary" : { // "0014,1020" : [ "DA", "ValidationExpiryDate", 1, 1 ] + // "00e1,10c2" : [ "UI", "PET-CT Multi Modality Name", 1, 1, "ELSCINT1" ] } } diff -r e4f8e377782f -r a657f7772e69 UnitTestsSources/DicomMapTests.cpp --- a/UnitTestsSources/DicomMapTests.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/UnitTestsSources/DicomMapTests.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -204,7 +204,7 @@ if (!ok) { - std::cout << it->Format() << ": " << FromDcmtkBridge::GetName(*it) + std::cout << it->Format() << ": " << FromDcmtkBridge::GetTagName(*it, "") << " not expected at level " << EnumerationToString(level) << std::endl; } diff -r e4f8e377782f -r a657f7772e69 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Thu Oct 27 12:37:30 2016 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Oct 31 15:23:32 2016 +0100 @@ -57,7 +57,7 @@ TEST(DicomFormat, Tag) { - ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010))); + ASSERT_EQ("PatientName", FromDcmtkBridge::GetTagName(DicomTag(0x0010, 0x0010), "")); DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription"); ASSERT_EQ(0x0008, t.GetGroup()); @@ -576,8 +576,8 @@ TEST(ParsedDicomFile, ToJsonFlags1) { - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_PersonName, "MyPrivateTag", 1, 1); - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_PersonName, "MyPrivateTag", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, ""); ParsedDicomFile f(true); f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false); // Even group => public tag @@ -717,9 +717,9 @@ TEST(ParsedDicomFile, FromJson) { - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7057, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1); - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7059, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1); - FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7057, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7059, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, ""); + FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, ""); Json::Value v; const std::string sopClassUid = "1.2.840.10008.5.1.4.1.1.1"; // CR Image Storage: