comparison UnitTestsSources/DicomMapTests.cpp @ 3202:ef4d86d05503

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 06 Feb 2019 15:21:32 +0100
parents b69fe409cb4d
children 810772486249
comparison
equal deleted inserted replaced
3201:b69fe409cb4d 3202:ef4d86d05503
36 36
37 #include "../Core/OrthancException.h" 37 #include "../Core/OrthancException.h"
38 #include "../Core/DicomFormat/DicomMap.h" 38 #include "../Core/DicomFormat/DicomMap.h"
39 #include "../Core/DicomParsing/FromDcmtkBridge.h" 39 #include "../Core/DicomParsing/FromDcmtkBridge.h"
40 #include "../Core/DicomParsing/ParsedDicomFile.h" 40 #include "../Core/DicomParsing/ParsedDicomFile.h"
41 #include "../Core/DicomParsing/DicomWebJsonVisitor.h"
41 42
42 #include "../OrthancServer/DicomInstanceToStore.h" 43 #include "../OrthancServer/DicomInstanceToStore.h"
43 44
44 #include <memory> 45 #include <memory>
45 #include <dcmtk/dcmdata/dcdeftag.h> 46 #include <dcmtk/dcmdata/dcdeftag.h>
552 ASSERT_FALSE(b.HasOnlyMainDicomTags()); 553 ASSERT_FALSE(b.HasOnlyMainDicomTags());
553 } 554 }
554 555
555 556
556 557
557
558
559 #include <boost/math/special_functions/round.hpp>
560 #include <pugixml.hpp>
561
562
563 static const char* const KEY_ALPHABETIC = "Alphabetic";
564 static const char* const KEY_BULK_DATA_URI = "BulkDataURI";
565 static const char* const KEY_INLINE_BINARY = "InlineBinary";
566 static const char* const KEY_SQ = "SQ";
567 static const char* const KEY_VALUE = "Value";
568 static const char* const KEY_VR = "vr";
569
570 namespace Orthanc
571 {
572 static void ExploreDataset(pugi::xml_node& target,
573 const Json::Value& source)
574 {
575 assert(source.type() == Json::objectValue);
576
577 Json::Value::Members members = source.getMemberNames();
578 for (size_t i = 0; i < members.size(); i++)
579 {
580 const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]);
581 const Json::Value& content = source[members[i]];
582
583 assert(content.type() == Json::objectValue &&
584 content.isMember("vr") &&
585 content["vr"].type() == Json::stringValue);
586 const std::string vr = content["vr"].asString();
587
588 const std::string keyword = FromDcmtkBridge::GetTagName(tag, "");
589
590 pugi::xml_node node = target.append_child("DicomAttribute");
591 node.append_attribute("tag").set_value(members[i].c_str());
592 node.append_attribute("vr").set_value(vr.c_str());
593
594 if (keyword != std::string(DcmTag_ERROR_TagName))
595 {
596 node.append_attribute("keyword").set_value(keyword.c_str());
597 }
598
599 if (content.isMember(KEY_VALUE))
600 {
601 assert(content[KEY_VALUE].type() == Json::arrayValue);
602
603 for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++)
604 {
605 std::string number = boost::lexical_cast<std::string>(j + 1);
606
607 if (vr == "SQ")
608 {
609 if (content[KEY_VALUE][j].type() == Json::objectValue)
610 {
611 pugi::xml_node child = node.append_child("Item");
612 child.append_attribute("number").set_value(number.c_str());
613 ExploreDataset(child, content[KEY_VALUE][j]);
614 }
615 }
616 if (vr == "PN")
617 {
618 if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) &&
619 content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue)
620 {
621 std::vector<std::string> tokens;
622 Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^');
623
624 pugi::xml_node child = node.append_child("PersonName");
625 child.append_attribute("number").set_value(number.c_str());
626
627 pugi::xml_node name = child.append_child(KEY_ALPHABETIC);
628
629 if (tokens.size() >= 1)
630 {
631 name.append_child("FamilyName").text() = tokens[0].c_str();
632 }
633
634 if (tokens.size() >= 2)
635 {
636 name.append_child("GivenName").text() = tokens[1].c_str();
637 }
638
639 if (tokens.size() >= 3)
640 {
641 name.append_child("MiddleName").text() = tokens[2].c_str();
642 }
643
644 if (tokens.size() >= 4)
645 {
646 name.append_child("NamePrefix").text() = tokens[3].c_str();
647 }
648
649 if (tokens.size() >= 5)
650 {
651 name.append_child("NameSuffix").text() = tokens[4].c_str();
652 }
653 }
654 }
655 else
656 {
657 pugi::xml_node child = node.append_child("Value");
658 child.append_attribute("number").set_value(number.c_str());
659
660 switch (content[KEY_VALUE][j].type())
661 {
662 case Json::stringValue:
663 child.text() = content[KEY_VALUE][j].asCString();
664 break;
665
666 case Json::realValue:
667 child.text() = content[KEY_VALUE][j].asFloat();
668 break;
669
670 case Json::intValue:
671 child.text() = content[KEY_VALUE][j].asInt();
672 break;
673
674 case Json::uintValue:
675 child.text() = content[KEY_VALUE][j].asUInt();
676 break;
677
678 default:
679 break;
680 }
681 }
682 }
683 }
684 else if (content.isMember(KEY_BULK_DATA_URI) &&
685 content[KEY_BULK_DATA_URI].type() == Json::stringValue)
686 {
687 pugi::xml_node child = node.append_child("BulkData");
688 child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString());
689 }
690 else if (content.isMember(KEY_INLINE_BINARY) &&
691 content[KEY_INLINE_BINARY].type() == Json::stringValue)
692 {
693 pugi::xml_node child = node.append_child("InlineBinary");
694 child.text() = content[KEY_INLINE_BINARY].asCString();
695 }
696 }
697 }
698
699
700 static void DicomWebJsonToXml(pugi::xml_document& target,
701 const Json::Value& source)
702 {
703 pugi::xml_node root = target.append_child("NativeDicomModel");
704 root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
705 root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
706 root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance");
707
708 ExploreDataset(root, source);
709
710 pugi::xml_node decl = target.prepend_child(pugi::node_declaration);
711 decl.append_attribute("version").set_value("1.0");
712 decl.append_attribute("encoding").set_value("utf-8");
713 }
714
715
716 enum DicomWebBinaryMode
717 {
718 DicomWebBinaryMode_Ignore,
719 DicomWebBinaryMode_BulkDataUri,
720 DicomWebBinaryMode_InlineBinary
721 };
722
723 class IDicomWebBinaryFormatter : public boost::noncopyable
724 {
725 public:
726 virtual ~IDicomWebBinaryFormatter()
727 {
728 }
729
730 virtual DicomWebBinaryMode Format(std::string& bulkDataUri,
731 const std::vector<DicomTag>& parentTags,
732 const std::vector<size_t>& parentIndexes,
733 const DicomTag& tag,
734 ValueRepresentation vr) = 0;
735 };
736
737 class DicomWebJsonVisitor : public ITagVisitor
738 {
739 private:
740 Json::Value result_;
741 IDicomWebBinaryFormatter *formatter_;
742
743 static std::string FormatTag(const DicomTag& tag)
744 {
745 char buf[16];
746 sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement());
747 return std::string(buf);
748 }
749
750 Json::Value& CreateNode(const std::vector<DicomTag>& parentTags,
751 const std::vector<size_t>& parentIndexes,
752 const DicomTag& tag)
753 {
754 assert(parentTags.size() == parentIndexes.size());
755
756 Json::Value* node = &result_;
757
758 for (size_t i = 0; i < parentTags.size(); i++)
759 {
760 std::string t = FormatTag(parentTags[i]);
761
762 if (!node->isMember(t))
763 {
764 Json::Value item = Json::objectValue;
765 item[KEY_VR] = KEY_SQ;
766 item[KEY_VALUE] = Json::arrayValue;
767 item[KEY_VALUE].append(Json::objectValue);
768 (*node) [t] = item;
769
770 node = &(*node)[t][KEY_VALUE][0];
771 }
772 else if ((*node) [t].type() != Json::objectValue ||
773 !(*node) [t].isMember(KEY_VR) ||
774 (*node) [t][KEY_VR].type() != Json::stringValue ||
775 (*node) [t][KEY_VR].asString() != KEY_SQ ||
776 !(*node) [t].isMember(KEY_VALUE) ||
777 (*node) [t][KEY_VALUE].type() != Json::arrayValue)
778 {
779 throw OrthancException(ErrorCode_InternalError);
780 }
781 else
782 {
783 size_t currentSize = (*node) [t][KEY_VALUE].size();
784
785 if (parentIndexes[i] < currentSize)
786 {
787 // The node already exists
788 }
789 else if (parentIndexes[i] == currentSize)
790 {
791 (*node) [t][KEY_VALUE].append(Json::objectValue);
792 }
793 else
794 {
795 throw OrthancException(ErrorCode_InternalError);
796 }
797
798 node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])];
799 }
800 }
801
802 assert(node->type() == Json::objectValue);
803
804 std::string t = FormatTag(tag);
805 if (node->isMember(t))
806 {
807 throw OrthancException(ErrorCode_InternalError);
808 }
809 else
810 {
811 (*node) [t] = Json::objectValue;
812 return (*node) [t];
813 }
814 }
815
816 static Json::Value FormatInteger(int64_t value)
817 {
818 if (value < 0)
819 {
820 return Json::Value(static_cast<int32_t>(value));
821 }
822 else
823 {
824 return Json::Value(static_cast<uint32_t>(value));
825 }
826 }
827
828 static Json::Value FormatDouble(double value)
829 {
830 long long a = boost::math::llround<double>(value);
831
832 double d = fabs(value - static_cast<double>(a));
833
834 if (d <= std::numeric_limits<double>::epsilon() * 100.0)
835 {
836 return FormatInteger(a);
837 }
838 else
839 {
840 return Json::Value(value);
841 }
842 }
843
844 public:
845 DicomWebJsonVisitor() :
846 formatter_(NULL)
847 {
848 Clear();
849 }
850
851 void SetFormatter(IDicomWebBinaryFormatter& formatter)
852 {
853 formatter_ = &formatter;
854 }
855
856 void Clear()
857 {
858 result_ = Json::objectValue;
859 }
860
861 const Json::Value& GetResult() const
862 {
863 return result_;
864 }
865
866 void FormatXml(pugi::xml_document& target) const
867 {
868 DicomWebJsonToXml(target, result_);
869 }
870
871 virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags,
872 const std::vector<size_t>& parentIndexes,
873 const DicomTag& tag,
874 ValueRepresentation vr) ORTHANC_OVERRIDE
875 {
876 }
877
878 virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags,
879 const std::vector<size_t>& parentIndexes,
880 const DicomTag& tag) ORTHANC_OVERRIDE
881 {
882 if (tag.GetElement() != 0x0000)
883 {
884 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
885 node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence);
886 }
887 }
888
889 virtual void VisitBinary(const std::vector<DicomTag>& parentTags,
890 const std::vector<size_t>& parentIndexes,
891 const DicomTag& tag,
892 ValueRepresentation vr,
893 const void* data,
894 size_t size) ORTHANC_OVERRIDE
895 {
896 assert(vr == ValueRepresentation_OtherByte ||
897 vr == ValueRepresentation_OtherDouble ||
898 vr == ValueRepresentation_OtherFloat ||
899 vr == ValueRepresentation_OtherLong ||
900 vr == ValueRepresentation_OtherWord ||
901 vr == ValueRepresentation_Unknown);
902
903 if (tag.GetElement() != 0x0000)
904 {
905 DicomWebBinaryMode mode;
906 std::string bulkDataUri;
907
908 if (formatter_ == NULL)
909 {
910 mode = DicomWebBinaryMode_InlineBinary;
911 }
912 else
913 {
914 mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr);
915 }
916
917 /*mode = DicomWebBinaryMode_BulkDataUri;
918 bulkDataUri = "http://localhost/" + tag.Format();*/
919
920 if (mode != DicomWebBinaryMode_Ignore)
921 {
922 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
923 node[KEY_VR] = EnumerationToString(vr);
924
925 switch (mode)
926 {
927 case DicomWebBinaryMode_BulkDataUri:
928 node[KEY_BULK_DATA_URI] = bulkDataUri;
929 break;
930
931 case DicomWebBinaryMode_InlineBinary:
932 {
933 std::string tmp(static_cast<const char*>(data), size);
934
935 std::string base64;
936 Toolbox::EncodeBase64(base64, tmp);
937
938 node[KEY_INLINE_BINARY] = base64;
939 break;
940 }
941
942 default:
943 throw OrthancException(ErrorCode_ParameterOutOfRange);
944 }
945 }
946 }
947 }
948
949 virtual void VisitIntegers(const std::vector<DicomTag>& parentTags,
950 const std::vector<size_t>& parentIndexes,
951 const DicomTag& tag,
952 ValueRepresentation vr,
953 const std::vector<int64_t>& values) ORTHANC_OVERRIDE
954 {
955 if (tag.GetElement() != 0x0000 &&
956 vr != ValueRepresentation_NotSupported)
957 {
958 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
959 node[KEY_VR] = EnumerationToString(vr);
960
961 if (!values.empty())
962 {
963 Json::Value content = Json::arrayValue;
964 for (size_t i = 0; i < values.size(); i++)
965 {
966 content.append(FormatInteger(values[i]));
967 }
968
969 node[KEY_VALUE] = content;
970 }
971 }
972 }
973
974 virtual void VisitDoubles(const std::vector<DicomTag>& parentTags,
975 const std::vector<size_t>& parentIndexes,
976 const DicomTag& tag,
977 ValueRepresentation vr,
978 const std::vector<double>& values) ORTHANC_OVERRIDE
979 {
980 if (tag.GetElement() != 0x0000 &&
981 vr != ValueRepresentation_NotSupported)
982 {
983 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
984 node[KEY_VR] = EnumerationToString(vr);
985
986 if (!values.empty())
987 {
988 Json::Value content = Json::arrayValue;
989 for (size_t i = 0; i < values.size(); i++)
990 {
991 content.append(FormatDouble(values[i]));
992 }
993
994 node[KEY_VALUE] = content;
995 }
996 }
997 }
998
999 virtual void VisitAttributes(const std::vector<DicomTag>& parentTags,
1000 const std::vector<size_t>& parentIndexes,
1001 const DicomTag& tag,
1002 const std::vector<DicomTag>& values) ORTHANC_OVERRIDE
1003 {
1004 if (tag.GetElement() != 0x0000)
1005 {
1006 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
1007 node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag);
1008
1009 if (!values.empty())
1010 {
1011 Json::Value content = Json::arrayValue;
1012 for (size_t i = 0; i < values.size(); i++)
1013 {
1014 content.append(FormatTag(values[i]));
1015 }
1016
1017 node[KEY_VALUE] = content;
1018 }
1019 }
1020 }
1021
1022 virtual Action VisitString(std::string& newValue,
1023 const std::vector<DicomTag>& parentTags,
1024 const std::vector<size_t>& parentIndexes,
1025 const DicomTag& tag,
1026 ValueRepresentation vr,
1027 const std::string& value) ORTHANC_OVERRIDE
1028 {
1029 if (tag.GetElement() == 0x0000 ||
1030 vr == ValueRepresentation_NotSupported)
1031 {
1032 return Action_None;
1033 }
1034 else
1035 {
1036 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
1037 node[KEY_VR] = EnumerationToString(vr);
1038
1039 if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
1040 {
1041 // TODO - The JSON file has an UTF-8 encoding, thus DCMTK
1042 // replaces the specific character set with "ISO_IR 192"
1043 // (UNICODE UTF-8). It is unclear whether the source
1044 // character set should be kept: We thus mimic DCMTK.
1045 node[KEY_VALUE].append("ISO_IR 192");
1046 }
1047 else
1048 {
1049 std::string truncated;
1050
1051 if (!value.empty() &&
1052 value[value.size() - 1] == '\0')
1053 {
1054 truncated = value.substr(0, value.size() - 1);
1055 }
1056 else
1057 {
1058 truncated = value;
1059 }
1060
1061 if (!truncated.empty())
1062 {
1063 std::vector<std::string> tokens;
1064 Toolbox::TokenizeString(tokens, truncated, '\\');
1065
1066 node[KEY_VALUE] = Json::arrayValue;
1067 for (size_t i = 0; i < tokens.size(); i++)
1068 {
1069 try
1070 {
1071 switch (vr)
1072 {
1073 case ValueRepresentation_PersonName:
1074 {
1075 Json::Value value = Json::objectValue;
1076 if (!tokens[i].empty())
1077 {
1078 value[KEY_ALPHABETIC] = tokens[i];
1079 }
1080 node[KEY_VALUE].append(value);
1081 break;
1082 }
1083
1084 case ValueRepresentation_IntegerString:
1085 if (tokens[i].empty())
1086 {
1087 node[KEY_VALUE].append(Json::nullValue);
1088 }
1089 else
1090 {
1091 int64_t value = boost::lexical_cast<int64_t>(tokens[i]);
1092 node[KEY_VALUE].append(FormatInteger(value));
1093 }
1094
1095 break;
1096
1097 case ValueRepresentation_DecimalString:
1098 if (tokens[i].empty())
1099 {
1100 node[KEY_VALUE].append(Json::nullValue);
1101 }
1102 else
1103 {
1104 double value = boost::lexical_cast<double>(tokens[i]);
1105 node[KEY_VALUE].append(FormatDouble(value));
1106 }
1107 break;
1108
1109 default:
1110 if (tokens[i].empty())
1111 {
1112 node[KEY_VALUE].append(Json::nullValue);
1113 }
1114 else
1115 {
1116 node[KEY_VALUE].append(tokens[i]);
1117 }
1118
1119 break;
1120 }
1121 }
1122 catch (boost::bad_lexical_cast&)
1123 {
1124 throw OrthancException(ErrorCode_BadFileFormat);
1125 }
1126 }
1127 }
1128 }
1129 }
1130
1131 return Action_None;
1132 }
1133 };
1134 }
1135
1136
1137
1138
1139
1140
1141 #include "../Core/SystemToolbox.h"
1142
1143
1144 /*
1145
1146 MarekLatin2.dcm
1147 HierarchicalAnonymization/StructuredReports/IM0
1148 DummyCT.dcm
1149 Brainix/Epi/IM-0001-0018.dcm
1150 Issue22.dcm
1151
1152
1153 cat << EOF > /tmp/tutu.py
1154 import json
1155 import sys
1156 j = json.loads(sys.stdin.read().decode("utf-8-sig"))
1157 print(json.dumps(j, indent=4, sort_keys=True, ensure_ascii=False).encode('utf-8'))
1158 EOF
1159
1160 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
1161
1162 make -j4 && ./UnitTests --gtest_filter=DicomWeb* && python /tmp/tutu.py < tutu.json > /tmp/b.json && diff -i /tmp/a.json /tmp/b.json
1163
1164 */
1165
1166 TEST(DicomWebJson, Basic)
1167 {
1168 std::string content;
1169 Orthanc::SystemToolbox::ReadFile(content, "/home/jodogne/Subversion/orthanc-tests/Database/DummyCT.dcm");
1170
1171 Orthanc::ParsedDicomFile dicom(content);
1172
1173 Orthanc::DicomWebJsonVisitor visitor;
1174 dicom.Apply(visitor);
1175
1176 Orthanc::SystemToolbox::WriteFile(visitor.GetResult().toStyledString(), "tutu.json");
1177
1178 pugi::xml_document xml;
1179 visitor.FormatXml(xml);
1180 xml.print(std::cout);
1181 }
1182
1183
1184 TEST(DicomWebJson, Multiplicity) 558 TEST(DicomWebJson, Multiplicity)
1185 { 559 {
1186 // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.4.html 560 // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.4.html
1187 561
1188 ParsedDicomFile dicom(false); 562 ParsedDicomFile dicom(false);
1212 ASSERT_EQ(1u, tag.getMemberNames().size()); 586 ASSERT_EQ(1u, tag.getMemberNames().size());
1213 } 587 }
1214 588
1215 pugi::xml_document xml; 589 pugi::xml_document xml;
1216 visitor.FormatXml(xml); 590 visitor.FormatXml(xml);
1217 xml.print(std::cout);
1218 } 591 }
1219 592
1220 593
1221 TEST(DicomWebJson, NullValue) 594 TEST(DicomWebJson, NullValue)
1222 { 595 {
1243 ASSERT_FLOAT_EQ(2.5f, value[3].asFloat()); 616 ASSERT_FLOAT_EQ(2.5f, value[3].asFloat());
1244 } 617 }
1245 618
1246 pugi::xml_document xml; 619 pugi::xml_document xml;
1247 visitor.FormatXml(xml); 620 visitor.FormatXml(xml);
1248 xml.print(std::cout);
1249 } 621 }
1250 622
1251 623
1252 TEST(DicomWebJson, ValueRepresentation) 624 TEST(DicomWebJson, ValueRepresentation)
1253 { 625 {
1386 ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["Value"][0].asString()); 758 ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["Value"][0].asString());
1387 759
1388 760
1389 pugi::xml_document xml; 761 pugi::xml_document xml;
1390 visitor.FormatXml(xml); 762 visitor.FormatXml(xml);
1391 xml.print(std::cout);
1392 } 763 }
1393 764
1394 765
1395 TEST(DicomWebJson, Sequence) 766 TEST(DicomWebJson, Sequence)
1396 { 767 {
1431 ASSERT_TRUE(items.find("item1") != items.end()); 802 ASSERT_TRUE(items.find("item1") != items.end());
1432 ASSERT_TRUE(items.find("item2") != items.end()); 803 ASSERT_TRUE(items.find("item2") != items.end());
1433 804
1434 pugi::xml_document xml; 805 pugi::xml_document xml;
1435 visitor.FormatXml(xml); 806 visitor.FormatXml(xml);
1436 xml.print(std::cout); 807 }
1437 }