Mercurial > hg > orthanc
comparison OrthancFramework/Sources/DicomFormat/DicomStreamReader.cpp @ 4220:92a21efa5c96
reorganization of DicomStreamReader
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 30 Sep 2020 15:33:47 +0200 |
parents | |
children | e4c0218b6b23 |
comparison
equal
deleted
inserted
replaced
4219:b8ed2852a35d | 4220:92a21efa5c96 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Lesser General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Lesser General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Lesser General Public | |
18 * License along with this program. If not, see | |
19 * <http://www.gnu.org/licenses/>. | |
20 **/ | |
21 | |
22 | |
23 #include "../PrecompiledHeaders.h" | |
24 #include "DicomStreamReader.h" | |
25 | |
26 #include "../OrthancException.h" | |
27 | |
28 namespace Orthanc | |
29 { | |
30 static uint16_t ReadUnsignedInteger16(const char* dicom, | |
31 bool littleEndian) | |
32 { | |
33 const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); | |
34 | |
35 if (littleEndian) | |
36 { | |
37 return (static_cast<uint16_t>(p[0]) | | |
38 (static_cast<uint16_t>(p[1]) << 8)); | |
39 } | |
40 else | |
41 { | |
42 return (static_cast<uint16_t>(p[1]) | | |
43 (static_cast<uint16_t>(p[0]) << 8)); | |
44 } | |
45 } | |
46 | |
47 | |
48 static uint32_t ReadUnsignedInteger32(const char* dicom, | |
49 bool littleEndian) | |
50 { | |
51 const uint8_t* p = reinterpret_cast<const uint8_t*>(dicom); | |
52 | |
53 if (littleEndian) | |
54 { | |
55 return (static_cast<uint32_t>(p[0]) | | |
56 (static_cast<uint32_t>(p[1]) << 8) | | |
57 (static_cast<uint32_t>(p[2]) << 16) | | |
58 (static_cast<uint32_t>(p[3]) << 24)); | |
59 } | |
60 else | |
61 { | |
62 return (static_cast<uint32_t>(p[3]) | | |
63 (static_cast<uint32_t>(p[2]) << 8) | | |
64 (static_cast<uint32_t>(p[1]) << 16) | | |
65 (static_cast<uint32_t>(p[0]) << 24)); | |
66 } | |
67 } | |
68 | |
69 | |
70 static DicomTag ReadTag(const char* dicom, | |
71 bool littleEndian) | |
72 { | |
73 return DicomTag(ReadUnsignedInteger16(dicom, littleEndian), | |
74 ReadUnsignedInteger16(dicom + 2, littleEndian)); | |
75 } | |
76 | |
77 | |
78 static bool IsShortExplicitTag(ValueRepresentation vr) | |
79 { | |
80 /** | |
81 * Are we in the case of Table 7.1-2? "Data Element with | |
82 * Explicit VR of AE, AS, AT, CS, DA, DS, DT, FL, FD, IS, LO, | |
83 * LT, PN, SH, SL, SS, ST, TM, UI, UL and US" | |
84 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 | |
85 **/ | |
86 return (vr == ValueRepresentation_ApplicationEntity /* AE */ || | |
87 vr == ValueRepresentation_AgeString /* AS */ || | |
88 vr == ValueRepresentation_AttributeTag /* AT */ || | |
89 vr == ValueRepresentation_CodeString /* CS */ || | |
90 vr == ValueRepresentation_Date /* DA */ || | |
91 vr == ValueRepresentation_DecimalString /* DS */ || | |
92 vr == ValueRepresentation_DateTime /* DT */ || | |
93 vr == ValueRepresentation_FloatingPointSingle /* FL */ || | |
94 vr == ValueRepresentation_FloatingPointDouble /* FD */ || | |
95 vr == ValueRepresentation_IntegerString /* IS */ || | |
96 vr == ValueRepresentation_LongString /* LO */ || | |
97 vr == ValueRepresentation_LongText /* LT */ || | |
98 vr == ValueRepresentation_PersonName /* PN */ || | |
99 vr == ValueRepresentation_ShortString /* SH */ || | |
100 vr == ValueRepresentation_SignedLong /* SL */ || | |
101 vr == ValueRepresentation_SignedShort /* SS */ || | |
102 vr == ValueRepresentation_ShortText /* ST */ || | |
103 vr == ValueRepresentation_Time /* TM */ || | |
104 vr == ValueRepresentation_UniqueIdentifier /* UI */ || | |
105 vr == ValueRepresentation_UnsignedLong /* UL */ || | |
106 vr == ValueRepresentation_UnsignedShort /* US */); | |
107 } | |
108 | |
109 | |
110 static void PrintBlock(const std::string& block) | |
111 { | |
112 for (size_t i = 0; i < block.size(); i++) | |
113 { | |
114 printf("%02x ", static_cast<uint8_t>(block[i])); | |
115 if (i % 16 == 15) | |
116 printf("\n"); | |
117 } | |
118 printf("\n"); | |
119 } | |
120 | |
121 | |
122 | |
123 bool DicomStreamReader::IsLittleEndian() const | |
124 { | |
125 return (transferSyntax_ != DicomTransferSyntax_BigEndianExplicit); | |
126 } | |
127 | |
128 | |
129 void DicomStreamReader::HandlePreamble(IVisitor& visitor, | |
130 const std::string& block) | |
131 { | |
132 //printf("PREAMBLE:\n"); | |
133 //PrintBlock(block); | |
134 | |
135 assert(block.size() == 144u); | |
136 assert(reader_.GetProcessedBytes() == 144u); | |
137 | |
138 /** | |
139 * The "DICOM file meta information" is always encoded using | |
140 * "Explicit VR Little Endian Transfer Syntax" | |
141 * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html | |
142 **/ | |
143 if (block[128] != 'D' || | |
144 block[129] != 'I' || | |
145 block[130] != 'C' || | |
146 block[131] != 'M' || | |
147 ReadTag(block.c_str() + 132, true) != DicomTag(0x0002, 0x0000) || | |
148 block[136] != 'U' || | |
149 block[137] != 'L' || | |
150 ReadUnsignedInteger16(block.c_str() + 138, true) != 4) | |
151 { | |
152 throw OrthancException(ErrorCode_BadFileFormat); | |
153 } | |
154 | |
155 uint32_t length = ReadUnsignedInteger32(block.c_str() + 140, true); | |
156 | |
157 reader_.Schedule(length); | |
158 state_ = State_MetaHeader; | |
159 } | |
160 | |
161 | |
162 void DicomStreamReader::HandleMetaHeader(IVisitor& visitor, | |
163 const std::string& block) | |
164 { | |
165 //printf("META-HEADER:\n"); | |
166 //PrintBlock(block); | |
167 | |
168 size_t pos = 0; | |
169 const char* p = block.c_str(); | |
170 | |
171 bool hasTransferSyntax = false; | |
172 | |
173 while (pos + 8 <= block.size()) | |
174 { | |
175 DicomTag tag = ReadTag(p + pos, true); | |
176 | |
177 ValueRepresentation vr = StringToValueRepresentation(std::string(p + pos + 4, 2), true); | |
178 | |
179 if (IsShortExplicitTag(vr)) | |
180 { | |
181 uint16_t length = ReadUnsignedInteger16(p + pos + 6, true); | |
182 | |
183 std::string value; | |
184 value.assign(p + pos + 8, length); | |
185 | |
186 if (tag.GetGroup() == 0x0002) | |
187 { | |
188 visitor.VisitMetaHeaderTag(tag, vr, value); | |
189 } | |
190 | |
191 if (tag == DICOM_TAG_TRANSFER_SYNTAX_UID) | |
192 { | |
193 // Remove possible padding byte | |
194 if (!value.empty() && | |
195 value[value.size() - 1] == '\0') | |
196 { | |
197 value.resize(value.size() - 1); | |
198 } | |
199 | |
200 if (LookupTransferSyntax(transferSyntax_, value)) | |
201 { | |
202 hasTransferSyntax = true; | |
203 } | |
204 else | |
205 { | |
206 throw OrthancException(ErrorCode_NotImplemented, "Unsupported transfer syntax: " + value); | |
207 } | |
208 } | |
209 | |
210 pos += length + 8; | |
211 } | |
212 else if (pos + 12 <= block.size()) | |
213 { | |
214 uint16_t reserved = ReadUnsignedInteger16(p + pos + 6, true); | |
215 if (reserved != 0) | |
216 { | |
217 break; | |
218 } | |
219 | |
220 uint32_t length = ReadUnsignedInteger32(p + pos + 8, true); | |
221 | |
222 std::string value; | |
223 value.assign(p + pos + 12, length); | |
224 | |
225 if (tag.GetGroup() == 0x0002) | |
226 { | |
227 visitor.VisitMetaHeaderTag(tag, vr, value); | |
228 } | |
229 | |
230 pos += length + 12; | |
231 } | |
232 } | |
233 | |
234 if (pos != block.size()) | |
235 { | |
236 throw OrthancException(ErrorCode_BadFileFormat); | |
237 } | |
238 | |
239 if (!hasTransferSyntax) | |
240 { | |
241 throw OrthancException(ErrorCode_BadFileFormat, "DICOM file meta-header without transfer syntax UID"); | |
242 } | |
243 | |
244 visitor.VisitTransferSyntax(transferSyntax_); | |
245 | |
246 reader_.Schedule(8); | |
247 state_ = State_DatasetTag; | |
248 } | |
249 | |
250 | |
251 void DicomStreamReader::HandleDatasetTag(const std::string& block, | |
252 const DicomTag& untilTag) | |
253 { | |
254 static const DicomTag DICOM_TAG_SEQUENCE_ITEM(0xfffe, 0xe000); | |
255 static const DicomTag DICOM_TAG_SEQUENCE_DELIMITATION_ITEM(0xfffe, 0xe00d); | |
256 static const DicomTag DICOM_TAG_SEQUENCE_DELIMITATION_SEQUENCE(0xfffe, 0xe0dd); | |
257 | |
258 assert(block.size() == 8u); | |
259 | |
260 const bool littleEndian = IsLittleEndian(); | |
261 DicomTag tag = ReadTag(block.c_str(), littleEndian); | |
262 | |
263 if (sequenceDepth_ == 0 && | |
264 tag >= untilTag) | |
265 { | |
266 state_ = State_Done; | |
267 return; | |
268 } | |
269 | |
270 if (tag == DICOM_TAG_SEQUENCE_ITEM || | |
271 tag == DICOM_TAG_SEQUENCE_DELIMITATION_ITEM || | |
272 tag == DICOM_TAG_SEQUENCE_DELIMITATION_SEQUENCE) | |
273 { | |
274 //printf("SEQUENCE TAG:\n"); | |
275 //PrintBlock(block); | |
276 | |
277 // The special sequence items are encoded like "Implicit VR" | |
278 uint32_t length = ReadUnsignedInteger32(block.c_str() + 4, littleEndian); | |
279 | |
280 if (tag == DICOM_TAG_SEQUENCE_ITEM) | |
281 { | |
282 for (unsigned int i = 0; i <= sequenceDepth_; i++) | |
283 printf(" "); | |
284 if (length == 0xffffffffu) | |
285 { | |
286 // Undefined length: Need to loop over the tags of the nested dataset | |
287 printf("...next dataset in sequence...\n"); | |
288 reader_.Schedule(8); | |
289 state_ = State_DatasetTag; | |
290 } | |
291 else | |
292 { | |
293 // Explicit length: Can skip the full sequence at once | |
294 printf("...next dataset in sequence... %u bytes\n", length); | |
295 reader_.Schedule(length); | |
296 state_ = State_DatasetValue; | |
297 } | |
298 } | |
299 else if (tag == DICOM_TAG_SEQUENCE_DELIMITATION_ITEM || | |
300 tag == DICOM_TAG_SEQUENCE_DELIMITATION_SEQUENCE) | |
301 { | |
302 if (length != 0 || | |
303 sequenceDepth_ == 0) | |
304 { | |
305 throw OrthancException(ErrorCode_BadFileFormat); | |
306 } | |
307 | |
308 if (tag == DICOM_TAG_SEQUENCE_DELIMITATION_SEQUENCE) | |
309 { | |
310 for (unsigned int i = 0; i < sequenceDepth_; i++) | |
311 printf(" "); | |
312 printf("...leaving sequence...\n"); | |
313 | |
314 sequenceDepth_ --; | |
315 } | |
316 else | |
317 { | |
318 if (sequenceDepth_ == 0) | |
319 { | |
320 throw OrthancException(ErrorCode_BadFileFormat); | |
321 } | |
322 } | |
323 | |
324 reader_.Schedule(8); | |
325 state_ = State_DatasetTag; | |
326 } | |
327 else | |
328 { | |
329 throw OrthancException(ErrorCode_InternalError); | |
330 } | |
331 } | |
332 else | |
333 { | |
334 //printf("DATASET TAG:\n"); | |
335 //PrintBlock(block); | |
336 | |
337 previousTag_ = tag; | |
338 | |
339 ValueRepresentation vr = ValueRepresentation_Unknown; | |
340 | |
341 if (transferSyntax_ == DicomTransferSyntax_LittleEndianImplicit) | |
342 { | |
343 if (sequenceDepth_ == 0) | |
344 { | |
345 danglingTag_ = tag; | |
346 danglingVR_ = vr; | |
347 } | |
348 | |
349 uint32_t length = ReadUnsignedInteger32(block.c_str() + 4, true /* little endian */); | |
350 HandleDatasetExplicitLength(length); | |
351 } | |
352 else | |
353 { | |
354 // This in an explicit transfer syntax | |
355 | |
356 vr = StringToValueRepresentation( | |
357 std::string(block.c_str() + 4, 2), false /* ignore unknown VR */); | |
358 | |
359 if (vr != ValueRepresentation_Sequence && | |
360 sequenceDepth_ > 0) | |
361 { | |
362 for (unsigned int i = 0; i <= sequenceDepth_; i++) | |
363 printf(" "); | |
364 printf("%s\n", tag.Format().c_str()); | |
365 } | |
366 | |
367 if (vr == ValueRepresentation_Sequence) | |
368 { | |
369 for (unsigned int i = 0; i <= sequenceDepth_; i++) | |
370 printf(" "); | |
371 printf("...entering sequence... %s\n", tag.Format().c_str()); | |
372 sequenceDepth_ ++; | |
373 reader_.Schedule(4); | |
374 state_ = State_SequenceExplicitLength; | |
375 } | |
376 else if (IsShortExplicitTag(vr)) | |
377 { | |
378 uint16_t length = ReadUnsignedInteger16(block.c_str() + 6, littleEndian); | |
379 | |
380 reader_.Schedule(length); | |
381 state_ = State_DatasetValue; | |
382 } | |
383 else | |
384 { | |
385 uint16_t reserved = ReadUnsignedInteger16(block.c_str() + 6, littleEndian); | |
386 if (reserved != 0) | |
387 { | |
388 throw OrthancException(ErrorCode_BadFileFormat); | |
389 } | |
390 | |
391 reader_.Schedule(4); | |
392 state_ = State_DatasetExplicitLength; | |
393 } | |
394 | |
395 if (sequenceDepth_ == 0) | |
396 { | |
397 danglingTag_ = tag; | |
398 danglingVR_ = vr; | |
399 } | |
400 } | |
401 } | |
402 } | |
403 | |
404 | |
405 void DicomStreamReader::HandleDatasetExplicitLength(uint32_t length) | |
406 { | |
407 if (length == 0xffffffffu) | |
408 { | |
409 /** | |
410 * This is the case of pixel data with compressed transfer | |
411 * syntaxes. Schedule the reading of the first tag of the | |
412 * nested dataset. | |
413 * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.5.html | |
414 **/ | |
415 | |
416 for (unsigned int i = 0; i <= sequenceDepth_; i++) | |
417 printf(" "); | |
418 printf("...entering sequence... %s\n", previousTag_.Format().c_str()); | |
419 | |
420 state_ = State_DatasetTag; | |
421 reader_.Schedule(8); | |
422 sequenceDepth_ ++; | |
423 } | |
424 else | |
425 { | |
426 reader_.Schedule(length); | |
427 state_ = State_DatasetValue; | |
428 } | |
429 } | |
430 | |
431 | |
432 void DicomStreamReader::HandleDatasetExplicitLength(const std::string& block) | |
433 { | |
434 //printf("DATASET TAG LENGTH:\n"); | |
435 //PrintBlock(block); | |
436 | |
437 assert(block.size() == 4); | |
438 | |
439 uint32_t length = ReadUnsignedInteger32(block.c_str(), IsLittleEndian()); | |
440 HandleDatasetExplicitLength(length); | |
441 } | |
442 | |
443 | |
444 void DicomStreamReader::HandleSequenceExplicitLength(const std::string& block) | |
445 { | |
446 //printf("DATASET TAG LENGTH:\n"); | |
447 //PrintBlock(block); | |
448 | |
449 assert(block.size() == 4); | |
450 | |
451 uint32_t length = ReadUnsignedInteger32(block.c_str(), IsLittleEndian()); | |
452 if (length == 0xffffffffu) | |
453 { | |
454 state_ = State_DatasetTag; | |
455 reader_.Schedule(8); | |
456 } | |
457 else | |
458 { | |
459 for (unsigned int i = 0; i <= sequenceDepth_; i++) | |
460 printf(" "); | |
461 printf("...skipping sequence thanks to explicit length... %d\n", length); | |
462 | |
463 reader_.Schedule(length); | |
464 state_ = State_SequenceExplicitValue; | |
465 } | |
466 } | |
467 | |
468 | |
469 void DicomStreamReader::HandleSequenceExplicitValue() | |
470 { | |
471 if (sequenceDepth_ == 0) | |
472 { | |
473 throw OrthancException(ErrorCode_InternalError); | |
474 } | |
475 | |
476 sequenceDepth_ --; | |
477 | |
478 state_ = State_DatasetTag; | |
479 reader_.Schedule(8); | |
480 } | |
481 | |
482 | |
483 void DicomStreamReader::HandleDatasetValue(IVisitor& visitor, | |
484 const std::string& block) | |
485 { | |
486 if (sequenceDepth_ == 0) | |
487 { | |
488 bool c; | |
489 | |
490 if (!block.empty() && | |
491 (block[block.size() - 1] == ' ' || | |
492 block[block.size() - 1] == '\0') && | |
493 (danglingVR_ == ValueRepresentation_ApplicationEntity || | |
494 danglingVR_ == ValueRepresentation_AgeString || | |
495 danglingVR_ == ValueRepresentation_CodeString || | |
496 danglingVR_ == ValueRepresentation_DecimalString || | |
497 danglingVR_ == ValueRepresentation_IntegerString || | |
498 danglingVR_ == ValueRepresentation_LongString || | |
499 danglingVR_ == ValueRepresentation_LongText || | |
500 danglingVR_ == ValueRepresentation_PersonName || | |
501 danglingVR_ == ValueRepresentation_ShortString || | |
502 danglingVR_ == ValueRepresentation_ShortText || | |
503 danglingVR_ == ValueRepresentation_UniqueIdentifier || | |
504 danglingVR_ == ValueRepresentation_UnlimitedText)) | |
505 { | |
506 std::string s(block.begin(), block.end() - 1); | |
507 c = visitor.VisitDatasetTag(danglingTag_, danglingVR_, s, IsLittleEndian()); | |
508 } | |
509 else | |
510 { | |
511 c = visitor.VisitDatasetTag(danglingTag_, danglingVR_, block, IsLittleEndian()); | |
512 } | |
513 | |
514 if (!c) | |
515 { | |
516 state_ = State_Done; | |
517 return; | |
518 } | |
519 } | |
520 | |
521 reader_.Schedule(8); | |
522 state_ = State_DatasetTag; | |
523 } | |
524 | |
525 | |
526 DicomStreamReader::DicomStreamReader(std::istream& stream) : | |
527 reader_(stream), | |
528 state_(State_Preamble), | |
529 transferSyntax_(DicomTransferSyntax_LittleEndianImplicit), // Dummy | |
530 previousTag_(0x0000, 0x0000), // Dummy | |
531 danglingTag_(0x0000, 0x0000), // Dummy | |
532 danglingVR_(ValueRepresentation_Unknown), // Dummy | |
533 sequenceDepth_(0) | |
534 { | |
535 reader_.Schedule(128 /* empty header */ + | |
536 4 /* "DICM" magic value */ + | |
537 4 /* (0x0002, 0x0000) tag */ + | |
538 2 /* value representation of (0x0002, 0x0000) == "UL" */ + | |
539 2 /* length of "UL" value == 4 */ + | |
540 4 /* actual length of the meta-header */); | |
541 } | |
542 | |
543 | |
544 void DicomStreamReader::Consume(IVisitor& visitor, | |
545 const DicomTag& untilTag) | |
546 { | |
547 while (state_ != State_Done) | |
548 { | |
549 std::string block; | |
550 if (reader_.Read(block)) | |
551 { | |
552 switch (state_) | |
553 { | |
554 case State_Preamble: | |
555 HandlePreamble(visitor, block); | |
556 break; | |
557 | |
558 case State_MetaHeader: | |
559 HandleMetaHeader(visitor, block); | |
560 break; | |
561 | |
562 case State_DatasetTag: | |
563 HandleDatasetTag(block, untilTag); | |
564 break; | |
565 | |
566 case State_DatasetExplicitLength: | |
567 HandleDatasetExplicitLength(block); | |
568 break; | |
569 | |
570 case State_SequenceExplicitLength: | |
571 HandleSequenceExplicitLength(block); | |
572 break; | |
573 | |
574 case State_SequenceExplicitValue: | |
575 HandleSequenceExplicitValue(); | |
576 break; | |
577 | |
578 case State_DatasetValue: | |
579 HandleDatasetValue(visitor, block); | |
580 break; | |
581 | |
582 default: | |
583 throw OrthancException(ErrorCode_InternalError); | |
584 } | |
585 } | |
586 else | |
587 { | |
588 return; // No more data in the stream | |
589 } | |
590 } | |
591 } | |
592 | |
593 | |
594 void DicomStreamReader::Consume(IVisitor& visitor) | |
595 { | |
596 DicomTag untilTag(0xffff, 0xffff); | |
597 Consume(visitor, untilTag); | |
598 } | |
599 } |