# HG changeset patch # User Sebastien Jodogne # Date 1614166775 -3600 # Node ID 31f940334496b7b919beb5e667fb5cd90fc5d57c # Parent 59b667dd58a84faa82cc980deb784c9fbefe0993 Handle public tags with "UN" value representation and containing a string (cf. DICOM CP 246) diff -r 59b667dd58a8 -r 31f940334496 NEWS --- a/NEWS Tue Feb 23 20:05:26 2021 +0100 +++ b/NEWS Wed Feb 24 12:39:35 2021 +0100 @@ -6,7 +6,7 @@ * The "dicom-as-json" attachments are not explicitly stored anymore to improve performance * If the storage area doesn't support range reading, or if "StorageCompression" - is enabled, a new type of attachment "dicom-until-pixel-data" is generated. + is enabled, a new type of attachment "dicom-until-pixel-data" is generated * New metadata automatically computed at the instance level: "PixelDataOffset" * New configuration option related to networking: - "Timeout" in "DicomModalities" to set DICOM SCU timeout on a per-modality basis @@ -17,7 +17,8 @@ * API version upgraded to 11 * BREAKING CHANGES: - - External applications should *not* call "/instances/.../attachments/dicom-as-json" anymore + - External applications should not call "/instances/.../attachments/dicom-as-json" anymore, + and should use "/instances/.../tags" instead - "/instances/.../tags" route does not report the tags after "Pixel Data" (7fe0,0010) anymore * "/peers/{id}/store-straight": Synchronously send the DICOM instance in POST body to the peer * New arguments in the REST API: @@ -37,6 +38,7 @@ * Improved precision of floating-point numbers in DICOM-as-JSON and DICOM summary * Optimization in C-STORE SCP by avoiding an unnecessary DICOM parsing * Fix build on big-endian architectures +* Handle public tags with "UN" value representation and containing a string (cf. DICOM CP 246) * The numbering of sequences in Orthanc Explorer now uses the DICOM convention (starts at 1) * Possibility to generate a static library containing the Orthanc Framework diff -r 59b667dd58a8 -r 31f940334496 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue Feb 23 20:05:26 2021 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Wed Feb 24 12:39:35 2021 +0100 @@ -554,6 +554,24 @@ } + static DicomValue* CreateValueFromUtf8String(const DicomTag& tag, + const std::string& utf8, + unsigned int maxStringLength, + const std::set& ignoreTagLength) + { + if (maxStringLength != 0 && + utf8.size() > maxStringLength && + ignoreTagLength.find(tag) == ignoreTagLength.end()) + { + return new DicomValue; // Too long, create a NULL value + } + else + { + return new DicomValue(utf8, false); + } + } + + DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, DicomToJsonFlags flags, unsigned int maxStringLength, @@ -567,28 +585,20 @@ throw OrthancException(ErrorCode_BadParameterType); } - char *c = NULL; - if (element.isaString() && - element.getString(c).good()) { - if (c == NULL) // This case corresponds to the empty string + char *c = NULL; + if (element.isaString() && + element.getString(c).good()) { - return new DicomValue("", false); - } - else - { - std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); - - if (maxStringLength != 0 && - utf8.size() > maxStringLength && - ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + if (c == NULL) // This case corresponds to the empty string { - return new DicomValue; // Too long, create a NULL value + return new DicomValue("", false); } else { - return new DicomValue(utf8, false); + const std::string s(c); + const std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); + return CreateValueFromUtf8String(GetTag(element), utf8, maxStringLength, ignoreTagLength); } } } @@ -596,9 +606,14 @@ if (element.getVR() == EVR_UN) { - // Unknown value representation: Lookup in the dictionary. This - // is notably the case for private tags registered with the - // "Dictionary" configuration option. + /** + * Unknown value representation: Lookup in the dictionary. This + * is notably the case for private tags registered with the + * "Dictionary" configuration option, or for public tags with + * "EVR_UN" in the case of Little Endian Implicit transfer + * syntax (cf. DICOM CP 246). + * ftp://medical.nema.org/medical/dicom/final/cp246_ft.pdf + **/ DictionaryLocker locker; const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), @@ -608,27 +623,49 @@ { Uint8* data = NULL; - // At (*), we do not try and convert to UTF-8, as nothing says - // the encoding of the private tag is the same as that of the - // remaining of the DICOM dataset. Only go for ASCII strings. - - if (element.getUint8Array(data) == EC_Normal && - Toolbox::IsAsciiString(data, element.getLength())) // (*) + if (element.getUint8Array(data) == EC_Normal) { - if (data == NULL) + Uint32 length = element.getLength(); + + if (data == NULL || + length == 0) { return new DicomValue("", false); // Empty string } - else if (maxStringLength != 0 && - element.getLength() > maxStringLength && - ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) + + // Remove the trailing padding, if any + if (length > 0 && + length % 2 == 0 && + data[length - 1] == '\0') + { + length = length - 1; + } + + if (element.getTag().isPrivate()) { - return new DicomValue; // Too long, create a NULL value + // For private tags, we do not try and convert to UTF-8, + // as nothing ensures that the encoding of the private tag + // is the same as that of the remaining of the DICOM + // dataset. Only go for ASCII strings. + if (Toolbox::IsAsciiString(data, length)) + { + const std::string s(reinterpret_cast(data), length); + return CreateValueFromUtf8String(GetTag(element), s, maxStringLength, ignoreTagLength); + } + else + { + // Not a plain ASCII string: Consider it as a binary + // value that is handled in the switch-case below + } } else { - std::string s(reinterpret_cast(data), element.getLength()); - return new DicomValue(s, false); + // For public tags, convert to UTF-8 by using the + // "SpecificCharacterSet" tag, if present. This branch is + // new in Orthanc 1.9.1 (cf. DICOM CP 246). + const std::string s(reinterpret_cast(data), length); + const std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); + return CreateValueFromUtf8String(GetTag(element), utf8, maxStringLength, ignoreTagLength); } } } diff -r 59b667dd58a8 -r 31f940334496 OrthancFramework/Sources/Toolbox.cpp --- a/OrthancFramework/Sources/Toolbox.cpp Tue Feb 23 20:05:26 2021 +0100 +++ b/OrthancFramework/Sources/Toolbox.cpp Wed Feb 24 12:39:35 2021 +0100 @@ -2270,7 +2270,8 @@ target[name] = v["Value"].asString(); } else if (type == "TooLong" || - type == "Null") + type == "Null" || + type == "Binary") { target[name] = Json::nullValue; }