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 }