# HG changeset patch # User Sebastien Jodogne # Date 1549434538 -3600 # Node ID 7724ed267b5cc1f5b28828b0037c56a31630818d # Parent 24a76ed0d8a33c2a47bc7c4a07640a8408539af8 unit testing dicomweb conversion diff -r 24a76ed0d8a3 -r 7724ed267b5c Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Tue Feb 05 21:10:47 2019 +0100 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Wed Feb 06 07:28:58 2019 +0100 @@ -1289,16 +1289,18 @@ case EVR_OB: return ValueRepresentation_OtherByte; - // Not supported as of DCMTK 3.6.0 - /*case EVR_OD: - return ValueRepresentation_OtherDouble;*/ +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OD: + return ValueRepresentation_OtherDouble; +#endif case EVR_OF: return ValueRepresentation_OtherFloat; - // Not supported as of DCMTK 3.6.0 - /*case EVR_OL: - return ValueRepresentation_OtherLong;*/ +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_OL: + return ValueRepresentation_OtherLong; +#endif case EVR_OW: return ValueRepresentation_OtherWord; @@ -1324,9 +1326,10 @@ case EVR_TM: return ValueRepresentation_Time; - // Not supported as of DCMTK 3.6.0 - /*case EVR_UC: - return ValueRepresentation_UnlimitedCharacters;*/ +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_UC: + return ValueRepresentation_UnlimitedCharacters; +#endif case EVR_UI: return ValueRepresentation_UniqueIdentifier; @@ -1337,9 +1340,10 @@ case EVR_UN: return ValueRepresentation_Unknown; - // Not supported as of DCMTK 3.6.0 - /*case EVR_UR: - return ValueRepresentation_UniversalResource;*/ +#if DCMTK_VERSION_NUMBER >= 362 + case EVR_UR: + return ValueRepresentation_UniversalResource; +#endif case EVR_US: return ValueRepresentation_UnsignedShort; @@ -2164,12 +2168,62 @@ const DicomTag& tag, Encoding encoding) { - // TODO - Merge this function with ConvertLeafElement() + // TODO - Merge this function, that is more recent, with ConvertLeafElement() assert(element.isLeaf()); DcmEVR evr = element.getTag().getEVR(); - ValueRepresentation vr = FromDcmtkBridge::Convert(evr); + + + /** + * Fix the EVR for types internal to DCMTK + **/ + + if (evr == EVR_ox) // OB or OW depending on context + { + evr = EVR_OB; + } + + if (evr == EVR_UNKNOWN || // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + evr == EVR_UNKNOWN2B) // used internally for elements with unknown VR with 2-byte length field in explicit VR + { + evr = EVR_UN; + } + + const ValueRepresentation vr = FromDcmtkBridge::Convert(evr); + + + /** + * Deal with binary data (including PixelData). + **/ + + if (evr == EVR_OB || // other byte + evr == EVR_OF || // other float +#if DCMTK_VERSION_NUMBER >= 362 + evr == EVR_OD || // other double + evr == EVR_OL || // other long +#endif + evr == EVR_OW || // other word + evr == EVR_UN) // unknown value representation + { + Uint8* data = NULL; + + if (element.getUint8Array(data) == EC_Normal) + { + visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength()); + } + else + { + visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); + } + + return; // We're done + } + + + /** + * Deal with plain strings (and convert them to UTF-8) + **/ char *c = NULL; if (element.isaString() && @@ -2215,18 +2269,13 @@ try { // http://support.dcmtk.org/docs/dcvr_8h-source.html - switch (element.getVR()) + switch (evr) { /** - * Deal with binary data (including PixelData). + * Plain string values. **/ - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_UN: // unknown value representation - case EVR_ox: // OB or OW depending on context case EVR_DS: // decimal string case EVR_IS: // integer string case EVR_AS: // age string @@ -2242,21 +2291,46 @@ case EVR_UT: // unlimited text case EVR_PN: // person name case EVR_UI: // unique identifier - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR { Uint8* data = NULL; if (element.getUint8Array(data) == EC_Normal) { - visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength()); + const Uint32 length = element.getLength(); + Uint32 l = 0; + while (l < length && + data[l] != 0) + { + l++; + } + + if (l == length) + { + // Not a null-terminated plain string + visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); + } + else + { + std::string ignored; + std::string s(reinterpret_cast(data), l); + ITagVisitor::Action action = visitor.VisitString + (ignored, parentTags, parentIndexes, tag, vr, + Toolbox::ConvertToUtf8(s, encoding)); + + if (action != ITagVisitor::Action_None) + { + LOG(WARNING) << "Cannot replace this string tag: " + << FromDcmtkBridge::GetTagName(element) + << " (" << tag.Format() << ")"; + } + } } else { visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); } - break; + return; } /** @@ -2417,12 +2491,14 @@ **/ case EVR_SQ: // sequence of items + { return; - - - /** - * Internal to DCMTK. - **/ + } + + + /** + * Internal to DCMTK. + **/ case EVR_xs: // SS or US depending on context case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) @@ -2438,13 +2514,15 @@ case EVR_pixelItem: // used internally for pixel items in a compressed image case EVR_PixelData: // used internally for uncompressed pixeld data case EVR_OverlayData: // used internally for overlay data + { visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); return; - - - /** - * Default case. - **/ + } + + + /** + * Default case. + **/ default: return; diff -r 24a76ed0d8a3 -r 7724ed267b5c Core/DicomParsing/ITagVisitor.h --- a/Core/DicomParsing/ITagVisitor.h Tue Feb 05 21:10:47 2019 +0100 +++ b/Core/DicomParsing/ITagVisitor.h Wed Feb 06 07:28:58 2019 +0100 @@ -84,7 +84,7 @@ const DicomTag& tag, const std::vector& values) = 0; - // Visiting a binary buffer + // OB, OD, OF, OL, OW, UN virtual void VisitBinary(const std::vector& parentTags, const std::vector& parentIndexes, const DicomTag& tag, diff -r 24a76ed0d8a3 -r 7724ed267b5c UnitTestsSources/DicomMapTests.cpp --- a/UnitTestsSources/DicomMapTests.cpp Tue Feb 05 21:10:47 2019 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Wed Feb 06 07:28:58 2019 +0100 @@ -721,9 +721,15 @@ const void* data, size_t size) ORTHANC_OVERRIDE { - if (tag.GetElement() != 0x0000 && - vr != ValueRepresentation_NotSupported /*&& - !bulkUriRoot_.empty()*/) + assert(vr == ValueRepresentation_OtherByte || + vr == ValueRepresentation_OtherDouble || + vr == ValueRepresentation_OtherFloat || + vr == ValueRepresentation_OtherLong || + vr == ValueRepresentation_OtherWord || + vr == ValueRepresentation_Unknown); + + if (tag.GetElement() != 0x0000 /*&& + !bulkUriRoot_.empty()*/) { Json::Value& node = CreateNode(parentTags, parentIndexes, tag); node[KEY_VR] = EnumerationToString(vr); @@ -817,18 +823,8 @@ ValueRepresentation vr, const std::string& tutu) ORTHANC_OVERRIDE { - if (vr == ValueRepresentation_OtherByte || - vr == ValueRepresentation_OtherDouble || - vr == ValueRepresentation_OtherFloat || - vr == ValueRepresentation_OtherLong || - vr == ValueRepresentation_OtherWord || - vr == ValueRepresentation_Unknown) - { - VisitBinary(parentTags, parentIndexes, tag, vr, tutu.c_str(), tutu.size()); - return Action_None; - } - else if (tag.GetElement() == 0x0000 || - vr == ValueRepresentation_NotSupported) + if (tag.GetElement() == 0x0000 || + vr == ValueRepresentation_NotSupported) { return Action_None; } @@ -873,18 +869,29 @@ } case ValueRepresentation_IntegerString: - { - int64_t value = boost::lexical_cast(tokens[i]); - node[KEY_VALUE].append(FormatInteger(value)); + if (tokens[i].empty()) + { + node[KEY_VALUE].append(Json::nullValue); + } + else + { + int64_t value = boost::lexical_cast(tokens[i]); + node[KEY_VALUE].append(FormatInteger(value)); + } + break; - } case ValueRepresentation_DecimalString: - { - double value = boost::lexical_cast(tokens[i]); - node[KEY_VALUE].append(FormatDouble(value)); + if (tokens[i].empty()) + { + node[KEY_VALUE].append(Json::nullValue); + } + else + { + double value = boost::lexical_cast(tokens[i]); + node[KEY_VALUE].append(FormatDouble(value)); + } break; - } default: if (tokens[i].empty()) @@ -931,23 +938,134 @@ print(json.dumps(j, indent=4, sort_keys=True, ensure_ascii=False).encode('utf-8')) EOF -DCMDICTPATH=/home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/data/dicom.dic /home/jodogne/Downloads/dcmtk-3.6.4/i/bin/dcm2json ~/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0018.dcm | tr -d '\0' | sed 's/\\u0000//g' | sed 's/\.0$//' | python /tmp/tutu.py > /tmp/a.json +DCMDICTPATH=/home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/data/dicom.dic /home/jodogne/Downloads/dcmtk-3.6.4/i/bin/dcm2json ~/Subversion/orthanc-tests/Database/DummyCT.dcm | tr -d '\0' | sed 's/\\u0000//g' | sed 's/\.0$//' | python /tmp/tutu.py > /tmp/a.json -make -j4 && ./UnitTests --gtest_filter=DicomWeb* && python /tmp/tutu.py < /tmp/tutu.json > /tmp/b.json && diff -i /tmp/a.json /tmp/b.json +make -j4 && ./UnitTests --gtest_filter=DicomWeb* && python /tmp/tutu.py < tutu.json > /tmp/b.json && diff -i /tmp/a.json /tmp/b.json */ TEST(DicomWebJson, Basic) { std::string content; - Orthanc::SystemToolbox::ReadFile(content, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0018.dcm"); + Orthanc::SystemToolbox::ReadFile(content, "/home/jodogne/Subversion/orthanc-tests/Database/DummyCT.dcm"); Orthanc::ParsedDicomFile dicom(content); Orthanc::DicomJsonVisitor visitor; dicom.Apply(visitor); - Orthanc::SystemToolbox::WriteFile(visitor.GetResult().toStyledString(), "/tmp/tutu.json"); + Orthanc::SystemToolbox::WriteFile(visitor.GetResult().toStyledString(), "tutu.json"); +} + + +TEST(DicomWebJson, Multiplicity) +{ + // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.4.html + + ParsedDicomFile dicom(false); + dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1\\2.3\\4"); + dicom.ReplacePlainString(DICOM_TAG_IMAGE_POSITION_PATIENT, ""); + + Orthanc::DicomJsonVisitor visitor; + dicom.Apply(visitor); + + { + const Json::Value& tag = visitor.GetResult() ["00200037"]; + const Json::Value& value = tag["Value"]; + + ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString()); + ASSERT_EQ(2u, tag.getMemberNames().size()); + ASSERT_EQ(3u, value.size()); + ASSERT_EQ(Json::realValue, value[1].type()); + ASSERT_FLOAT_EQ(1.0f, value[0].asFloat()); + ASSERT_FLOAT_EQ(2.3f, value[1].asFloat()); + ASSERT_FLOAT_EQ(4.0f, value[2].asFloat()); + } + + { + const Json::Value& tag = visitor.GetResult() ["00200032"]; + ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString()); + ASSERT_EQ(1u, tag.getMemberNames().size()); + } } + +TEST(DicomWebJson, NullValue) +{ + // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.5.html + + ParsedDicomFile dicom(false); + dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1.5\\\\\\2.5"); + + Orthanc::DicomJsonVisitor visitor; + dicom.Apply(visitor); + + { + const Json::Value& tag = visitor.GetResult() ["00200037"]; + const Json::Value& value = tag["Value"]; + + ASSERT_EQ(EnumerationToString(ValueRepresentation_DecimalString), tag["vr"].asString()); + ASSERT_EQ(2u, tag.getMemberNames().size()); + ASSERT_EQ(4u, value.size()); + ASSERT_EQ(Json::realValue, value[0].type()); + ASSERT_EQ(Json::nullValue, value[1].type()); + ASSERT_EQ(Json::nullValue, value[2].type()); + ASSERT_EQ(Json::realValue, value[3].type()); + ASSERT_FLOAT_EQ(1.5f, value[0].asFloat()); + ASSERT_FLOAT_EQ(2.5f, value[3].asFloat()); + } +} + + +TEST(DicomWebJson, ValueRepresentation) +{ + // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html + + ParsedDicomFile dicom(false); + dicom.ReplacePlainString(DicomTag(0x0040, 0x0241), "AE"); + dicom.ReplacePlainString(DicomTag(0x0010, 0x1010), "AS"); + ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()-> + putAndInsertTagKey(DcmTag(0x0020, 0x9165), + DcmTagKey(0x0010, 0x0020)).good()); + dicom.ReplacePlainString(DicomTag(0x0008, 0x0052), "CS"); + dicom.ReplacePlainString(DicomTag(0x0008, 0x0012), "DA"); + dicom.ReplacePlainString(DicomTag(0x0010, 0x1020), "42"); // DS + dicom.ReplacePlainString(DicomTag(0x0008, 0x002a), "DT"); + dicom.ReplacePlainString(DicomTag(0x0010, 0x9431), "43"); // FL + dicom.ReplacePlainString(DicomTag(0x0008, 0x1163), "44"); // FD + dicom.ReplacePlainString(DicomTag(0x0008, 0x1160), "45"); // IS + dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO"); + dicom.ReplacePlainString(DicomTag(0x0008, 0x0108), "LT"); + + Orthanc::DicomJsonVisitor visitor; + dicom.Apply(visitor); + + std::cout << visitor.GetResult(); + ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["vr"].asString()); + ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["Value"][0].asString()); + ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["vr"].asString()); + ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["Value"][0].asString()); + ASSERT_EQ("AT", visitor.GetResult() ["00209165"]["vr"].asString()); + ASSERT_EQ("00100020", visitor.GetResult() ["00209165"]["Value"][0].asString()); + ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["vr"].asString()); + ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["Value"][0].asString()); + ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString()); + ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString()); + ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString()); + ASSERT_FLOAT_EQ(42.0f, visitor.GetResult() ["00101020"]["Value"][0].asFloat()); + ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString()); + ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString()); + ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString()); + ASSERT_FLOAT_EQ(43.0f, visitor.GetResult() ["00109431"]["Value"][0].asFloat()); + ASSERT_EQ("FD", visitor.GetResult() ["00081163"]["vr"].asString()); + ASSERT_FLOAT_EQ(44.0f, visitor.GetResult() ["00081163"]["Value"][0].asFloat()); + ASSERT_EQ("IS", visitor.GetResult() ["00081160"]["vr"].asString()); + ASSERT_FLOAT_EQ(45.0f, visitor.GetResult() ["00081160"]["Value"][0].asFloat()); + ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["vr"].asString()); + ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["Value"][0].asString()); + ASSERT_EQ("LT", visitor.GetResult() ["00080108"]["vr"].asString()); + ASSERT_EQ("LT", visitor.GetResult() ["00080108"]["Value"][0].asString()); +} + + #endif