Mercurial > hg > orthanc-wsi
comparison Resources/Orthanc/OrthancServer/FromDcmtkBridge.cpp @ 59:7a3853d51c45
Move "Framework/Orthanc/" as "Resources/Orthanc/"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 25 Nov 2016 16:34:34 +0100 |
parents | Framework/Orthanc/OrthancServer/FromDcmtkBridge.cpp@06847108819c |
children | 5b127ab0080b |
comparison
equal
deleted
inserted
replaced
58:35468714a38e | 59:7a3853d51c45 |
---|---|
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 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * In addition, as a special exception, the copyright holders of this | |
12 * program give permission to link the code of its release with the | |
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
14 * that use the same license as the "OpenSSL" library), and distribute | |
15 * the linked executables. You must obey the GNU General Public License | |
16 * in all respects for all of the code used other than "OpenSSL". If you | |
17 * modify file(s) with this exception, you may extend this exception to | |
18 * your version of the file(s), but you are not obligated to do so. If | |
19 * you do not wish to do so, delete this exception statement from your | |
20 * version. If you delete this exception statement from all source files | |
21 * in the program, then also delete it here. | |
22 * | |
23 * This program is distributed in the hope that it will be useful, but | |
24 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
26 * General Public License for more details. | |
27 * | |
28 * You should have received a copy of the GNU General Public License | |
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
30 **/ | |
31 | |
32 | |
33 #include "PrecompiledHeadersServer.h" | |
34 | |
35 #ifndef NOMINMAX | |
36 #define NOMINMAX | |
37 #endif | |
38 | |
39 #include "FromDcmtkBridge.h" | |
40 #include "ToDcmtkBridge.h" | |
41 #include "../Core/Logging.h" | |
42 #include "../Core/SystemToolbox.h" | |
43 #include "../Core/Toolbox.h" | |
44 #include "../Core/TemporaryFile.h" | |
45 #include "../Core/OrthancException.h" | |
46 | |
47 #include <list> | |
48 #include <limits> | |
49 | |
50 #include <boost/lexical_cast.hpp> | |
51 #include <boost/filesystem.hpp> | |
52 #include <boost/algorithm/string/predicate.hpp> | |
53 | |
54 #include <dcmtk/dcmdata/dcdeftag.h> | |
55 #include <dcmtk/dcmdata/dcdicent.h> | |
56 #include <dcmtk/dcmdata/dcdict.h> | |
57 #include <dcmtk/dcmdata/dcfilefo.h> | |
58 #include <dcmtk/dcmdata/dcostrmb.h> | |
59 #include <dcmtk/dcmdata/dcpixel.h> | |
60 #include <dcmtk/dcmdata/dcuid.h> | |
61 #include <dcmtk/dcmdata/dcistrmb.h> | |
62 | |
63 #include <dcmtk/dcmdata/dcvrae.h> | |
64 #include <dcmtk/dcmdata/dcvras.h> | |
65 #include <dcmtk/dcmdata/dcvrat.h> | |
66 #include <dcmtk/dcmdata/dcvrcs.h> | |
67 #include <dcmtk/dcmdata/dcvrda.h> | |
68 #include <dcmtk/dcmdata/dcvrds.h> | |
69 #include <dcmtk/dcmdata/dcvrdt.h> | |
70 #include <dcmtk/dcmdata/dcvrfd.h> | |
71 #include <dcmtk/dcmdata/dcvrfl.h> | |
72 #include <dcmtk/dcmdata/dcvris.h> | |
73 #include <dcmtk/dcmdata/dcvrlo.h> | |
74 #include <dcmtk/dcmdata/dcvrlt.h> | |
75 #include <dcmtk/dcmdata/dcvrpn.h> | |
76 #include <dcmtk/dcmdata/dcvrsh.h> | |
77 #include <dcmtk/dcmdata/dcvrsl.h> | |
78 #include <dcmtk/dcmdata/dcvrss.h> | |
79 #include <dcmtk/dcmdata/dcvrst.h> | |
80 #include <dcmtk/dcmdata/dcvrtm.h> | |
81 #include <dcmtk/dcmdata/dcvrui.h> | |
82 #include <dcmtk/dcmdata/dcvrul.h> | |
83 #include <dcmtk/dcmdata/dcvrus.h> | |
84 #include <dcmtk/dcmdata/dcvrut.h> | |
85 | |
86 | |
87 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 | |
88 #include <EmbeddedResources.h> | |
89 #endif | |
90 | |
91 | |
92 namespace Orthanc | |
93 { | |
94 static inline uint16_t GetCharValue(char c) | |
95 { | |
96 if (c >= '0' && c <= '9') | |
97 return c - '0'; | |
98 else if (c >= 'a' && c <= 'f') | |
99 return c - 'a' + 10; | |
100 else if (c >= 'A' && c <= 'F') | |
101 return c - 'A' + 10; | |
102 else | |
103 return 0; | |
104 } | |
105 | |
106 static inline uint16_t GetTagValue(const char* c) | |
107 { | |
108 return ((GetCharValue(c[0]) << 12) + | |
109 (GetCharValue(c[1]) << 8) + | |
110 (GetCharValue(c[2]) << 4) + | |
111 GetCharValue(c[3])); | |
112 } | |
113 | |
114 | |
115 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 | |
116 static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, | |
117 EmbeddedResources::FileResourceId resource) | |
118 { | |
119 std::string content; | |
120 EmbeddedResources::GetFileResource(content, resource); | |
121 | |
122 TemporaryFile tmp; | |
123 tmp.Write(content); | |
124 | |
125 if (!dictionary.loadDictionary(tmp.GetPath().c_str())) | |
126 { | |
127 LOG(ERROR) << "Cannot read embedded dictionary. Under Windows, make sure that " | |
128 << "your TEMP directory does not contain special characters."; | |
129 throw OrthancException(ErrorCode_InternalError); | |
130 } | |
131 } | |
132 | |
133 #else | |
134 static void LoadExternalDictionary(DcmDataDictionary& dictionary, | |
135 const std::string& directory, | |
136 const std::string& filename) | |
137 { | |
138 boost::filesystem::path p = directory; | |
139 p = p / filename; | |
140 | |
141 LOG(WARNING) << "Loading the external DICOM dictionary " << p; | |
142 | |
143 if (!dictionary.loadDictionary(p.string().c_str())) | |
144 { | |
145 throw OrthancException(ErrorCode_InternalError); | |
146 } | |
147 } | |
148 #endif | |
149 | |
150 | |
151 namespace | |
152 { | |
153 class DictionaryLocker | |
154 { | |
155 private: | |
156 DcmDataDictionary& dictionary_; | |
157 | |
158 public: | |
159 DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) | |
160 { | |
161 } | |
162 | |
163 ~DictionaryLocker() | |
164 { | |
165 dcmDataDict.unlock(); | |
166 } | |
167 | |
168 DcmDataDictionary& operator*() | |
169 { | |
170 return dictionary_; | |
171 } | |
172 | |
173 DcmDataDictionary* operator->() | |
174 { | |
175 return &dictionary_; | |
176 } | |
177 }; | |
178 } | |
179 | |
180 | |
181 void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) | |
182 { | |
183 { | |
184 DictionaryLocker locker; | |
185 | |
186 locker->clear(); | |
187 | |
188 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 | |
189 LOG(WARNING) << "Loading the embedded dictionaries"; | |
190 /** | |
191 * Do not load DICONDE dictionary, it breaks the other tags. The | |
192 * command "strace storescu 2>&1 |grep dic" shows that DICONDE | |
193 * dictionary is not loaded by storescu. | |
194 **/ | |
195 //LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICONDE); | |
196 | |
197 LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_DICOM); | |
198 | |
199 if (loadPrivateDictionary) | |
200 { | |
201 LOG(INFO) << "Loading the embedded dictionary of private tags"; | |
202 LoadEmbeddedDictionary(*locker, EmbeddedResources::DICTIONARY_PRIVATE); | |
203 } | |
204 else | |
205 { | |
206 LOG(INFO) << "The dictionary of private tags has not been loaded"; | |
207 } | |
208 | |
209 #elif defined(__linux__) || defined(__FreeBSD_kernel__) | |
210 std::string path = DCMTK_DICTIONARY_DIR; | |
211 | |
212 const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); | |
213 if (env != NULL) | |
214 { | |
215 path = std::string(env); | |
216 } | |
217 | |
218 LoadExternalDictionary(*locker, path, "dicom.dic"); | |
219 | |
220 if (loadPrivateDictionary) | |
221 { | |
222 LoadExternalDictionary(*locker, path, "private.dic"); | |
223 } | |
224 else | |
225 { | |
226 LOG(INFO) << "The dictionary of private tags has not been loaded"; | |
227 } | |
228 | |
229 #else | |
230 #error Support your platform here | |
231 #endif | |
232 } | |
233 | |
234 /* make sure data dictionary is loaded */ | |
235 if (!dcmDataDict.isDictionaryLoaded()) | |
236 { | |
237 LOG(ERROR) << "No DICOM dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; | |
238 throw OrthancException(ErrorCode_InternalError); | |
239 } | |
240 | |
241 { | |
242 // Test the dictionary with a simple DICOM tag | |
243 DcmTag key(0x0010, 0x1030); // This is PatientWeight | |
244 if (key.getEVR() != EVR_DS) | |
245 { | |
246 LOG(ERROR) << "The DICOM dictionary has not been correctly read"; | |
247 throw OrthancException(ErrorCode_InternalError); | |
248 } | |
249 } | |
250 } | |
251 | |
252 | |
253 void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, | |
254 ValueRepresentation vr, | |
255 const std::string& name, | |
256 unsigned int minMultiplicity, | |
257 unsigned int maxMultiplicity, | |
258 const std::string& privateCreator) | |
259 { | |
260 if (minMultiplicity < 1) | |
261 { | |
262 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
263 } | |
264 | |
265 bool arbitrary = false; | |
266 if (maxMultiplicity == 0) | |
267 { | |
268 maxMultiplicity = DcmVariableVM; | |
269 arbitrary = true; | |
270 } | |
271 else if (maxMultiplicity < minMultiplicity) | |
272 { | |
273 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
274 } | |
275 | |
276 DcmEVR evr = ToDcmtkBridge::Convert(vr); | |
277 | |
278 LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " | |
279 << name << " (multiplicity: " << minMultiplicity << "-" | |
280 << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")"; | |
281 | |
282 std::auto_ptr<DcmDictEntry> entry; | |
283 if (privateCreator.empty()) | |
284 { | |
285 if (tag.GetGroup() % 2 == 1) | |
286 { | |
287 char buf[128]; | |
288 sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " | |
289 "but no private creator was associated with it", | |
290 tag.GetGroup(), tag.GetElement()); | |
291 LOG(WARNING) << buf; | |
292 } | |
293 | |
294 entry.reset(new DcmDictEntry(tag.GetGroup(), | |
295 tag.GetElement(), | |
296 evr, name.c_str(), | |
297 static_cast<int>(minMultiplicity), | |
298 static_cast<int>(maxMultiplicity), | |
299 NULL /* version */, | |
300 OFTrue /* doCopyString */, | |
301 NULL /* private creator */)); | |
302 } | |
303 else | |
304 { | |
305 // "Private Data Elements have an odd Group Number that is not | |
306 // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or | |
307 // (FFFF,eeee)." | |
308 if (tag.GetGroup() % 2 == 0 /* even */ || | |
309 tag.GetGroup() == 0x0001 || | |
310 tag.GetGroup() == 0x0003 || | |
311 tag.GetGroup() == 0x0005 || | |
312 tag.GetGroup() == 0x0007 || | |
313 tag.GetGroup() == 0xffff) | |
314 { | |
315 char buf[128]; | |
316 sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", | |
317 tag.GetGroup(), tag.GetElement()); | |
318 LOG(ERROR) << buf; | |
319 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
320 } | |
321 | |
322 entry.reset(new DcmDictEntry(tag.GetGroup(), | |
323 tag.GetElement(), | |
324 evr, name.c_str(), | |
325 static_cast<int>(minMultiplicity), | |
326 static_cast<int>(maxMultiplicity), | |
327 "private" /* version */, | |
328 OFTrue /* doCopyString */, | |
329 privateCreator.c_str())); | |
330 } | |
331 | |
332 entry->setGroupRangeRestriction(DcmDictRange_Unspecified); | |
333 entry->setElementRangeRestriction(DcmDictRange_Unspecified); | |
334 | |
335 { | |
336 DictionaryLocker locker; | |
337 | |
338 if (locker->findEntry(name.c_str())) | |
339 { | |
340 LOG(ERROR) << "Cannot register two tags with the same symbolic name \"" << name << "\""; | |
341 throw OrthancException(ErrorCode_AlreadyExistingTag); | |
342 } | |
343 | |
344 locker->addEntry(entry.release()); | |
345 } | |
346 } | |
347 | |
348 | |
349 Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset, | |
350 Encoding defaultEncoding) | |
351 { | |
352 Encoding encoding = defaultEncoding; | |
353 | |
354 OFString tmp; | |
355 if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) | |
356 { | |
357 std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); | |
358 | |
359 if (characterSet.empty()) | |
360 { | |
361 // Empty specific character set tag: Use the default encoding | |
362 } | |
363 else if (GetDicomEncoding(encoding, characterSet.c_str())) | |
364 { | |
365 // The specific character set is supported by the Orthanc core | |
366 } | |
367 else | |
368 { | |
369 LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet | |
370 << ", fallback to ASCII (remove all special characters)"; | |
371 encoding = Encoding_Ascii; | |
372 } | |
373 } | |
374 else | |
375 { | |
376 // No specific character set tag: Use the default encoding | |
377 } | |
378 | |
379 return encoding; | |
380 } | |
381 | |
382 | |
383 void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, | |
384 DcmItem& dataset, | |
385 unsigned int maxStringLength, | |
386 Encoding defaultEncoding) | |
387 { | |
388 Encoding encoding = DetectEncoding(dataset, defaultEncoding); | |
389 | |
390 target.Clear(); | |
391 for (unsigned long i = 0; i < dataset.card(); i++) | |
392 { | |
393 DcmElement* element = dataset.getElement(i); | |
394 if (element && element->isLeaf()) | |
395 { | |
396 target.SetValue(element->getTag().getGTag(), | |
397 element->getTag().getETag(), | |
398 ConvertLeafElement(*element, DicomToJsonFlags_Default, maxStringLength, encoding)); | |
399 } | |
400 } | |
401 } | |
402 | |
403 | |
404 DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) | |
405 { | |
406 return DicomTag(tag.getGTag(), tag.getETag()); | |
407 } | |
408 | |
409 | |
410 DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) | |
411 { | |
412 return DicomTag(element.getGTag(), element.getETag()); | |
413 } | |
414 | |
415 | |
416 DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, | |
417 DicomToJsonFlags flags, | |
418 unsigned int maxStringLength, | |
419 Encoding encoding) | |
420 { | |
421 if (!element.isLeaf()) | |
422 { | |
423 // This function is only applicable to leaf elements | |
424 throw OrthancException(ErrorCode_BadParameterType); | |
425 } | |
426 | |
427 char *c = NULL; | |
428 if (element.isaString() && | |
429 element.getString(c).good()) | |
430 { | |
431 if (c == NULL) // This case corresponds to the empty string | |
432 { | |
433 return new DicomValue("", false); | |
434 } | |
435 else | |
436 { | |
437 std::string s(c); | |
438 std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); | |
439 | |
440 if (maxStringLength != 0 && | |
441 utf8.size() > maxStringLength) | |
442 { | |
443 return new DicomValue; // Too long, create a NULL value | |
444 } | |
445 else | |
446 { | |
447 return new DicomValue(utf8, false); | |
448 } | |
449 } | |
450 } | |
451 | |
452 | |
453 if (element.getVR() == EVR_UN) | |
454 { | |
455 // Unknown value representation: Lookup in the dictionary. This | |
456 // is notably the case for private tags registered with the | |
457 // "Dictionary" configuration option. | |
458 DictionaryLocker locker; | |
459 | |
460 const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), | |
461 element.getTag().getPrivateCreator()); | |
462 if (entry != NULL && | |
463 entry->getVR().isaString()) | |
464 { | |
465 Uint8* data = NULL; | |
466 | |
467 // At (*), we do not try and convert to UTF-8, as nothing says | |
468 // the encoding of the private tag is the same as that of the | |
469 // remaining of the DICOM dataset. Only go for ASCII strings. | |
470 | |
471 if (element.getUint8Array(data) == EC_Normal && | |
472 Toolbox::IsAsciiString(data, element.getLength())) // (*) | |
473 { | |
474 if (data == NULL) | |
475 { | |
476 return new DicomValue("", false); // Empty string | |
477 } | |
478 else if (maxStringLength != 0 && | |
479 element.getLength() > maxStringLength) | |
480 { | |
481 return new DicomValue; // Too long, create a NULL value | |
482 } | |
483 else | |
484 { | |
485 std::string s(reinterpret_cast<const char*>(data), element.getLength()); | |
486 return new DicomValue(s, false); | |
487 } | |
488 } | |
489 } | |
490 } | |
491 | |
492 | |
493 try | |
494 { | |
495 // http://support.dcmtk.org/docs/dcvr_8h-source.html | |
496 switch (element.getVR()) | |
497 { | |
498 | |
499 /** | |
500 * Deal with binary data (including PixelData). | |
501 **/ | |
502 | |
503 case EVR_OB: // other byte | |
504 case EVR_OF: // other float | |
505 case EVR_OW: // other word | |
506 case EVR_UN: // unknown value representation | |
507 case EVR_ox: // OB or OW depending on context | |
508 case EVR_DS: // decimal string | |
509 case EVR_IS: // integer string | |
510 case EVR_AS: // age string | |
511 case EVR_DA: // date string | |
512 case EVR_DT: // date time string | |
513 case EVR_TM: // time string | |
514 case EVR_AE: // application entity title | |
515 case EVR_CS: // code string | |
516 case EVR_SH: // short string | |
517 case EVR_LO: // long string | |
518 case EVR_ST: // short text | |
519 case EVR_LT: // long text | |
520 case EVR_UT: // unlimited text | |
521 case EVR_PN: // person name | |
522 case EVR_UI: // unique identifier | |
523 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) | |
524 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR | |
525 { | |
526 if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) | |
527 { | |
528 Uint8* data = NULL; | |
529 if (element.getUint8Array(data) == EC_Normal) | |
530 { | |
531 return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true); | |
532 } | |
533 } | |
534 | |
535 return new DicomValue; | |
536 } | |
537 | |
538 /** | |
539 * Numeric types | |
540 **/ | |
541 | |
542 case EVR_SL: // signed long | |
543 { | |
544 Sint32 f; | |
545 if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good()) | |
546 return new DicomValue(boost::lexical_cast<std::string>(f), false); | |
547 else | |
548 return new DicomValue; | |
549 } | |
550 | |
551 case EVR_SS: // signed short | |
552 { | |
553 Sint16 f; | |
554 if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good()) | |
555 return new DicomValue(boost::lexical_cast<std::string>(f), false); | |
556 else | |
557 return new DicomValue; | |
558 } | |
559 | |
560 case EVR_UL: // unsigned long | |
561 { | |
562 Uint32 f; | |
563 if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good()) | |
564 return new DicomValue(boost::lexical_cast<std::string>(f), false); | |
565 else | |
566 return new DicomValue; | |
567 } | |
568 | |
569 case EVR_US: // unsigned short | |
570 { | |
571 Uint16 f; | |
572 if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good()) | |
573 return new DicomValue(boost::lexical_cast<std::string>(f), false); | |
574 else | |
575 return new DicomValue; | |
576 } | |
577 | |
578 case EVR_FL: // float single-precision | |
579 { | |
580 Float32 f; | |
581 if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good()) | |
582 return new DicomValue(boost::lexical_cast<std::string>(f), false); | |
583 else | |
584 return new DicomValue; | |
585 } | |
586 | |
587 case EVR_FD: // float double-precision | |
588 { | |
589 Float64 f; | |
590 if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good()) | |
591 return new DicomValue(boost::lexical_cast<std::string>(f), false); | |
592 else | |
593 return new DicomValue; | |
594 } | |
595 | |
596 | |
597 /** | |
598 * Attribute tag. | |
599 **/ | |
600 | |
601 case EVR_AT: | |
602 { | |
603 DcmTagKey tag; | |
604 if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good()) | |
605 { | |
606 DicomTag t(tag.getGroup(), tag.getElement()); | |
607 return new DicomValue(t.Format(), false); | |
608 } | |
609 else | |
610 { | |
611 return new DicomValue; | |
612 } | |
613 } | |
614 | |
615 | |
616 /** | |
617 * Sequence types, should never occur at this point because of | |
618 * "element.isLeaf()". | |
619 **/ | |
620 | |
621 case EVR_SQ: // sequence of items | |
622 return new DicomValue; | |
623 | |
624 | |
625 /** | |
626 * Internal to DCMTK. | |
627 **/ | |
628 | |
629 case EVR_xs: // SS or US depending on context | |
630 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) | |
631 case EVR_na: // na="not applicable", for data which has no VR | |
632 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor | |
633 case EVR_item: // used internally for items | |
634 case EVR_metainfo: // used internally for meta info datasets | |
635 case EVR_dataset: // used internally for datasets | |
636 case EVR_fileFormat: // used internally for DICOM files | |
637 case EVR_dicomDir: // used internally for DICOMDIR objects | |
638 case EVR_dirRecord: // used internally for DICOMDIR records | |
639 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image | |
640 case EVR_pixelItem: // used internally for pixel items in a compressed image | |
641 case EVR_PixelData: // used internally for uncompressed pixeld data | |
642 case EVR_OverlayData: // used internally for overlay data | |
643 return new DicomValue; | |
644 | |
645 | |
646 /** | |
647 * Default case. | |
648 **/ | |
649 | |
650 default: | |
651 return new DicomValue; | |
652 } | |
653 } | |
654 catch (boost::bad_lexical_cast) | |
655 { | |
656 return new DicomValue; | |
657 } | |
658 catch (std::bad_cast) | |
659 { | |
660 return new DicomValue; | |
661 } | |
662 } | |
663 | |
664 | |
665 static Json::Value& PrepareNode(Json::Value& parent, | |
666 DcmElement& element, | |
667 DicomToJsonFormat format) | |
668 { | |
669 assert(parent.type() == Json::objectValue); | |
670 | |
671 DicomTag tag(FromDcmtkBridge::GetTag(element)); | |
672 const std::string formattedTag = tag.Format(); | |
673 | |
674 if (format == DicomToJsonFormat_Short) | |
675 { | |
676 parent[formattedTag] = Json::nullValue; | |
677 return parent[formattedTag]; | |
678 } | |
679 | |
680 // This code gives access to the name of the private tags | |
681 std::string tagName = FromDcmtkBridge::GetTagName(element); | |
682 | |
683 switch (format) | |
684 { | |
685 case DicomToJsonFormat_Human: | |
686 parent[tagName] = Json::nullValue; | |
687 return parent[tagName]; | |
688 | |
689 case DicomToJsonFormat_Full: | |
690 { | |
691 parent[formattedTag] = Json::objectValue; | |
692 Json::Value& node = parent[formattedTag]; | |
693 | |
694 if (element.isLeaf()) | |
695 { | |
696 node["Name"] = tagName; | |
697 | |
698 if (element.getTag().getPrivateCreator() != NULL) | |
699 { | |
700 node["PrivateCreator"] = element.getTag().getPrivateCreator(); | |
701 } | |
702 | |
703 return node; | |
704 } | |
705 else | |
706 { | |
707 node["Name"] = tagName; | |
708 node["Type"] = "Sequence"; | |
709 node["Value"] = Json::nullValue; | |
710 return node["Value"]; | |
711 } | |
712 } | |
713 | |
714 default: | |
715 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
716 } | |
717 } | |
718 | |
719 | |
720 static void LeafValueToJson(Json::Value& target, | |
721 const DicomValue& value, | |
722 DicomToJsonFormat format, | |
723 DicomToJsonFlags flags, | |
724 unsigned int maxStringLength) | |
725 { | |
726 Json::Value* targetValue = NULL; | |
727 Json::Value* targetType = NULL; | |
728 | |
729 switch (format) | |
730 { | |
731 case DicomToJsonFormat_Short: | |
732 case DicomToJsonFormat_Human: | |
733 { | |
734 assert(target.type() == Json::nullValue); | |
735 targetValue = ⌖ | |
736 break; | |
737 } | |
738 | |
739 case DicomToJsonFormat_Full: | |
740 { | |
741 assert(target.type() == Json::objectValue); | |
742 target["Value"] = Json::nullValue; | |
743 target["Type"] = Json::nullValue; | |
744 targetType = &target["Type"]; | |
745 targetValue = &target["Value"]; | |
746 break; | |
747 } | |
748 | |
749 default: | |
750 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
751 } | |
752 | |
753 assert(targetValue != NULL); | |
754 assert(targetValue->type() == Json::nullValue); | |
755 assert(targetType == NULL || targetType->type() == Json::nullValue); | |
756 | |
757 if (value.IsNull()) | |
758 { | |
759 if (targetType != NULL) | |
760 { | |
761 *targetType = "Null"; | |
762 } | |
763 } | |
764 else if (value.IsBinary()) | |
765 { | |
766 if (flags & DicomToJsonFlags_ConvertBinaryToAscii) | |
767 { | |
768 *targetValue = Toolbox::ConvertToAscii(value.GetContent()); | |
769 } | |
770 else | |
771 { | |
772 std::string s; | |
773 value.FormatDataUriScheme(s); | |
774 *targetValue = s; | |
775 } | |
776 | |
777 if (targetType != NULL) | |
778 { | |
779 *targetType = "Binary"; | |
780 } | |
781 } | |
782 else if (maxStringLength == 0 || | |
783 value.GetContent().size() <= maxStringLength) | |
784 { | |
785 *targetValue = value.GetContent(); | |
786 | |
787 if (targetType != NULL) | |
788 { | |
789 *targetType = "String"; | |
790 } | |
791 } | |
792 else | |
793 { | |
794 if (targetType != NULL) | |
795 { | |
796 *targetType = "TooLong"; | |
797 } | |
798 } | |
799 } | |
800 | |
801 | |
802 void FromDcmtkBridge::ElementToJson(Json::Value& parent, | |
803 DcmElement& element, | |
804 DicomToJsonFormat format, | |
805 DicomToJsonFlags flags, | |
806 unsigned int maxStringLength, | |
807 Encoding encoding) | |
808 { | |
809 if (parent.type() == Json::nullValue) | |
810 { | |
811 parent = Json::objectValue; | |
812 } | |
813 | |
814 assert(parent.type() == Json::objectValue); | |
815 Json::Value& target = PrepareNode(parent, element, format); | |
816 | |
817 if (element.isLeaf()) | |
818 { | |
819 // The "0" below lets "LeafValueToJson()" take care of "TooLong" values | |
820 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, flags, 0, encoding)); | |
821 LeafValueToJson(target, *v, format, flags, maxStringLength); | |
822 } | |
823 else | |
824 { | |
825 assert(target.type() == Json::nullValue); | |
826 target = Json::arrayValue; | |
827 | |
828 // "All subclasses of DcmElement except for DcmSequenceOfItems | |
829 // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset | |
830 // etc. are not." The following dynamic_cast is thus OK. | |
831 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element); | |
832 | |
833 for (unsigned long i = 0; i < sequence.card(); i++) | |
834 { | |
835 DcmItem* child = sequence.getItem(i); | |
836 Json::Value& v = target.append(Json::objectValue); | |
837 DatasetToJson(v, *child, format, flags, maxStringLength, encoding); | |
838 } | |
839 } | |
840 } | |
841 | |
842 | |
843 void FromDcmtkBridge::DatasetToJson(Json::Value& parent, | |
844 DcmItem& item, | |
845 DicomToJsonFormat format, | |
846 DicomToJsonFlags flags, | |
847 unsigned int maxStringLength, | |
848 Encoding encoding) | |
849 { | |
850 assert(parent.type() == Json::objectValue); | |
851 | |
852 for (unsigned long i = 0; i < item.card(); i++) | |
853 { | |
854 DcmElement* element = item.getElement(i); | |
855 if (element == NULL) | |
856 { | |
857 throw OrthancException(ErrorCode_InternalError); | |
858 } | |
859 | |
860 DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); | |
861 | |
862 /*element->getTag().isPrivate()*/ | |
863 if (tag.IsPrivate() && | |
864 !(flags & DicomToJsonFlags_IncludePrivateTags)) | |
865 { | |
866 continue; | |
867 } | |
868 | |
869 if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) | |
870 { | |
871 DictionaryLocker locker; | |
872 if (locker->findEntry(element->getTag(), NULL) == NULL) | |
873 { | |
874 continue; | |
875 } | |
876 } | |
877 | |
878 DcmEVR evr = element->getTag().getEVR(); | |
879 if (evr == EVR_OB || | |
880 evr == EVR_OF || | |
881 evr == EVR_OW || | |
882 evr == EVR_UN || | |
883 evr == EVR_ox) | |
884 { | |
885 // This is a binary tag | |
886 if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || | |
887 (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) | |
888 { | |
889 continue; | |
890 } | |
891 } | |
892 | |
893 FromDcmtkBridge::ElementToJson(parent, *element, format, flags, maxStringLength, encoding); | |
894 } | |
895 } | |
896 | |
897 | |
898 void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, | |
899 DcmDataset& dataset, | |
900 DicomToJsonFormat format, | |
901 DicomToJsonFlags flags, | |
902 unsigned int maxStringLength, | |
903 Encoding defaultEncoding) | |
904 { | |
905 Encoding encoding = DetectEncoding(dataset, defaultEncoding); | |
906 | |
907 target = Json::objectValue; | |
908 DatasetToJson(target, dataset, format, flags, maxStringLength, encoding); | |
909 } | |
910 | |
911 | |
912 void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, | |
913 DcmMetaInfo& dataset, | |
914 DicomToJsonFormat format, | |
915 DicomToJsonFlags flags, | |
916 unsigned int maxStringLength) | |
917 { | |
918 target = Json::objectValue; | |
919 DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii); | |
920 } | |
921 | |
922 | |
923 | |
924 static std::string GetTagNameInternal(DcmTag& tag) | |
925 { | |
926 { | |
927 // Some patches for important tags because of different DICOM | |
928 // dictionaries between DCMTK versions | |
929 DicomTag tmp(tag.getGroup(), tag.getElement()); | |
930 std::string n = tmp.GetMainTagsName(); | |
931 if (n.size() != 0) | |
932 { | |
933 return n; | |
934 } | |
935 // End of patches | |
936 } | |
937 | |
938 #if 0 | |
939 // This version explicitly calls the dictionary | |
940 const DcmDataDictionary& dict = dcmDataDict.rdlock(); | |
941 const DcmDictEntry* entry = dict.findEntry(tag, NULL); | |
942 | |
943 std::string s(DcmTag_ERROR_TagName); | |
944 if (entry != NULL) | |
945 { | |
946 s = std::string(entry->getTagName()); | |
947 } | |
948 | |
949 dcmDataDict.unlock(); | |
950 return s; | |
951 #else | |
952 const char* name = tag.getTagName(); | |
953 if (name == NULL) | |
954 { | |
955 return DcmTag_ERROR_TagName; | |
956 } | |
957 else | |
958 { | |
959 return std::string(name); | |
960 } | |
961 #endif | |
962 } | |
963 | |
964 | |
965 std::string FromDcmtkBridge::GetTagName(const DicomTag& t, | |
966 const std::string& privateCreator) | |
967 { | |
968 DcmTag tag(t.GetGroup(), t.GetElement()); | |
969 | |
970 if (!privateCreator.empty()) | |
971 { | |
972 tag.setPrivateCreator(privateCreator.c_str()); | |
973 } | |
974 | |
975 return GetTagNameInternal(tag); | |
976 } | |
977 | |
978 | |
979 std::string FromDcmtkBridge::GetTagName(const DcmElement& element) | |
980 { | |
981 // Copy the tag to ensure const-correctness of DcmElement. Note | |
982 // that the private creator information is also copied. | |
983 DcmTag tag(element.getTag()); | |
984 | |
985 return GetTagNameInternal(tag); | |
986 } | |
987 | |
988 | |
989 | |
990 DicomTag FromDcmtkBridge::ParseTag(const char* name) | |
991 { | |
992 if (strlen(name) == 9 && | |
993 isxdigit(name[0]) && | |
994 isxdigit(name[1]) && | |
995 isxdigit(name[2]) && | |
996 isxdigit(name[3]) && | |
997 (name[4] == '-' || name[4] == ',') && | |
998 isxdigit(name[5]) && | |
999 isxdigit(name[6]) && | |
1000 isxdigit(name[7]) && | |
1001 isxdigit(name[8])) | |
1002 { | |
1003 uint16_t group = GetTagValue(name); | |
1004 uint16_t element = GetTagValue(name + 5); | |
1005 return DicomTag(group, element); | |
1006 } | |
1007 | |
1008 if (strlen(name) == 8 && | |
1009 isxdigit(name[0]) && | |
1010 isxdigit(name[1]) && | |
1011 isxdigit(name[2]) && | |
1012 isxdigit(name[3]) && | |
1013 isxdigit(name[4]) && | |
1014 isxdigit(name[5]) && | |
1015 isxdigit(name[6]) && | |
1016 isxdigit(name[7])) | |
1017 { | |
1018 uint16_t group = GetTagValue(name); | |
1019 uint16_t element = GetTagValue(name + 4); | |
1020 return DicomTag(group, element); | |
1021 } | |
1022 | |
1023 #if 0 | |
1024 const DcmDataDictionary& dict = dcmDataDict.rdlock(); | |
1025 const DcmDictEntry* entry = dict.findEntry(name); | |
1026 | |
1027 if (entry == NULL) | |
1028 { | |
1029 dcmDataDict.unlock(); | |
1030 throw OrthancException(ErrorCode_UnknownDicomTag); | |
1031 } | |
1032 else | |
1033 { | |
1034 DcmTagKey key = entry->getKey(); | |
1035 DicomTag tag(key.getGroup(), key.getElement()); | |
1036 dcmDataDict.unlock(); | |
1037 return tag; | |
1038 } | |
1039 #else | |
1040 DcmTag tag; | |
1041 if (DcmTag::findTagFromName(name, tag).good()) | |
1042 { | |
1043 return DicomTag(tag.getGTag(), tag.getETag()); | |
1044 } | |
1045 else | |
1046 { | |
1047 throw OrthancException(ErrorCode_UnknownDicomTag); | |
1048 } | |
1049 #endif | |
1050 } | |
1051 | |
1052 | |
1053 bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) | |
1054 { | |
1055 DcmTag tmp(tag.GetGroup(), tag.GetElement()); | |
1056 return tmp.isUnknownVR(); | |
1057 } | |
1058 | |
1059 | |
1060 void FromDcmtkBridge::ToJson(Json::Value& result, | |
1061 const DicomMap& values, | |
1062 bool simplify) | |
1063 { | |
1064 if (result.type() != Json::objectValue) | |
1065 { | |
1066 throw OrthancException(ErrorCode_BadParameterType); | |
1067 } | |
1068 | |
1069 result.clear(); | |
1070 | |
1071 for (DicomMap::Map::const_iterator | |
1072 it = values.map_.begin(); it != values.map_.end(); ++it) | |
1073 { | |
1074 // TODO Inject PrivateCreator if some is available in the DicomMap? | |
1075 const std::string tagName = GetTagName(it->first, ""); | |
1076 | |
1077 if (simplify) | |
1078 { | |
1079 if (it->second->IsNull()) | |
1080 { | |
1081 result[tagName] = Json::nullValue; | |
1082 } | |
1083 else | |
1084 { | |
1085 // TODO IsBinary | |
1086 result[tagName] = it->second->GetContent(); | |
1087 } | |
1088 } | |
1089 else | |
1090 { | |
1091 Json::Value value = Json::objectValue; | |
1092 | |
1093 value["Name"] = tagName; | |
1094 | |
1095 if (it->second->IsNull()) | |
1096 { | |
1097 value["Type"] = "Null"; | |
1098 value["Value"] = Json::nullValue; | |
1099 } | |
1100 else | |
1101 { | |
1102 // TODO IsBinary | |
1103 value["Type"] = "String"; | |
1104 value["Value"] = it->second->GetContent(); | |
1105 } | |
1106 | |
1107 result[it->first.Format()] = value; | |
1108 } | |
1109 } | |
1110 } | |
1111 | |
1112 | |
1113 std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) | |
1114 { | |
1115 char uid[100]; | |
1116 | |
1117 switch (level) | |
1118 { | |
1119 case ResourceType_Patient: | |
1120 // The "PatientID" field is of type LO (Long String), 64 | |
1121 // Bytes Maximum. An UUID is of length 36, thus it can be used | |
1122 // as a random PatientID. | |
1123 return SystemToolbox::GenerateUuid(); | |
1124 | |
1125 case ResourceType_Instance: | |
1126 return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); | |
1127 | |
1128 case ResourceType_Series: | |
1129 return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); | |
1130 | |
1131 case ResourceType_Study: | |
1132 return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); | |
1133 | |
1134 default: | |
1135 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1136 } | |
1137 } | |
1138 | |
1139 bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, | |
1140 DcmDataset& dataSet) | |
1141 { | |
1142 // Determine the transfer syntax which shall be used to write the | |
1143 // information to the file. We always switch to the Little Endian | |
1144 // syntax, with explicit length. | |
1145 | |
1146 // http://support.dcmtk.org/docs/dcxfer_8h-source.html | |
1147 | |
1148 | |
1149 /** | |
1150 * Note that up to Orthanc 0.7.1 (inclusive), the | |
1151 * "EXS_LittleEndianExplicit" was always used to save the DICOM | |
1152 * dataset into memory. We now keep the original transfer syntax | |
1153 * (if available). | |
1154 **/ | |
1155 E_TransferSyntax xfer = dataSet.getOriginalXfer(); | |
1156 if (xfer == EXS_Unknown) | |
1157 { | |
1158 // No information about the original transfer syntax: This is | |
1159 // most probably a DICOM dataset that was read from memory. | |
1160 xfer = EXS_LittleEndianExplicit; | |
1161 } | |
1162 | |
1163 E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; | |
1164 | |
1165 // Create the meta-header information | |
1166 DcmFileFormat ff(&dataSet); | |
1167 ff.validateMetaInfo(xfer); | |
1168 ff.removeInvalidGroups(); | |
1169 | |
1170 // Create a memory buffer with the proper size | |
1171 { | |
1172 const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) | |
1173 buffer.resize(estimatedSize); | |
1174 } | |
1175 | |
1176 DcmOutputBufferStream ob(&buffer[0], buffer.size()); | |
1177 | |
1178 // Fill the memory buffer with the meta-header and the dataset | |
1179 ff.transferInit(); | |
1180 OFCondition c = ff.write(ob, xfer, encodingType, NULL, | |
1181 /*opt_groupLength*/ EGL_recalcGL, | |
1182 /*opt_paddingType*/ EPD_withoutPadding); | |
1183 ff.transferEnd(); | |
1184 | |
1185 if (c.good()) | |
1186 { | |
1187 // The DICOM file is successfully written, truncate the target | |
1188 // buffer if its size was overestimated by (*) | |
1189 ob.flush(); | |
1190 | |
1191 size_t effectiveSize = static_cast<size_t>(ob.tell()); | |
1192 if (effectiveSize < buffer.size()) | |
1193 { | |
1194 buffer.resize(effectiveSize); | |
1195 } | |
1196 | |
1197 return true; | |
1198 } | |
1199 else | |
1200 { | |
1201 // Error | |
1202 buffer.clear(); | |
1203 return false; | |
1204 } | |
1205 } | |
1206 | |
1207 | |
1208 ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) | |
1209 { | |
1210 DcmTag t(tag.GetGroup(), tag.GetElement()); | |
1211 return Convert(t.getEVR()); | |
1212 } | |
1213 | |
1214 ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr) | |
1215 { | |
1216 switch (vr) | |
1217 { | |
1218 case EVR_AE: | |
1219 return ValueRepresentation_ApplicationEntity; | |
1220 | |
1221 case EVR_AS: | |
1222 return ValueRepresentation_AgeString; | |
1223 | |
1224 case EVR_AT: | |
1225 return ValueRepresentation_AttributeTag; | |
1226 | |
1227 case EVR_CS: | |
1228 return ValueRepresentation_CodeString; | |
1229 | |
1230 case EVR_DA: | |
1231 return ValueRepresentation_Date; | |
1232 | |
1233 case EVR_DS: | |
1234 return ValueRepresentation_DecimalString; | |
1235 | |
1236 case EVR_DT: | |
1237 return ValueRepresentation_DateTime; | |
1238 | |
1239 case EVR_FL: | |
1240 return ValueRepresentation_FloatingPointSingle; | |
1241 | |
1242 case EVR_FD: | |
1243 return ValueRepresentation_FloatingPointDouble; | |
1244 | |
1245 case EVR_IS: | |
1246 return ValueRepresentation_IntegerString; | |
1247 | |
1248 case EVR_LO: | |
1249 return ValueRepresentation_LongString; | |
1250 | |
1251 case EVR_LT: | |
1252 return ValueRepresentation_LongText; | |
1253 | |
1254 case EVR_OB: | |
1255 return ValueRepresentation_OtherByte; | |
1256 | |
1257 // Not supported as of DCMTK 3.6.0 | |
1258 /*case EVR_OD: | |
1259 return ValueRepresentation_OtherDouble;*/ | |
1260 | |
1261 case EVR_OF: | |
1262 return ValueRepresentation_OtherFloat; | |
1263 | |
1264 // Not supported as of DCMTK 3.6.0 | |
1265 /*case EVR_OL: | |
1266 return ValueRepresentation_OtherLong;*/ | |
1267 | |
1268 case EVR_OW: | |
1269 return ValueRepresentation_OtherWord; | |
1270 | |
1271 case EVR_PN: | |
1272 return ValueRepresentation_PersonName; | |
1273 | |
1274 case EVR_SH: | |
1275 return ValueRepresentation_ShortString; | |
1276 | |
1277 case EVR_SL: | |
1278 return ValueRepresentation_SignedLong; | |
1279 | |
1280 case EVR_SQ: | |
1281 return ValueRepresentation_Sequence; | |
1282 | |
1283 case EVR_SS: | |
1284 return ValueRepresentation_SignedShort; | |
1285 | |
1286 case EVR_ST: | |
1287 return ValueRepresentation_ShortText; | |
1288 | |
1289 case EVR_TM: | |
1290 return ValueRepresentation_Time; | |
1291 | |
1292 // Not supported as of DCMTK 3.6.0 | |
1293 /*case EVR_UC: | |
1294 return ValueRepresentation_UnlimitedCharacters;*/ | |
1295 | |
1296 case EVR_UI: | |
1297 return ValueRepresentation_UniqueIdentifier; | |
1298 | |
1299 case EVR_UL: | |
1300 return ValueRepresentation_UnsignedLong; | |
1301 | |
1302 case EVR_UN: | |
1303 return ValueRepresentation_Unknown; | |
1304 | |
1305 // Not supported as of DCMTK 3.6.0 | |
1306 /*case EVR_UR: | |
1307 return ValueRepresentation_UniversalResource;*/ | |
1308 | |
1309 case EVR_US: | |
1310 return ValueRepresentation_UnsignedShort; | |
1311 | |
1312 case EVR_UT: | |
1313 return ValueRepresentation_UnlimitedText; | |
1314 | |
1315 default: | |
1316 return ValueRepresentation_NotSupported; | |
1317 } | |
1318 } | |
1319 | |
1320 | |
1321 static bool IsBinaryTag(const DcmTag& key) | |
1322 { | |
1323 return (key.isUnknownVR() || | |
1324 key.getEVR() == EVR_OB || | |
1325 key.getEVR() == EVR_OF || | |
1326 key.getEVR() == EVR_OW || | |
1327 key.getEVR() == EVR_UN || | |
1328 key.getEVR() == EVR_ox); | |
1329 } | |
1330 | |
1331 | |
1332 DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) | |
1333 { | |
1334 DcmTag key(tag.GetGroup(), tag.GetElement()); | |
1335 | |
1336 if (tag.IsPrivate() || | |
1337 IsBinaryTag(key)) | |
1338 { | |
1339 return new DcmOtherByteOtherWord(key); | |
1340 } | |
1341 | |
1342 switch (key.getEVR()) | |
1343 { | |
1344 // http://support.dcmtk.org/docs/dcvr_8h-source.html | |
1345 | |
1346 /** | |
1347 * Binary types, handled above | |
1348 **/ | |
1349 | |
1350 case EVR_OB: // other byte | |
1351 case EVR_OF: // other float | |
1352 case EVR_OW: // other word | |
1353 case EVR_UN: // unknown value representation | |
1354 case EVR_ox: // OB or OW depending on context | |
1355 throw OrthancException(ErrorCode_InternalError); | |
1356 | |
1357 | |
1358 /** | |
1359 * String types. | |
1360 * http://support.dcmtk.org/docs/classDcmByteString.html | |
1361 **/ | |
1362 | |
1363 case EVR_AS: // age string | |
1364 return new DcmAgeString(key); | |
1365 | |
1366 case EVR_AE: // application entity title | |
1367 return new DcmApplicationEntity(key); | |
1368 | |
1369 case EVR_CS: // code string | |
1370 return new DcmCodeString(key); | |
1371 | |
1372 case EVR_DA: // date string | |
1373 return new DcmDate(key); | |
1374 | |
1375 case EVR_DT: // date time string | |
1376 return new DcmDateTime(key); | |
1377 | |
1378 case EVR_DS: // decimal string | |
1379 return new DcmDecimalString(key); | |
1380 | |
1381 case EVR_IS: // integer string | |
1382 return new DcmIntegerString(key); | |
1383 | |
1384 case EVR_TM: // time string | |
1385 return new DcmTime(key); | |
1386 | |
1387 case EVR_UI: // unique identifier | |
1388 return new DcmUniqueIdentifier(key); | |
1389 | |
1390 case EVR_ST: // short text | |
1391 return new DcmShortText(key); | |
1392 | |
1393 case EVR_LO: // long string | |
1394 return new DcmLongString(key); | |
1395 | |
1396 case EVR_LT: // long text | |
1397 return new DcmLongText(key); | |
1398 | |
1399 case EVR_UT: // unlimited text | |
1400 return new DcmUnlimitedText(key); | |
1401 | |
1402 case EVR_SH: // short string | |
1403 return new DcmShortString(key); | |
1404 | |
1405 case EVR_PN: // person name | |
1406 return new DcmPersonName(key); | |
1407 | |
1408 | |
1409 /** | |
1410 * Numerical types | |
1411 **/ | |
1412 | |
1413 case EVR_SL: // signed long | |
1414 return new DcmSignedLong(key); | |
1415 | |
1416 case EVR_SS: // signed short | |
1417 return new DcmSignedShort(key); | |
1418 | |
1419 case EVR_UL: // unsigned long | |
1420 return new DcmUnsignedLong(key); | |
1421 | |
1422 case EVR_US: // unsigned short | |
1423 return new DcmUnsignedShort(key); | |
1424 | |
1425 case EVR_FL: // float single-precision | |
1426 return new DcmFloatingPointSingle(key); | |
1427 | |
1428 case EVR_FD: // float double-precision | |
1429 return new DcmFloatingPointDouble(key); | |
1430 | |
1431 | |
1432 /** | |
1433 * Sequence types, should never occur at this point. | |
1434 **/ | |
1435 | |
1436 case EVR_SQ: // sequence of items | |
1437 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1438 | |
1439 | |
1440 /** | |
1441 * TODO | |
1442 **/ | |
1443 | |
1444 case EVR_AT: // attribute tag | |
1445 throw OrthancException(ErrorCode_NotImplemented); | |
1446 | |
1447 | |
1448 /** | |
1449 * Internal to DCMTK. | |
1450 **/ | |
1451 | |
1452 case EVR_xs: // SS or US depending on context | |
1453 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) | |
1454 case EVR_na: // na="not applicable", for data which has no VR | |
1455 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor | |
1456 case EVR_item: // used internally for items | |
1457 case EVR_metainfo: // used internally for meta info datasets | |
1458 case EVR_dataset: // used internally for datasets | |
1459 case EVR_fileFormat: // used internally for DICOM files | |
1460 case EVR_dicomDir: // used internally for DICOMDIR objects | |
1461 case EVR_dirRecord: // used internally for DICOMDIR records | |
1462 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image | |
1463 case EVR_pixelItem: // used internally for pixel items in a compressed image | |
1464 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) | |
1465 case EVR_PixelData: // used internally for uncompressed pixeld data | |
1466 case EVR_OverlayData: // used internally for overlay data | |
1467 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR | |
1468 default: | |
1469 break; | |
1470 } | |
1471 | |
1472 throw OrthancException(ErrorCode_InternalError); | |
1473 } | |
1474 | |
1475 | |
1476 | |
1477 void FromDcmtkBridge::FillElementWithString(DcmElement& element, | |
1478 const DicomTag& tag, | |
1479 const std::string& utf8Value, | |
1480 bool decodeDataUriScheme, | |
1481 Encoding dicomEncoding) | |
1482 { | |
1483 std::string binary; | |
1484 const std::string* decoded = &utf8Value; | |
1485 | |
1486 if (decodeDataUriScheme && | |
1487 boost::starts_with(utf8Value, "data:application/octet-stream;base64,")) | |
1488 { | |
1489 std::string mime; | |
1490 if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) | |
1491 { | |
1492 throw OrthancException(ErrorCode_BadFileFormat); | |
1493 } | |
1494 | |
1495 decoded = &binary; | |
1496 } | |
1497 else if (dicomEncoding != Encoding_Utf8) | |
1498 { | |
1499 binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); | |
1500 decoded = &binary; | |
1501 } | |
1502 | |
1503 DcmTag key(tag.GetGroup(), tag.GetElement()); | |
1504 | |
1505 if (tag.IsPrivate() || | |
1506 IsBinaryTag(key)) | |
1507 { | |
1508 if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) | |
1509 { | |
1510 return; | |
1511 } | |
1512 else | |
1513 { | |
1514 throw OrthancException(ErrorCode_InternalError); | |
1515 } | |
1516 } | |
1517 | |
1518 bool ok = false; | |
1519 | |
1520 try | |
1521 { | |
1522 switch (key.getEVR()) | |
1523 { | |
1524 // http://support.dcmtk.org/docs/dcvr_8h-source.html | |
1525 | |
1526 /** | |
1527 * TODO. | |
1528 **/ | |
1529 | |
1530 case EVR_OB: // other byte | |
1531 case EVR_OF: // other float | |
1532 case EVR_OW: // other word | |
1533 case EVR_AT: // attribute tag | |
1534 throw OrthancException(ErrorCode_NotImplemented); | |
1535 | |
1536 case EVR_UN: // unknown value representation | |
1537 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1538 | |
1539 | |
1540 /** | |
1541 * String types. | |
1542 **/ | |
1543 | |
1544 case EVR_DS: // decimal string | |
1545 case EVR_IS: // integer string | |
1546 case EVR_AS: // age string | |
1547 case EVR_DA: // date string | |
1548 case EVR_DT: // date time string | |
1549 case EVR_TM: // time string | |
1550 case EVR_AE: // application entity title | |
1551 case EVR_CS: // code string | |
1552 case EVR_SH: // short string | |
1553 case EVR_LO: // long string | |
1554 case EVR_ST: // short text | |
1555 case EVR_LT: // long text | |
1556 case EVR_UT: // unlimited text | |
1557 case EVR_PN: // person name | |
1558 case EVR_UI: // unique identifier | |
1559 { | |
1560 ok = element.putString(decoded->c_str()).good(); | |
1561 break; | |
1562 } | |
1563 | |
1564 | |
1565 /** | |
1566 * Numerical types | |
1567 **/ | |
1568 | |
1569 case EVR_SL: // signed long | |
1570 { | |
1571 ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good(); | |
1572 break; | |
1573 } | |
1574 | |
1575 case EVR_SS: // signed short | |
1576 { | |
1577 ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good(); | |
1578 break; | |
1579 } | |
1580 | |
1581 case EVR_UL: // unsigned long | |
1582 { | |
1583 ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good(); | |
1584 break; | |
1585 } | |
1586 | |
1587 case EVR_US: // unsigned short | |
1588 { | |
1589 ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good(); | |
1590 break; | |
1591 } | |
1592 | |
1593 case EVR_FL: // float single-precision | |
1594 { | |
1595 ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good(); | |
1596 break; | |
1597 } | |
1598 | |
1599 case EVR_FD: // float double-precision | |
1600 { | |
1601 ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good(); | |
1602 break; | |
1603 } | |
1604 | |
1605 | |
1606 /** | |
1607 * Sequence types, should never occur at this point. | |
1608 **/ | |
1609 | |
1610 case EVR_SQ: // sequence of items | |
1611 { | |
1612 ok = false; | |
1613 break; | |
1614 } | |
1615 | |
1616 | |
1617 /** | |
1618 * Internal to DCMTK. | |
1619 **/ | |
1620 | |
1621 case EVR_ox: // OB or OW depending on context | |
1622 case EVR_xs: // SS or US depending on context | |
1623 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) | |
1624 case EVR_na: // na="not applicable", for data which has no VR | |
1625 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor | |
1626 case EVR_item: // used internally for items | |
1627 case EVR_metainfo: // used internally for meta info datasets | |
1628 case EVR_dataset: // used internally for datasets | |
1629 case EVR_fileFormat: // used internally for DICOM files | |
1630 case EVR_dicomDir: // used internally for DICOMDIR objects | |
1631 case EVR_dirRecord: // used internally for DICOMDIR records | |
1632 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image | |
1633 case EVR_pixelItem: // used internally for pixel items in a compressed image | |
1634 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) | |
1635 case EVR_PixelData: // used internally for uncompressed pixeld data | |
1636 case EVR_OverlayData: // used internally for overlay data | |
1637 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR | |
1638 default: | |
1639 break; | |
1640 } | |
1641 } | |
1642 catch (boost::bad_lexical_cast&) | |
1643 { | |
1644 ok = false; | |
1645 } | |
1646 | |
1647 if (!ok) | |
1648 { | |
1649 throw OrthancException(ErrorCode_InternalError); | |
1650 } | |
1651 } | |
1652 | |
1653 | |
1654 DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, | |
1655 const Json::Value& value, | |
1656 bool decodeDataUriScheme, | |
1657 Encoding dicomEncoding) | |
1658 { | |
1659 std::auto_ptr<DcmElement> element; | |
1660 | |
1661 switch (value.type()) | |
1662 { | |
1663 case Json::stringValue: | |
1664 element.reset(CreateElementForTag(tag)); | |
1665 FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding); | |
1666 break; | |
1667 | |
1668 case Json::arrayValue: | |
1669 { | |
1670 DcmTag key(tag.GetGroup(), tag.GetElement()); | |
1671 if (key.getEVR() != EVR_SQ) | |
1672 { | |
1673 throw OrthancException(ErrorCode_BadParameterType); | |
1674 } | |
1675 | |
1676 DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key, value.size()); | |
1677 element.reset(sequence); | |
1678 | |
1679 for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) | |
1680 { | |
1681 std::auto_ptr<DcmItem> item(new DcmItem); | |
1682 | |
1683 Json::Value::Members members = value[i].getMemberNames(); | |
1684 for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) | |
1685 { | |
1686 item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding)); | |
1687 } | |
1688 | |
1689 sequence->append(item.release()); | |
1690 } | |
1691 | |
1692 break; | |
1693 } | |
1694 | |
1695 default: | |
1696 throw OrthancException(ErrorCode_BadParameterType); | |
1697 } | |
1698 | |
1699 return element.release(); | |
1700 } | |
1701 | |
1702 | |
1703 DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset) | |
1704 { | |
1705 DcmElement *element = NULL; | |
1706 if (!dataset.findAndGetElement(DCM_PixelData, element).good()) | |
1707 { | |
1708 throw OrthancException(ErrorCode_BadFileFormat); | |
1709 } | |
1710 | |
1711 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); | |
1712 DcmPixelSequence* pixelSequence = NULL; | |
1713 if (!pixelData.getEncapsulatedRepresentation | |
1714 (dataset.getOriginalXfer(), NULL, pixelSequence).good()) | |
1715 { | |
1716 return NULL; | |
1717 } | |
1718 else | |
1719 { | |
1720 return pixelSequence; | |
1721 } | |
1722 } | |
1723 | |
1724 | |
1725 Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json, | |
1726 Encoding defaultEncoding) | |
1727 { | |
1728 if (json.type() != Json::objectValue) | |
1729 { | |
1730 throw OrthancException(ErrorCode_BadParameterType); | |
1731 } | |
1732 | |
1733 Encoding encoding = defaultEncoding; | |
1734 | |
1735 const Json::Value::Members tags = json.getMemberNames(); | |
1736 | |
1737 // Look for SpecificCharacterSet (0008,0005) in the JSON file | |
1738 for (size_t i = 0; i < tags.size(); i++) | |
1739 { | |
1740 DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); | |
1741 if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) | |
1742 { | |
1743 const Json::Value& value = json[tags[i]]; | |
1744 if (value.type() != Json::stringValue || | |
1745 !GetDicomEncoding(encoding, value.asCString())) | |
1746 { | |
1747 LOG(ERROR) << "Unknown encoding while creating DICOM from JSON: " << value; | |
1748 throw OrthancException(ErrorCode_BadRequest); | |
1749 } | |
1750 } | |
1751 } | |
1752 | |
1753 return encoding; | |
1754 } | |
1755 | |
1756 | |
1757 static void SetString(DcmDataset& target, | |
1758 const DcmTag& tag, | |
1759 const std::string& value) | |
1760 { | |
1761 if (!target.putAndInsertString(tag, value.c_str()).good()) | |
1762 { | |
1763 throw OrthancException(ErrorCode_InternalError); | |
1764 } | |
1765 } | |
1766 | |
1767 | |
1768 DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 | |
1769 bool generateIdentifiers, | |
1770 bool decodeDataUriScheme, | |
1771 Encoding defaultEncoding) | |
1772 { | |
1773 std::auto_ptr<DcmDataset> result(new DcmDataset); | |
1774 Encoding encoding = ExtractEncoding(json, defaultEncoding); | |
1775 | |
1776 SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); | |
1777 | |
1778 const Json::Value::Members tags = json.getMemberNames(); | |
1779 | |
1780 bool hasPatientId = false; | |
1781 bool hasStudyInstanceUid = false; | |
1782 bool hasSeriesInstanceUid = false; | |
1783 bool hasSopInstanceUid = false; | |
1784 | |
1785 for (size_t i = 0; i < tags.size(); i++) | |
1786 { | |
1787 DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); | |
1788 const Json::Value& value = json[tags[i]]; | |
1789 | |
1790 if (tag == DICOM_TAG_PATIENT_ID) | |
1791 { | |
1792 hasPatientId = true; | |
1793 } | |
1794 else if (tag == DICOM_TAG_STUDY_INSTANCE_UID) | |
1795 { | |
1796 hasStudyInstanceUid = true; | |
1797 } | |
1798 else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) | |
1799 { | |
1800 hasSeriesInstanceUid = true; | |
1801 } | |
1802 else if (tag == DICOM_TAG_SOP_INSTANCE_UID) | |
1803 { | |
1804 hasSopInstanceUid = true; | |
1805 } | |
1806 | |
1807 if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) | |
1808 { | |
1809 std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); | |
1810 const DcmTagKey& tag = element->getTag(); | |
1811 | |
1812 result->findAndDeleteElement(tag); | |
1813 | |
1814 DcmElement* tmp = element.release(); | |
1815 if (!result->insert(tmp, false, false).good()) | |
1816 { | |
1817 delete tmp; | |
1818 throw OrthancException(ErrorCode_InternalError); | |
1819 } | |
1820 } | |
1821 } | |
1822 | |
1823 if (!hasPatientId && | |
1824 generateIdentifiers) | |
1825 { | |
1826 SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient)); | |
1827 } | |
1828 | |
1829 if (!hasStudyInstanceUid && | |
1830 generateIdentifiers) | |
1831 { | |
1832 SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study)); | |
1833 } | |
1834 | |
1835 if (!hasSeriesInstanceUid && | |
1836 generateIdentifiers) | |
1837 { | |
1838 SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series)); | |
1839 } | |
1840 | |
1841 if (!hasSopInstanceUid && | |
1842 generateIdentifiers) | |
1843 { | |
1844 SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance)); | |
1845 } | |
1846 | |
1847 return result.release(); | |
1848 } | |
1849 | |
1850 | |
1851 DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer, | |
1852 size_t size) | |
1853 { | |
1854 DcmInputBufferStream is; | |
1855 if (size > 0) | |
1856 { | |
1857 is.setBuffer(buffer, size); | |
1858 } | |
1859 is.setEos(); | |
1860 | |
1861 std::auto_ptr<DcmFileFormat> result(new DcmFileFormat); | |
1862 | |
1863 result->transferInit(); | |
1864 if (!result->read(is).good()) | |
1865 { | |
1866 throw OrthancException(ErrorCode_BadFileFormat); | |
1867 } | |
1868 | |
1869 result->loadAllDataIntoMemory(); | |
1870 result->transferEnd(); | |
1871 | |
1872 return result.release(); | |
1873 } | |
1874 | |
1875 | |
1876 void FromDcmtkBridge::FromJson(DicomMap& target, | |
1877 const Json::Value& source) | |
1878 { | |
1879 if (source.type() != Json::objectValue) | |
1880 { | |
1881 throw OrthancException(ErrorCode_BadFileFormat); | |
1882 } | |
1883 | |
1884 target.Clear(); | |
1885 | |
1886 Json::Value::Members members = source.getMemberNames(); | |
1887 | |
1888 for (size_t i = 0; i < members.size(); i++) | |
1889 { | |
1890 const Json::Value& value = source[members[i]]; | |
1891 | |
1892 if (value.type() != Json::stringValue) | |
1893 { | |
1894 throw OrthancException(ErrorCode_BadFileFormat); | |
1895 } | |
1896 | |
1897 target.SetValue(ParseTag(members[i]), value.asString(), false); | |
1898 } | |
1899 } | |
1900 } |