Mercurial > hg > orthanc
comparison OrthancFramework/UnitTestsSources/DicomMapTests.cpp @ 4214:7b011cfda135
working on DicomStreamReader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 28 Sep 2020 21:05:13 +0200 |
parents | be2eca8b02e1 |
children | 28e9457dc7ab |
comparison
equal
deleted
inserted
replaced
4213:be2eca8b02e1 | 4214:7b011cfda135 |
---|---|
691 DicomTransferSyntax ts; | 691 DicomTransferSyntax ts; |
692 ASSERT_TRUE(LookupTransferSyntax(ts, s)); | 692 ASSERT_TRUE(LookupTransferSyntax(ts, s)); |
693 ASSERT_EQ(ts, it->second); | 693 ASSERT_EQ(ts, it->second); |
694 } | 694 } |
695 } | 695 } |
696 | |
697 | |
698 namespace | |
699 { | |
700 class StreamBlockReader : public boost::noncopyable | |
701 { | |
702 private: | |
703 std::istream& stream_; | |
704 std::string block_; | |
705 size_t blockPos_; | |
706 uint64_t processedBytes_; | |
707 | |
708 public: | |
709 StreamBlockReader(std::istream& stream) : | |
710 stream_(stream), | |
711 blockPos_(0), | |
712 processedBytes_(0) | |
713 { | |
714 } | |
715 | |
716 void Schedule(size_t blockSize) | |
717 { | |
718 if (!block_.empty()) | |
719 { | |
720 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
721 } | |
722 else | |
723 { | |
724 block_.resize(blockSize); | |
725 blockPos_ = 0; | |
726 } | |
727 } | |
728 | |
729 bool Read(std::string& block) | |
730 { | |
731 if (block_.empty()) | |
732 { | |
733 if (blockPos_ != 0) | |
734 { | |
735 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
736 } | |
737 | |
738 block.clear(); | |
739 return true; | |
740 } | |
741 else | |
742 { | |
743 while (blockPos_ < block_.size()) | |
744 { | |
745 char c; | |
746 stream_.get(c); | |
747 | |
748 if (stream_.good()) | |
749 { | |
750 block_[blockPos_] = c; | |
751 blockPos_++; | |
752 } | |
753 else | |
754 { | |
755 return false; | |
756 } | |
757 } | |
758 | |
759 processedBytes_ += block_.size(); | |
760 | |
761 block.swap(block_); | |
762 block_.clear(); | |
763 return true; | |
764 } | |
765 } | |
766 | |
767 uint64_t GetProcessedBytes() const | |
768 { | |
769 return processedBytes_; | |
770 } | |
771 }; | |
772 | |
773 | |
774 | |
775 | |
776 /** | |
777 * This class parses a stream containing a DICOM instance. It does | |
778 * *not* support the visit of sequences (it only works at the first | |
779 * level of the hierarchy), and it stops the processing once pixel | |
780 * data is reached in compressed transfer syntaxes. | |
781 **/ | |
782 class DicomStreamReader : public boost::noncopyable | |
783 { | |
784 public: | |
785 class IVisitor : public boost::noncopyable | |
786 { | |
787 public: | |
788 virtual ~IVisitor() | |
789 { | |
790 } | |
791 | |
792 virtual void VisitMetaHeaderTag(const DicomTag& tag, | |
793 const ValueRepresentation& vr, | |
794 const std::string& value) = 0; | |
795 | |
796 // Return "false" to stop processing | |
797 virtual bool VisitDatasetTag(const DicomTag& tag, | |
798 const ValueRepresentation& vr, | |
799 DicomTransferSyntax transferSyntax, | |
800 const std::string& value) = 0; | |
801 }; | |
802 | |
803 private: | |
804 enum State | |
805 { | |
806 State_Preamble, | |
807 State_MetaHeader, | |
808 State_DatasetTag, | |
809 State_DatasetExplicitLength, | |
810 State_DatasetValue, | |
811 State_Done | |
812 }; | |
813 | |
814 StreamBlockReader reader_; | |
815 State state_; | |
816 DicomTransferSyntax transferSyntax_; | |
817 DicomTag danglingTag_; | |
818 ValueRepresentation danglingVR_; | |
819 | |
820 static uint16_t ReadUnsignedInteger16(const char* dicom, | |
821 bool littleEndian) | |
822 { | |
823 const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); | |
824 | |
825 if (littleEndian) | |
826 { | |
827 return (static_cast<uint16_t>(p[0]) | | |
828 (static_cast<uint16_t>(p[1]) << 8)); | |
829 } | |
830 else | |
831 { | |
832 return (static_cast<uint16_t>(p[1]) | | |
833 (static_cast<uint16_t>(p[0]) << 8)); | |
834 } | |
835 } | |
836 | |
837 | |
838 static uint32_t ReadUnsignedInteger32(const char* dicom, | |
839 bool littleEndian) | |
840 { | |
841 const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); | |
842 | |
843 if (littleEndian) | |
844 { | |
845 return (static_cast<uint32_t>(p[0]) | | |
846 (static_cast<uint32_t>(p[1]) << 8) | | |
847 (static_cast<uint32_t>(p[2]) << 16) | | |
848 (static_cast<uint32_t>(p[3]) << 24)); | |
849 } | |
850 else | |
851 { | |
852 return (static_cast<uint32_t>(p[3]) | | |
853 (static_cast<uint32_t>(p[2]) << 8) | | |
854 (static_cast<uint32_t>(p[1]) << 16) | | |
855 (static_cast<uint32_t>(p[0]) << 24)); | |
856 } | |
857 } | |
858 | |
859 | |
860 static DicomTag ReadTag(const char* dicom, | |
861 bool littleEndian) | |
862 { | |
863 return DicomTag(ReadUnsignedInteger16(dicom, littleEndian), | |
864 ReadUnsignedInteger16(dicom + 2, littleEndian)); | |
865 } | |
866 | |
867 | |
868 static bool IsShortExplicitTag(ValueRepresentation vr) | |
869 { | |
870 /** | |
871 * Are we in the case of Table 7.1-2? "Data Element with | |
872 * Explicit VR of AE, AS, AT, CS, DA, DS, DT, FL, FD, IS, LO, | |
873 * LT, PN, SH, SL, SS, ST, TM, UI, UL and US" | |
874 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 | |
875 **/ | |
876 return (vr == ValueRepresentation_ApplicationEntity /* AE */ || | |
877 vr == ValueRepresentation_AgeString /* AS */ || | |
878 vr == ValueRepresentation_AttributeTag /* AT */ || | |
879 vr == ValueRepresentation_CodeString /* CS */ || | |
880 vr == ValueRepresentation_Date /* DA */ || | |
881 vr == ValueRepresentation_DecimalString /* DS */ || | |
882 vr == ValueRepresentation_DateTime /* DT */ || | |
883 vr == ValueRepresentation_FloatingPointSingle /* FL */ || | |
884 vr == ValueRepresentation_FloatingPointDouble /* FD */ || | |
885 vr == ValueRepresentation_IntegerString /* IS */ || | |
886 vr == ValueRepresentation_LongString /* LO */ || | |
887 vr == ValueRepresentation_LongText /* LT */ || | |
888 vr == ValueRepresentation_PersonName /* PN */ || | |
889 vr == ValueRepresentation_ShortString /* SH */ || | |
890 vr == ValueRepresentation_SignedLong /* SL */ || | |
891 vr == ValueRepresentation_SignedShort /* SS */ || | |
892 vr == ValueRepresentation_ShortText /* ST */ || | |
893 vr == ValueRepresentation_Time /* TM */ || | |
894 vr == ValueRepresentation_UniqueIdentifier /* UI */ || | |
895 vr == ValueRepresentation_UnsignedLong /* UL */ || | |
896 vr == ValueRepresentation_UnsignedShort /* US */); | |
897 } | |
898 | |
899 | |
900 void PrintBlock(const std::string& block) | |
901 { | |
902 for (size_t i = 0; i < block.size(); i++) | |
903 { | |
904 printf("%02x ", static_cast<uint8_t>(block[i])); | |
905 if (i % 16 == 15) | |
906 printf("\n"); | |
907 } | |
908 printf("\n"); | |
909 } | |
910 | |
911 void HandlePreamble(IVisitor& visitor, | |
912 const std::string& block) | |
913 { | |
914 printf("PREAMBLE:\n"); | |
915 PrintBlock(block); | |
916 | |
917 assert(block.size() == 144u); | |
918 assert(reader_.GetProcessedBytes() == 144u); | |
919 | |
920 /** | |
921 * The "DICOM file meta information" is always encoded using | |
922 * "Explicit VR Little Endian Transfer Syntax" | |
923 * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html | |
924 **/ | |
925 if (block[128] != 'D' || | |
926 block[129] != 'I' || | |
927 block[130] != 'C' || | |
928 block[131] != 'M' || | |
929 ReadTag(block.c_str() + 132, true) != DicomTag(0x0002, 0x0000) || | |
930 block[136] != 'U' || | |
931 block[137] != 'L' || | |
932 ReadUnsignedInteger16(block.c_str() + 138, true) != 4) | |
933 { | |
934 throw OrthancException(ErrorCode_BadFileFormat); | |
935 } | |
936 | |
937 uint32_t length = ReadUnsignedInteger32(block.c_str() + 140, true); | |
938 | |
939 reader_.Schedule(length); | |
940 state_ = State_MetaHeader; | |
941 } | |
942 | |
943 | |
944 void HandleMetaHeader(IVisitor& visitor, | |
945 const std::string& block) | |
946 { | |
947 printf("META-HEADER:\n"); | |
948 PrintBlock(block); | |
949 | |
950 size_t pos = 0; | |
951 const char* p = block.c_str(); | |
952 | |
953 bool hasTransferSyntax = false; | |
954 | |
955 while (pos + 8 <= block.size()) | |
956 { | |
957 DicomTag tag = ReadTag(p + pos, true); | |
958 | |
959 ValueRepresentation vr = StringToValueRepresentation(std::string(p + pos + 4, 2), true); | |
960 | |
961 if (IsShortExplicitTag(vr)) | |
962 { | |
963 uint16_t length = ReadUnsignedInteger16(p + pos + 6, true); | |
964 | |
965 std::string value; | |
966 value.assign(p + pos + 8, length); | |
967 | |
968 if (tag.GetGroup() == 0x0002) | |
969 { | |
970 visitor.VisitMetaHeaderTag(tag, vr, value); | |
971 } | |
972 | |
973 if (tag == DICOM_TAG_TRANSFER_SYNTAX_UID) | |
974 { | |
975 // Remove possible padding byte | |
976 if (!value.empty() && | |
977 value[value.size() - 1] == '\0') | |
978 { | |
979 value.resize(value.size() - 1); | |
980 } | |
981 | |
982 if (LookupTransferSyntax(transferSyntax_, value)) | |
983 { | |
984 hasTransferSyntax = true; | |
985 } | |
986 else | |
987 { | |
988 throw OrthancException(ErrorCode_NotImplemented, "Unsupported transfer syntax: " + value); | |
989 } | |
990 } | |
991 | |
992 pos += length + 8; | |
993 } | |
994 else if (pos + 12 <= block.size()) | |
995 { | |
996 uint16_t reserved = ReadUnsignedInteger16(p + pos + 6, true); | |
997 if (reserved != 0) | |
998 { | |
999 break; | |
1000 } | |
1001 | |
1002 uint32_t length = ReadUnsignedInteger32(p + pos + 8, true); | |
1003 | |
1004 std::string value; | |
1005 value.assign(p + pos + 12, length); | |
1006 | |
1007 if (tag.GetGroup() == 0x0002) | |
1008 { | |
1009 visitor.VisitMetaHeaderTag(tag, vr, value); | |
1010 } | |
1011 | |
1012 pos += length + 12; | |
1013 } | |
1014 } | |
1015 | |
1016 if (pos != block.size()) | |
1017 { | |
1018 throw OrthancException(ErrorCode_BadFileFormat); | |
1019 } | |
1020 | |
1021 if (!hasTransferSyntax) | |
1022 { | |
1023 throw OrthancException(ErrorCode_BadFileFormat, "DICOM file meta-header without transfer syntax UID"); | |
1024 } | |
1025 | |
1026 reader_.Schedule(8); | |
1027 state_ = State_DatasetTag; | |
1028 } | |
1029 | |
1030 | |
1031 void HandleDatasetTag(IVisitor& visitor, | |
1032 const std::string& block) | |
1033 { | |
1034 printf("DATASET TAG:\n"); | |
1035 PrintBlock(block); | |
1036 | |
1037 assert(block.size() == 8u); | |
1038 | |
1039 const bool littleEndian = (transferSyntax_ != DicomTransferSyntax_BigEndianExplicit); | |
1040 | |
1041 danglingTag_ = ReadTag(block.c_str(), littleEndian); | |
1042 danglingVR_ = ValueRepresentation_Unknown; | |
1043 | |
1044 /*if (danglingTag_ == DICOM_TAG_PIXEL_DATA) | |
1045 { | |
1046 state_ = State_Done; | |
1047 return; | |
1048 }*/ | |
1049 | |
1050 if (transferSyntax_ == DicomTransferSyntax_LittleEndianImplicit) | |
1051 { | |
1052 uint32_t length = ReadUnsignedInteger32(block.c_str() + 4, true /* little endian */); | |
1053 | |
1054 reader_.Schedule(length); | |
1055 state_ = State_DatasetValue; | |
1056 } | |
1057 else | |
1058 { | |
1059 // This in an explicit transfer syntax | |
1060 | |
1061 danglingVR_ = StringToValueRepresentation( | |
1062 std::string(block.c_str() + 4, 2), false /* ignore unknown VR */); | |
1063 | |
1064 if (IsShortExplicitTag(danglingVR_)) | |
1065 { | |
1066 uint16_t length = ReadUnsignedInteger16(block.c_str() + 6, littleEndian); | |
1067 | |
1068 reader_.Schedule(length); | |
1069 state_ = State_DatasetValue; | |
1070 } | |
1071 else | |
1072 { | |
1073 uint16_t reserved = ReadUnsignedInteger16(block.c_str() + 6, littleEndian); | |
1074 if (reserved != 0) | |
1075 { | |
1076 throw OrthancException(ErrorCode_BadFileFormat); | |
1077 } | |
1078 | |
1079 reader_.Schedule(4); | |
1080 state_ = State_DatasetExplicitLength; | |
1081 } | |
1082 }; | |
1083 } | |
1084 | |
1085 | |
1086 void HandleDatasetExplicitLength(IVisitor& visitor, | |
1087 const std::string& block) | |
1088 { | |
1089 //printf("DATASET TAG LENGTH:\n"); | |
1090 //PrintBlock(block); | |
1091 | |
1092 assert(block.size() == 4); | |
1093 | |
1094 const bool littleEndian = (transferSyntax_ != DicomTransferSyntax_BigEndianExplicit); | |
1095 | |
1096 uint32_t length = ReadUnsignedInteger32(block.c_str(), littleEndian); | |
1097 if (length == 0xffffffffu) | |
1098 { | |
1099 // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.5.html | |
1100 printf("AIE\n"); | |
1101 | |
1102 /** | |
1103 * This is the case for compressed transfer syntaxes. We stop | |
1104 * the processing here, as this would cause StreamBlockReader | |
1105 * to allocate a huge memory buffer of 2GB. | |
1106 **/ | |
1107 state_ = State_Done; | |
1108 } | |
1109 else | |
1110 { | |
1111 reader_.Schedule(length); | |
1112 state_ = State_DatasetValue; | |
1113 } | |
1114 } | |
1115 | |
1116 | |
1117 public: | |
1118 DicomStreamReader(std::istream& stream) : | |
1119 reader_(stream), | |
1120 state_(State_Preamble), | |
1121 transferSyntax_(DicomTransferSyntax_LittleEndianImplicit), // Dummy | |
1122 danglingTag_(0x0000, 0x0000), // Dummy | |
1123 danglingVR_(ValueRepresentation_Unknown) // Dummy | |
1124 { | |
1125 reader_.Schedule(128 /* empty header */ + | |
1126 4 /* "DICM" magic value */ + | |
1127 4 /* (0x0002, 0x0000) tag */ + | |
1128 2 /* value representation of (0x0002, 0x0000) == "UL" */ + | |
1129 2 /* length of "UL" value == 4 */ + | |
1130 4 /* actual length of the meta-header */); | |
1131 } | |
1132 | |
1133 void Consume(IVisitor& visitor) | |
1134 { | |
1135 while (state_ != State_Done) | |
1136 { | |
1137 std::string block; | |
1138 if (reader_.Read(block)) | |
1139 { | |
1140 switch (state_) | |
1141 { | |
1142 case State_Preamble: | |
1143 HandlePreamble(visitor, block); | |
1144 break; | |
1145 | |
1146 case State_MetaHeader: | |
1147 HandleMetaHeader(visitor, block); | |
1148 break; | |
1149 | |
1150 case State_DatasetTag: | |
1151 HandleDatasetTag(visitor, block); | |
1152 break; | |
1153 | |
1154 case State_DatasetExplicitLength: | |
1155 HandleDatasetExplicitLength(visitor, block); | |
1156 break; | |
1157 | |
1158 case State_DatasetValue: | |
1159 if (visitor.VisitDatasetTag(danglingTag_, danglingVR_, transferSyntax_, block)) | |
1160 { | |
1161 reader_.Schedule(8); | |
1162 state_ = State_DatasetTag; | |
1163 } | |
1164 else | |
1165 { | |
1166 state_ = State_Done; | |
1167 } | |
1168 break; | |
1169 | |
1170 default: | |
1171 throw OrthancException(ErrorCode_InternalError); | |
1172 } | |
1173 } | |
1174 else | |
1175 { | |
1176 return; // No more data in the stream | |
1177 } | |
1178 } | |
1179 } | |
1180 | |
1181 bool IsDone() const | |
1182 { | |
1183 return (state_ == State_Done); | |
1184 } | |
1185 | |
1186 uint64_t GetProcessedBytes() const | |
1187 { | |
1188 return reader_.GetProcessedBytes(); | |
1189 } | |
1190 }; | |
1191 | |
1192 | |
1193 | |
1194 class V : public DicomStreamReader::IVisitor | |
1195 { | |
1196 public: | |
1197 virtual void VisitMetaHeaderTag(const DicomTag& tag, | |
1198 const ValueRepresentation& vr, | |
1199 const std::string& value) | |
1200 { | |
1201 std::cout << "Header: " << tag.Format() << " [" << value.c_str() << "] (" << value.size() << ")" << std::endl; | |
1202 } | |
1203 | |
1204 virtual bool VisitDatasetTag(const DicomTag& tag, | |
1205 const ValueRepresentation& vr, | |
1206 DicomTransferSyntax transferSyntax, | |
1207 const std::string& value) | |
1208 { | |
1209 if (tag.GetGroup() < 0x7f00) | |
1210 std::cout << "Dataset: " << tag.Format() << " [" << value.c_str() << "] (" << value.size() << ")" << std::endl; | |
1211 else | |
1212 std::cout << "Dataset: " << tag.Format() << " [PIXEL] (" << value.size() << ")" << std::endl; | |
1213 | |
1214 return true; | |
1215 } | |
1216 }; | |
1217 } | |
1218 | |
1219 | |
1220 | |
1221 TEST(DicomStreamReader, DISABLED_Tutu) | |
1222 { | |
1223 static const std::string PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/"; | |
1224 | |
1225 std::string dicom; | |
1226 //SystemToolbox::ReadFile(dicom, PATH + "../ColorTestMalaterre.dcm", false); | |
1227 //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.2.dcm", false); // Big Endian | |
1228 //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.1.dcm", false); | |
1229 //SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.50.dcm", false); | |
1230 SystemToolbox::ReadFile(dicom, PATH + "1.2.840.10008.1.2.4.51.dcm", false); | |
1231 | |
1232 std::stringstream stream; | |
1233 size_t pos = 0; | |
1234 | |
1235 DicomStreamReader r(stream); | |
1236 V visitor; | |
1237 | |
1238 while (pos < dicom.size() && | |
1239 !r.IsDone()) | |
1240 { | |
1241 //printf("."); | |
1242 //printf("%d\n", pos); | |
1243 r.Consume(visitor); | |
1244 stream.clear(); | |
1245 stream.put(dicom[pos++]); | |
1246 } | |
1247 | |
1248 r.Consume(visitor); | |
1249 | |
1250 printf(">> %d\n", r.GetProcessedBytes()); | |
1251 } | |
1252 | |
1253 TEST(DicomStreamReader, DISABLED_Tutu2) | |
1254 { | |
1255 static const std::string PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/"; | |
1256 | |
1257 //std::ifstream stream(PATH + "1.2.840.10008.1.2.4.50.dcm"); | |
1258 std::ifstream stream(PATH + "1.2.840.10008.1.2.2.dcm"); | |
1259 | |
1260 DicomStreamReader r(stream); | |
1261 V visitor; | |
1262 | |
1263 r.Consume(visitor); | |
1264 | |
1265 printf(">> %d\n", r.GetProcessedBytes()); | |
1266 } |