comparison OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/DicomParsing/FromDcmtkBridge.cpp@058b5ade8acd
children c02a2d9efbc2
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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 General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../PrecompiledHeaders.h"
35
36 #ifndef NOMINMAX
37 #define NOMINMAX
38 #endif
39
40 #if !defined(ORTHANC_SANDBOXED)
41 # error The macro ORTHANC_SANDBOXED must be defined
42 #endif
43
44 #if !defined(DCMTK_VERSION_NUMBER)
45 # error The macro DCMTK_VERSION_NUMBER must be defined
46 #endif
47
48 #include "FromDcmtkBridge.h"
49 #include "ToDcmtkBridge.h"
50 #include "../Compatibility.h"
51 #include "../Logging.h"
52 #include "../Toolbox.h"
53 #include "../OrthancException.h"
54
55 #if ORTHANC_SANDBOXED == 0
56 # include "../TemporaryFile.h"
57 #endif
58
59 #include <list>
60 #include <limits>
61
62 #include <boost/lexical_cast.hpp>
63 #include <boost/filesystem.hpp>
64 #include <boost/algorithm/string/predicate.hpp>
65 #include <boost/algorithm/string/join.hpp>
66
67 #include <dcmtk/dcmdata/dcdeftag.h>
68 #include <dcmtk/dcmdata/dcdicent.h>
69 #include <dcmtk/dcmdata/dcdict.h>
70 #include <dcmtk/dcmdata/dcfilefo.h>
71 #include <dcmtk/dcmdata/dcistrmb.h>
72 #include <dcmtk/dcmdata/dcostrmb.h>
73 #include <dcmtk/dcmdata/dcpixel.h>
74 #include <dcmtk/dcmdata/dcuid.h>
75 #include <dcmtk/dcmdata/dcxfer.h>
76
77 #include <dcmtk/dcmdata/dcvrae.h>
78 #include <dcmtk/dcmdata/dcvras.h>
79 #include <dcmtk/dcmdata/dcvrat.h>
80 #include <dcmtk/dcmdata/dcvrcs.h>
81 #include <dcmtk/dcmdata/dcvrda.h>
82 #include <dcmtk/dcmdata/dcvrds.h>
83 #include <dcmtk/dcmdata/dcvrdt.h>
84 #include <dcmtk/dcmdata/dcvrfd.h>
85 #include <dcmtk/dcmdata/dcvrfl.h>
86 #include <dcmtk/dcmdata/dcvris.h>
87 #include <dcmtk/dcmdata/dcvrlo.h>
88 #include <dcmtk/dcmdata/dcvrlt.h>
89 #include <dcmtk/dcmdata/dcvrpn.h>
90 #include <dcmtk/dcmdata/dcvrsh.h>
91 #include <dcmtk/dcmdata/dcvrsl.h>
92 #include <dcmtk/dcmdata/dcvrss.h>
93 #include <dcmtk/dcmdata/dcvrst.h>
94 #include <dcmtk/dcmdata/dcvrtm.h>
95 #include <dcmtk/dcmdata/dcvrui.h>
96 #include <dcmtk/dcmdata/dcvrul.h>
97 #include <dcmtk/dcmdata/dcvrus.h>
98 #include <dcmtk/dcmdata/dcvrut.h>
99
100 #if DCMTK_VERSION_NUMBER >= 361
101 # include <dcmtk/dcmdata/dcvruc.h>
102 # include <dcmtk/dcmdata/dcvrur.h>
103 #endif
104
105 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
106 # include <OrthancFrameworkResources.h>
107 #endif
108
109 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
110 # include <dcmtk/dcmjpeg/djdecode.h>
111 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
112 # include <dcmtk/dcmjpeg/djencode.h>
113 # endif
114 #endif
115
116 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
117 # include <dcmtk/dcmjpls/djdecode.h>
118 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
119 # include <dcmtk/dcmjpls/djencode.h>
120 # endif
121 #endif
122
123
124 #include <dcmtk/dcmdata/dcrledrg.h>
125 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
126 # include <dcmtk/dcmdata/dcrleerg.h>
127 # include <dcmtk/dcmimage/diregist.h> // include to support color images
128 #endif
129
130
131 namespace Orthanc
132 {
133 static bool IsBinaryTag(const DcmTag& key)
134 {
135 return (key.isUnknownVR() ||
136 key.getEVR() == EVR_OB ||
137 key.getEVR() == EVR_OW ||
138 key.getEVR() == EVR_UN ||
139 key.getEVR() == EVR_ox);
140 }
141
142
143 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
144 static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
145 FrameworkResources::FileResourceId resource)
146 {
147 std::string content;
148 FrameworkResources::GetFileResource(content, resource);
149
150 #if ORTHANC_SANDBOXED == 0
151 TemporaryFile tmp;
152 tmp.Write(content);
153
154 if (!dictionary.loadDictionary(tmp.GetPath().c_str()))
155 {
156 throw OrthancException(ErrorCode_InternalError,
157 "Cannot read embedded dictionary. Under Windows, make sure that "
158 "your TEMP directory does not contain special characters.");
159 }
160 #else
161 if (!dictionary.loadFromMemory(content))
162 {
163 throw OrthancException(ErrorCode_InternalError,
164 "Cannot read embedded dictionary. Under Windows, make sure that "
165 "your TEMP directory does not contain special characters.");
166 }
167 #endif
168 }
169 #endif
170
171
172 namespace
173 {
174 class DictionaryLocker : public boost::noncopyable
175 {
176 private:
177 DcmDataDictionary& dictionary_;
178
179 public:
180 DictionaryLocker() : dictionary_(dcmDataDict.wrlock())
181 {
182 }
183
184 ~DictionaryLocker()
185 {
186 #if DCMTK_VERSION_NUMBER >= 364
187 dcmDataDict.wrunlock();
188 #else
189 dcmDataDict.unlock();
190 #endif
191 }
192
193 DcmDataDictionary& operator*()
194 {
195 return dictionary_;
196 }
197
198 DcmDataDictionary* operator->()
199 {
200 return &dictionary_;
201 }
202 };
203
204
205 #define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \
206 \
207 struct converter \
208 { \
209 typedef cType CType; \
210 \
211 static bool Apply(CType& result, \
212 DcmElement& element, \
213 size_t i) \
214 { \
215 return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \
216 } \
217 };
218
219 DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32)
220 DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16)
221 DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32)
222 DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16)
223 DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32)
224 DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64)
225
226
227 template <typename F>
228 static DicomValue* ApplyDcmtkToCTypeConverter(DcmElement& element)
229 {
230 F f;
231 typename F::CType value;
232
233 if (element.getLength() > sizeof(typename F::CType)
234 && (element.getLength() % sizeof(typename F::CType)) == 0)
235 {
236 size_t count = element.getLength() / sizeof(typename F::CType);
237 std::vector<std::string> strings;
238 for (size_t i = 0; i < count; i++) {
239 if (f.Apply(value, element, i)) {
240 strings.push_back(boost::lexical_cast<std::string>(value));
241 }
242 }
243 return new DicomValue(boost::algorithm::join(strings, "\\"), false);
244 }
245 else if (f.Apply(value, element, 0)) {
246 return new DicomValue(boost::lexical_cast<std::string>(value), false);
247 }
248 else {
249 return new DicomValue;
250 }
251 }
252
253 }
254
255
256 void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary)
257 {
258 LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER;
259
260 {
261 DictionaryLocker locker;
262
263 locker->clear();
264
265 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
266 LOG(INFO) << "Loading the embedded dictionaries";
267 /**
268 * Do not load DICONDE dictionary, it breaks the other tags. The
269 * command "strace storescu 2>&1 |grep dic" shows that DICONDE
270 * dictionary is not loaded by storescu.
271 **/
272 //LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICONDE);
273
274 LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICOM);
275
276 if (loadPrivateDictionary)
277 {
278 LOG(INFO) << "Loading the embedded dictionary of private tags";
279 LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_PRIVATE);
280 }
281 else
282 {
283 LOG(INFO) << "The dictionary of private tags has not been loaded";
284 }
285
286 #else
287 std::vector<std::string> dictionaries;
288
289 const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
290 if (env != NULL)
291 {
292 // This mimics the behavior of DCMTK:
293 // https://support.dcmtk.org/docs/file_envvars.html
294 #if defined(_WIN32)
295 Toolbox::TokenizeString(dictionaries, std::string(env), ';');
296 #else
297 Toolbox::TokenizeString(dictionaries, std::string(env), ':');
298 #endif
299 }
300 else
301 {
302 boost::filesystem::path base = DCMTK_DICTIONARY_DIR;
303 dictionaries.push_back((base / "dicom.dic").string());
304 dictionaries.push_back((base / "private.dic").string());
305 }
306
307 for (size_t i = 0; i < dictionaries.size(); i++)
308 {
309 LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\"";
310
311 if (!locker->loadDictionary(dictionaries[i].c_str()))
312 {
313 throw OrthancException(ErrorCode_InexistentFile);
314 }
315 }
316
317 #endif
318 }
319
320 /* make sure data dictionary is loaded */
321 if (!dcmDataDict.isDictionaryLoaded())
322 {
323 throw OrthancException(ErrorCode_InternalError,
324 "No DICOM dictionary loaded, check environment variable: " +
325 std::string(DCM_DICT_ENVIRONMENT_VARIABLE));
326 }
327
328 {
329 // Test the dictionary with a simple DICOM tag
330 DcmTag key(0x0010, 0x1030); // This is PatientWeight
331 if (key.getEVR() != EVR_DS)
332 {
333 throw OrthancException(ErrorCode_InternalError,
334 "The DICOM dictionary has not been correctly read");
335 }
336 }
337 }
338
339
340 void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag,
341 ValueRepresentation vr,
342 const std::string& name,
343 unsigned int minMultiplicity,
344 unsigned int maxMultiplicity,
345 const std::string& privateCreator)
346 {
347 if (minMultiplicity < 1)
348 {
349 throw OrthancException(ErrorCode_ParameterOutOfRange);
350 }
351
352 bool arbitrary = false;
353 if (maxMultiplicity == 0)
354 {
355 maxMultiplicity = DcmVariableVM;
356 arbitrary = true;
357 }
358 else if (maxMultiplicity < minMultiplicity)
359 {
360 throw OrthancException(ErrorCode_ParameterOutOfRange);
361 }
362
363 DcmEVR evr = ToDcmtkBridge::Convert(vr);
364
365 LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " "
366 << name << " (multiplicity: " << minMultiplicity << "-"
367 << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
368
369 std::unique_ptr<DcmDictEntry> entry;
370 if (privateCreator.empty())
371 {
372 if (tag.GetGroup() % 2 == 1)
373 {
374 char buf[128];
375 sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), "
376 "but no private creator was associated with it",
377 tag.GetGroup(), tag.GetElement());
378 LOG(WARNING) << buf;
379 }
380
381 entry.reset(new DcmDictEntry(tag.GetGroup(),
382 tag.GetElement(),
383 evr, name.c_str(),
384 static_cast<int>(minMultiplicity),
385 static_cast<int>(maxMultiplicity),
386 NULL /* version */,
387 OFTrue /* doCopyString */,
388 NULL /* private creator */));
389 }
390 else
391 {
392 // "Private Data Elements have an odd Group Number that is not
393 // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or
394 // (FFFF,eeee)."
395 if (tag.GetGroup() % 2 == 0 /* even */ ||
396 tag.GetGroup() == 0x0001 ||
397 tag.GetGroup() == 0x0003 ||
398 tag.GetGroup() == 0x0005 ||
399 tag.GetGroup() == 0x0007 ||
400 tag.GetGroup() == 0xffff)
401 {
402 char buf[128];
403 sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009",
404 tag.GetGroup(), tag.GetElement());
405 throw OrthancException(ErrorCode_ParameterOutOfRange, std::string(buf));
406 }
407
408 entry.reset(new DcmDictEntry(tag.GetGroup(),
409 tag.GetElement(),
410 evr, name.c_str(),
411 static_cast<int>(minMultiplicity),
412 static_cast<int>(maxMultiplicity),
413 "private" /* version */,
414 OFTrue /* doCopyString */,
415 privateCreator.c_str()));
416 }
417
418 entry->setGroupRangeRestriction(DcmDictRange_Unspecified);
419 entry->setElementRangeRestriction(DcmDictRange_Unspecified);
420
421 {
422 DictionaryLocker locker;
423
424 if (locker->findEntry(name.c_str()))
425 {
426 throw OrthancException(ErrorCode_AlreadyExistingTag,
427 "Cannot register two tags with the same symbolic name \"" + name + "\"");
428 }
429
430 locker->addEntry(entry.release());
431 }
432 }
433
434
435 Encoding FromDcmtkBridge::DetectEncoding(bool& hasCodeExtensions,
436 DcmItem& dataset,
437 Encoding defaultEncoding)
438 {
439 // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2
440
441 OFString tmp;
442 if (dataset.findAndGetOFStringArray(DCM_SpecificCharacterSet, tmp).good())
443 {
444 std::vector<std::string> tokens;
445 Toolbox::TokenizeString(tokens, std::string(tmp.c_str()), '\\');
446
447 hasCodeExtensions = (tokens.size() > 1);
448
449 for (size_t i = 0; i < tokens.size(); i++)
450 {
451 std::string characterSet = Toolbox::StripSpaces(tokens[i]);
452
453 if (!characterSet.empty())
454 {
455 Encoding encoding;
456
457 if (GetDicomEncoding(encoding, characterSet.c_str()))
458 {
459 // The specific character set is supported by the Orthanc core
460 return encoding;
461 }
462 else
463 {
464 LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet
465 << ", fallback to ASCII (remove all special characters)";
466 return Encoding_Ascii;
467 }
468 }
469 }
470 }
471 else
472 {
473 hasCodeExtensions = false;
474 }
475
476 // No specific character set tag: Use the default encoding
477 return defaultEncoding;
478 }
479
480
481 void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target,
482 DcmItem& dataset,
483 unsigned int maxStringLength,
484 Encoding defaultEncoding,
485 const std::set<DicomTag>& ignoreTagLength)
486 {
487 bool hasCodeExtensions;
488 Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
489
490 target.Clear();
491 for (unsigned long i = 0; i < dataset.card(); i++)
492 {
493 DcmElement* element = dataset.getElement(i);
494 if (element && element->isLeaf())
495 {
496 target.SetValueInternal(element->getTag().getGTag(),
497 element->getTag().getETag(),
498 ConvertLeafElement(*element, DicomToJsonFlags_Default,
499 maxStringLength, encoding, hasCodeExtensions, ignoreTagLength));
500 }
501 }
502 }
503
504
505 DicomTag FromDcmtkBridge::Convert(const DcmTag& tag)
506 {
507 return DicomTag(tag.getGTag(), tag.getETag());
508 }
509
510
511 DicomTag FromDcmtkBridge::GetTag(const DcmElement& element)
512 {
513 return DicomTag(element.getGTag(), element.getETag());
514 }
515
516
517 DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
518 DicomToJsonFlags flags,
519 unsigned int maxStringLength,
520 Encoding encoding,
521 bool hasCodeExtensions,
522 const std::set<DicomTag>& ignoreTagLength)
523 {
524 if (!element.isLeaf())
525 {
526 // This function is only applicable to leaf elements
527 throw OrthancException(ErrorCode_BadParameterType);
528 }
529
530 char *c = NULL;
531 if (element.isaString() &&
532 element.getString(c).good())
533 {
534 if (c == NULL) // This case corresponds to the empty string
535 {
536 return new DicomValue("", false);
537 }
538 else
539 {
540 std::string s(c);
541 std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
542
543 if (maxStringLength != 0 &&
544 utf8.size() > maxStringLength &&
545 ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
546 {
547 return new DicomValue; // Too long, create a NULL value
548 }
549 else
550 {
551 return new DicomValue(utf8, false);
552 }
553 }
554 }
555
556
557 if (element.getVR() == EVR_UN)
558 {
559 // Unknown value representation: Lookup in the dictionary. This
560 // is notably the case for private tags registered with the
561 // "Dictionary" configuration option.
562 DictionaryLocker locker;
563
564 const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(),
565 element.getTag().getPrivateCreator());
566 if (entry != NULL &&
567 entry->getVR().isaString())
568 {
569 Uint8* data = NULL;
570
571 // At (*), we do not try and convert to UTF-8, as nothing says
572 // the encoding of the private tag is the same as that of the
573 // remaining of the DICOM dataset. Only go for ASCII strings.
574
575 if (element.getUint8Array(data) == EC_Normal &&
576 Toolbox::IsAsciiString(data, element.getLength())) // (*)
577 {
578 if (data == NULL)
579 {
580 return new DicomValue("", false); // Empty string
581 }
582 else if (maxStringLength != 0 &&
583 element.getLength() > maxStringLength &&
584 ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
585 {
586 return new DicomValue; // Too long, create a NULL value
587 }
588 else
589 {
590 std::string s(reinterpret_cast<const char*>(data), element.getLength());
591 return new DicomValue(s, false);
592 }
593 }
594 }
595 }
596
597
598 try
599 {
600 // http://support.dcmtk.org/docs/dcvr_8h-source.html
601 switch (element.getVR())
602 {
603
604 /**
605 * Deal with binary data (including PixelData).
606 **/
607
608 case EVR_OB: // other byte
609 case EVR_OF: // other float
610 case EVR_OW: // other word
611 case EVR_UN: // unknown value representation
612 case EVR_ox: // OB or OW depending on context
613 case EVR_DS: // decimal string
614 case EVR_IS: // integer string
615 case EVR_AS: // age string
616 case EVR_DA: // date string
617 case EVR_DT: // date time string
618 case EVR_TM: // time string
619 case EVR_AE: // application entity title
620 case EVR_CS: // code string
621 case EVR_SH: // short string
622 case EVR_LO: // long string
623 case EVR_ST: // short text
624 case EVR_LT: // long text
625 case EVR_UT: // unlimited text
626 case EVR_PN: // person name
627 case EVR_UI: // unique identifier
628 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
629 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
630 {
631 if (!(flags & DicomToJsonFlags_ConvertBinaryToNull))
632 {
633 Uint8* data = NULL;
634 if (element.getUint8Array(data) == EC_Normal)
635 {
636 return new DicomValue(reinterpret_cast<const char*>(data), element.getLength(), true);
637 }
638 }
639
640 return new DicomValue;
641 }
642
643 /**
644 * Numeric types
645 **/
646
647 case EVR_SL: // signed long
648 {
649 return ApplyDcmtkToCTypeConverter<DcmtkToSint32Converter>(element);
650 }
651
652 case EVR_SS: // signed short
653 {
654 return ApplyDcmtkToCTypeConverter<DcmtkToSint16Converter>(element);
655 }
656
657 case EVR_UL: // unsigned long
658 {
659 return ApplyDcmtkToCTypeConverter<DcmtkToUint32Converter>(element);
660 }
661
662 case EVR_US: // unsigned short
663 {
664 return ApplyDcmtkToCTypeConverter<DcmtkToUint16Converter>(element);
665 }
666
667 case EVR_FL: // float single-precision
668 {
669 return ApplyDcmtkToCTypeConverter<DcmtkToFloat32Converter>(element);
670 }
671
672 case EVR_FD: // float double-precision
673 {
674 return ApplyDcmtkToCTypeConverter<DcmtkToFloat64Converter>(element);
675 }
676
677
678 /**
679 * Attribute tag.
680 **/
681
682 case EVR_AT:
683 {
684 DcmTagKey tag;
685 if (dynamic_cast<DcmAttributeTag&>(element).getTagVal(tag, 0).good())
686 {
687 DicomTag t(tag.getGroup(), tag.getElement());
688 return new DicomValue(t.Format(), false);
689 }
690 else
691 {
692 return new DicomValue;
693 }
694 }
695
696
697 /**
698 * Sequence types, should never occur at this point because of
699 * "element.isLeaf()".
700 **/
701
702 case EVR_SQ: // sequence of items
703 return new DicomValue;
704
705
706 /**
707 * Internal to DCMTK.
708 **/
709
710 case EVR_xs: // SS or US depending on context
711 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
712 case EVR_na: // na="not applicable", for data which has no VR
713 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
714 case EVR_item: // used internally for items
715 case EVR_metainfo: // used internally for meta info datasets
716 case EVR_dataset: // used internally for datasets
717 case EVR_fileFormat: // used internally for DICOM files
718 case EVR_dicomDir: // used internally for DICOMDIR objects
719 case EVR_dirRecord: // used internally for DICOMDIR records
720 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
721 case EVR_pixelItem: // used internally for pixel items in a compressed image
722 case EVR_PixelData: // used internally for uncompressed pixeld data
723 case EVR_OverlayData: // used internally for overlay data
724 return new DicomValue;
725
726
727 /**
728 * Default case.
729 **/
730
731 default:
732 return new DicomValue;
733 }
734 }
735 catch (boost::bad_lexical_cast&)
736 {
737 return new DicomValue;
738 }
739 catch (std::bad_cast&)
740 {
741 return new DicomValue;
742 }
743 }
744
745
746 static Json::Value& PrepareNode(Json::Value& parent,
747 DcmElement& element,
748 DicomToJsonFormat format)
749 {
750 assert(parent.type() == Json::objectValue);
751
752 DicomTag tag(FromDcmtkBridge::GetTag(element));
753 const std::string formattedTag = tag.Format();
754
755 if (format == DicomToJsonFormat_Short)
756 {
757 parent[formattedTag] = Json::nullValue;
758 return parent[formattedTag];
759 }
760
761 // This code gives access to the name of the private tags
762 std::string tagName = FromDcmtkBridge::GetTagName(element);
763
764 switch (format)
765 {
766 case DicomToJsonFormat_Human:
767 parent[tagName] = Json::nullValue;
768 return parent[tagName];
769
770 case DicomToJsonFormat_Full:
771 {
772 parent[formattedTag] = Json::objectValue;
773 Json::Value& node = parent[formattedTag];
774
775 if (element.isLeaf())
776 {
777 node["Name"] = tagName;
778
779 if (element.getTag().getPrivateCreator() != NULL)
780 {
781 node["PrivateCreator"] = element.getTag().getPrivateCreator();
782 }
783
784 return node;
785 }
786 else
787 {
788 node["Name"] = tagName;
789 node["Type"] = "Sequence";
790 node["Value"] = Json::nullValue;
791 return node["Value"];
792 }
793 }
794
795 default:
796 throw OrthancException(ErrorCode_ParameterOutOfRange);
797 }
798 }
799
800
801 static void LeafValueToJson(Json::Value& target,
802 const DicomValue& value,
803 DicomToJsonFormat format,
804 DicomToJsonFlags flags,
805 unsigned int maxStringLength)
806 {
807 Json::Value* targetValue = NULL;
808 Json::Value* targetType = NULL;
809
810 switch (format)
811 {
812 case DicomToJsonFormat_Short:
813 case DicomToJsonFormat_Human:
814 {
815 assert(target.type() == Json::nullValue);
816 targetValue = &target;
817 break;
818 }
819
820 case DicomToJsonFormat_Full:
821 {
822 assert(target.type() == Json::objectValue);
823 target["Value"] = Json::nullValue;
824 target["Type"] = Json::nullValue;
825 targetType = &target["Type"];
826 targetValue = &target["Value"];
827 break;
828 }
829
830 default:
831 throw OrthancException(ErrorCode_ParameterOutOfRange);
832 }
833
834 assert(targetValue != NULL);
835 assert(targetValue->type() == Json::nullValue);
836 assert(targetType == NULL || targetType->type() == Json::nullValue);
837
838 if (value.IsNull())
839 {
840 if (targetType != NULL)
841 {
842 *targetType = "Null";
843 }
844 }
845 else if (value.IsBinary())
846 {
847 if (flags & DicomToJsonFlags_ConvertBinaryToAscii)
848 {
849 *targetValue = Toolbox::ConvertToAscii(value.GetContent());
850 }
851 else
852 {
853 std::string s;
854 value.FormatDataUriScheme(s);
855 *targetValue = s;
856 }
857
858 if (targetType != NULL)
859 {
860 *targetType = "Binary";
861 }
862 }
863 else if (maxStringLength == 0 ||
864 value.GetContent().size() <= maxStringLength)
865 {
866 *targetValue = value.GetContent();
867
868 if (targetType != NULL)
869 {
870 *targetType = "String";
871 }
872 }
873 else
874 {
875 if (targetType != NULL)
876 {
877 *targetType = "TooLong";
878 }
879 }
880 }
881
882
883 void FromDcmtkBridge::ElementToJson(Json::Value& parent,
884 DcmElement& element,
885 DicomToJsonFormat format,
886 DicomToJsonFlags flags,
887 unsigned int maxStringLength,
888 Encoding encoding,
889 bool hasCodeExtensions,
890 const std::set<DicomTag>& ignoreTagLength)
891 {
892 if (parent.type() == Json::nullValue)
893 {
894 parent = Json::objectValue;
895 }
896
897 assert(parent.type() == Json::objectValue);
898 Json::Value& target = PrepareNode(parent, element, format);
899
900 if (element.isLeaf())
901 {
902 // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
903 std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
904 (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength));
905
906 if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
907 {
908 LeafValueToJson(target, *v, format, flags, maxStringLength);
909 }
910 else
911 {
912 LeafValueToJson(target, *v, format, flags, 0);
913 }
914 }
915 else
916 {
917 assert(target.type() == Json::nullValue);
918 target = Json::arrayValue;
919
920 // "All subclasses of DcmElement except for DcmSequenceOfItems
921 // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
922 // etc. are not." The following dynamic_cast is thus OK.
923 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
924
925 for (unsigned long i = 0; i < sequence.card(); i++)
926 {
927 DcmItem* child = sequence.getItem(i);
928 Json::Value& v = target.append(Json::objectValue);
929 DatasetToJson(v, *child, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength);
930 }
931 }
932 }
933
934
935 void FromDcmtkBridge::DatasetToJson(Json::Value& parent,
936 DcmItem& item,
937 DicomToJsonFormat format,
938 DicomToJsonFlags flags,
939 unsigned int maxStringLength,
940 Encoding encoding,
941 bool hasCodeExtensions,
942 const std::set<DicomTag>& ignoreTagLength)
943 {
944 assert(parent.type() == Json::objectValue);
945
946 for (unsigned long i = 0; i < item.card(); i++)
947 {
948 DcmElement* element = item.getElement(i);
949 if (element == NULL)
950 {
951 throw OrthancException(ErrorCode_InternalError);
952 }
953
954 DicomTag tag(FromDcmtkBridge::Convert(element->getTag()));
955
956 /*element->getTag().isPrivate()*/
957 if (tag.IsPrivate() &&
958 !(flags & DicomToJsonFlags_IncludePrivateTags))
959 {
960 continue;
961 }
962
963 if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
964 {
965 DictionaryLocker locker;
966 if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL)
967 {
968 continue;
969 }
970 }
971
972 if (IsBinaryTag(element->getTag()))
973 {
974 // This is a binary tag
975 if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
976 (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary)))
977 {
978 continue;
979 }
980 }
981
982 FromDcmtkBridge::ElementToJson(parent, *element, format, flags,
983 maxStringLength, encoding, hasCodeExtensions, ignoreTagLength);
984 }
985 }
986
987
988 void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target,
989 DcmDataset& dataset,
990 DicomToJsonFormat format,
991 DicomToJsonFlags flags,
992 unsigned int maxStringLength,
993 Encoding defaultEncoding,
994 const std::set<DicomTag>& ignoreTagLength)
995 {
996 bool hasCodeExtensions;
997 Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
998
999 target = Json::objectValue;
1000 DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength);
1001 }
1002
1003
1004 void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target,
1005 DcmMetaInfo& dataset,
1006 DicomToJsonFormat format,
1007 DicomToJsonFlags flags,
1008 unsigned int maxStringLength)
1009 {
1010 std::set<DicomTag> ignoreTagLength;
1011 target = Json::objectValue;
1012 DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength);
1013 }
1014
1015
1016
1017 static std::string GetTagNameInternal(DcmTag& tag)
1018 {
1019 {
1020 // Some patches for important tags because of different DICOM
1021 // dictionaries between DCMTK versions
1022 DicomTag tmp(tag.getGroup(), tag.getElement());
1023 std::string n = tmp.GetMainTagsName();
1024 if (n.size() != 0)
1025 {
1026 return n;
1027 }
1028 // End of patches
1029 }
1030
1031 #if 0
1032 // This version explicitly calls the dictionary
1033 const DcmDataDictionary& dict = dcmDataDict.rdlock();
1034 const DcmDictEntry* entry = dict.findEntry(tag, NULL);
1035
1036 std::string s(DcmTag_ERROR_TagName);
1037 if (entry != NULL)
1038 {
1039 s = std::string(entry->getTagName());
1040 }
1041
1042 dcmDataDict.unlock();
1043 return s;
1044 #else
1045 const char* name = tag.getTagName();
1046 if (name == NULL)
1047 {
1048 return DcmTag_ERROR_TagName;
1049 }
1050 else
1051 {
1052 return std::string(name);
1053 }
1054 #endif
1055 }
1056
1057
1058 std::string FromDcmtkBridge::GetTagName(const DicomTag& t,
1059 const std::string& privateCreator)
1060 {
1061 DcmTag tag(t.GetGroup(), t.GetElement());
1062
1063 if (!privateCreator.empty())
1064 {
1065 tag.setPrivateCreator(privateCreator.c_str());
1066 }
1067
1068 return GetTagNameInternal(tag);
1069 }
1070
1071
1072 std::string FromDcmtkBridge::GetTagName(const DcmElement& element)
1073 {
1074 // Copy the tag to ensure const-correctness of DcmElement. Note
1075 // that the private creator information is also copied.
1076 DcmTag tag(element.getTag());
1077
1078 return GetTagNameInternal(tag);
1079 }
1080
1081
1082
1083 DicomTag FromDcmtkBridge::ParseTag(const char* name)
1084 {
1085 DicomTag parsed(0, 0);
1086 if (DicomTag::ParseHexadecimal(parsed, name))
1087 {
1088 return parsed;
1089 }
1090
1091 #if 0
1092 const DcmDataDictionary& dict = dcmDataDict.rdlock();
1093 const DcmDictEntry* entry = dict.findEntry(name);
1094
1095 if (entry == NULL)
1096 {
1097 dcmDataDict.unlock();
1098 throw OrthancException(ErrorCode_UnknownDicomTag);
1099 }
1100 else
1101 {
1102 DcmTagKey key = entry->getKey();
1103 DicomTag tag(key.getGroup(), key.getElement());
1104 dcmDataDict.unlock();
1105 return tag;
1106 }
1107 #else
1108 DcmTag tag;
1109 if (DcmTag::findTagFromName(name, tag).good())
1110 {
1111 return DicomTag(tag.getGTag(), tag.getETag());
1112 }
1113 else
1114 {
1115 LOG(INFO) << "Unknown DICOM tag: \"" << name << "\"";
1116 throw OrthancException(ErrorCode_UnknownDicomTag);
1117 }
1118 #endif
1119 }
1120
1121
1122 bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag)
1123 {
1124 DcmTag tmp(tag.GetGroup(), tag.GetElement());
1125 return tmp.isUnknownVR();
1126 }
1127
1128
1129 void FromDcmtkBridge::ToJson(Json::Value& result,
1130 const DicomMap& values,
1131 bool simplify)
1132 {
1133 if (result.type() != Json::objectValue)
1134 {
1135 throw OrthancException(ErrorCode_BadParameterType);
1136 }
1137
1138 result.clear();
1139
1140 for (DicomMap::Content::const_iterator
1141 it = values.content_.begin(); it != values.content_.end(); ++it)
1142 {
1143 // TODO Inject PrivateCreator if some is available in the DicomMap?
1144 const std::string tagName = GetTagName(it->first, "");
1145
1146 if (simplify)
1147 {
1148 if (it->second->IsNull())
1149 {
1150 result[tagName] = Json::nullValue;
1151 }
1152 else
1153 {
1154 // TODO IsBinary
1155 result[tagName] = it->second->GetContent();
1156 }
1157 }
1158 else
1159 {
1160 Json::Value value = Json::objectValue;
1161
1162 value["Name"] = tagName;
1163
1164 if (it->second->IsNull())
1165 {
1166 value["Type"] = "Null";
1167 value["Value"] = Json::nullValue;
1168 }
1169 else
1170 {
1171 // TODO IsBinary
1172 value["Type"] = "String";
1173 value["Value"] = it->second->GetContent();
1174 }
1175
1176 result[it->first.Format()] = value;
1177 }
1178 }
1179 }
1180
1181
1182 std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level)
1183 {
1184 char uid[100];
1185
1186 switch (level)
1187 {
1188 case ResourceType_Patient:
1189 // The "PatientID" field is of type LO (Long String), 64
1190 // Bytes Maximum. An UUID is of length 36, thus it can be used
1191 // as a random PatientID.
1192 return Toolbox::GenerateUuid();
1193
1194 case ResourceType_Instance:
1195 return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
1196
1197 case ResourceType_Series:
1198 return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
1199
1200 case ResourceType_Study:
1201 return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
1202
1203 default:
1204 throw OrthancException(ErrorCode_ParameterOutOfRange);
1205 }
1206 }
1207
1208
1209
1210 static bool SaveToMemoryBufferInternal(std::string& buffer,
1211 DcmFileFormat& dicom,
1212 E_TransferSyntax xfer)
1213 {
1214 E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
1215
1216 // Create a memory buffer with the proper size
1217 {
1218 const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType); // (*)
1219 buffer.resize(estimatedSize);
1220 }
1221
1222 DcmOutputBufferStream ob(&buffer[0], buffer.size());
1223
1224 // Fill the memory buffer with the meta-header and the dataset
1225 dicom.transferInit();
1226 OFCondition c = dicom.write(ob, xfer, encodingType, NULL,
1227 /*opt_groupLength*/ EGL_recalcGL,
1228 /*opt_paddingType*/ EPD_noChange,
1229 /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0,
1230 EWM_updateMeta /* creates new SOP instance UID on lossy */);
1231 dicom.transferEnd();
1232
1233 if (c.good())
1234 {
1235 // The DICOM file is successfully written, truncate the target
1236 // buffer if its size was overestimated by (*)
1237 ob.flush();
1238
1239 size_t effectiveSize = static_cast<size_t>(ob.tell());
1240 if (effectiveSize < buffer.size())
1241 {
1242 buffer.resize(effectiveSize);
1243 }
1244
1245 return true;
1246 }
1247 else
1248 {
1249 // Error
1250 buffer.clear();
1251 return false;
1252 }
1253 }
1254
1255
1256 bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
1257 DcmDataset& dataSet)
1258 {
1259 // Determine the transfer syntax which shall be used to write the
1260 // information to the file. If not possible, switch to the Little
1261 // Endian syntax, with explicit length.
1262
1263 // http://support.dcmtk.org/docs/dcxfer_8h-source.html
1264
1265
1266 /**
1267 * Note that up to Orthanc 0.7.1 (inclusive), the
1268 * "EXS_LittleEndianExplicit" was always used to save the DICOM
1269 * dataset into memory. We now keep the original transfer syntax
1270 * (if available).
1271 **/
1272 E_TransferSyntax xfer = dataSet.getCurrentXfer();
1273 if (xfer == EXS_Unknown)
1274 {
1275 // No information about the original transfer syntax: This is
1276 // most probably a DICOM dataset that was read from memory.
1277 xfer = EXS_LittleEndianExplicit;
1278 }
1279
1280 // Create the meta-header information
1281 DcmFileFormat ff(&dataSet);
1282 ff.validateMetaInfo(xfer);
1283 ff.removeInvalidGroups();
1284
1285 return SaveToMemoryBufferInternal(buffer, ff, xfer);
1286 }
1287
1288
1289 bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom,
1290 DicomTransferSyntax syntax,
1291 const DcmRepresentationParameter* representation)
1292 {
1293 E_TransferSyntax xfer;
1294 if (!LookupDcmtkTransferSyntax(xfer, syntax))
1295 {
1296 throw OrthancException(ErrorCode_InternalError);
1297 }
1298 else
1299 {
1300 DicomTransferSyntax sourceSyntax;
1301 bool known = LookupOrthancTransferSyntax(sourceSyntax, dicom);
1302
1303 if (!dicom.chooseRepresentation(xfer, representation).good() ||
1304 !dicom.canWriteXfer(xfer) ||
1305 !dicom.validateMetaInfo(xfer, EWM_updateMeta).good())
1306 {
1307 return false;
1308 }
1309 else
1310 {
1311 dicom.removeInvalidGroups();
1312
1313 if (known)
1314 {
1315 LOG(INFO) << "Transcoded an image from transfer syntax "
1316 << GetTransferSyntaxUid(sourceSyntax) << " to "
1317 << GetTransferSyntaxUid(syntax);
1318 }
1319 else
1320 {
1321 LOG(INFO) << "Transcoded an image from unknown transfer syntax to "
1322 << GetTransferSyntaxUid(syntax);
1323 }
1324
1325 return true;
1326 }
1327 }
1328 }
1329
1330
1331 ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag)
1332 {
1333 DcmTag t(tag.GetGroup(), tag.GetElement());
1334 return Convert(t.getEVR());
1335 }
1336
1337 ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr)
1338 {
1339 switch (vr)
1340 {
1341 case EVR_AE:
1342 return ValueRepresentation_ApplicationEntity;
1343
1344 case EVR_AS:
1345 return ValueRepresentation_AgeString;
1346
1347 case EVR_AT:
1348 return ValueRepresentation_AttributeTag;
1349
1350 case EVR_CS:
1351 return ValueRepresentation_CodeString;
1352
1353 case EVR_DA:
1354 return ValueRepresentation_Date;
1355
1356 case EVR_DS:
1357 return ValueRepresentation_DecimalString;
1358
1359 case EVR_DT:
1360 return ValueRepresentation_DateTime;
1361
1362 case EVR_FL:
1363 return ValueRepresentation_FloatingPointSingle;
1364
1365 case EVR_FD:
1366 return ValueRepresentation_FloatingPointDouble;
1367
1368 case EVR_IS:
1369 return ValueRepresentation_IntegerString;
1370
1371 case EVR_LO:
1372 return ValueRepresentation_LongString;
1373
1374 case EVR_LT:
1375 return ValueRepresentation_LongText;
1376
1377 case EVR_OB:
1378 return ValueRepresentation_OtherByte;
1379
1380 #if DCMTK_VERSION_NUMBER >= 361
1381 case EVR_OD:
1382 return ValueRepresentation_OtherDouble;
1383 #endif
1384
1385 case EVR_OF:
1386 return ValueRepresentation_OtherFloat;
1387
1388 #if DCMTK_VERSION_NUMBER >= 362
1389 case EVR_OL:
1390 return ValueRepresentation_OtherLong;
1391 #endif
1392
1393 case EVR_OW:
1394 return ValueRepresentation_OtherWord;
1395
1396 case EVR_PN:
1397 return ValueRepresentation_PersonName;
1398
1399 case EVR_SH:
1400 return ValueRepresentation_ShortString;
1401
1402 case EVR_SL:
1403 return ValueRepresentation_SignedLong;
1404
1405 case EVR_SQ:
1406 return ValueRepresentation_Sequence;
1407
1408 case EVR_SS:
1409 return ValueRepresentation_SignedShort;
1410
1411 case EVR_ST:
1412 return ValueRepresentation_ShortText;
1413
1414 case EVR_TM:
1415 return ValueRepresentation_Time;
1416
1417 #if DCMTK_VERSION_NUMBER >= 361
1418 case EVR_UC:
1419 return ValueRepresentation_UnlimitedCharacters;
1420 #endif
1421
1422 case EVR_UI:
1423 return ValueRepresentation_UniqueIdentifier;
1424
1425 case EVR_UL:
1426 return ValueRepresentation_UnsignedLong;
1427
1428 case EVR_UN:
1429 return ValueRepresentation_Unknown;
1430
1431 #if DCMTK_VERSION_NUMBER >= 361
1432 case EVR_UR:
1433 return ValueRepresentation_UniversalResource;
1434 #endif
1435
1436 case EVR_US:
1437 return ValueRepresentation_UnsignedShort;
1438
1439 case EVR_UT:
1440 return ValueRepresentation_UnlimitedText;
1441
1442 default:
1443 return ValueRepresentation_NotSupported;
1444 }
1445 }
1446
1447
1448 DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag,
1449 const std::string& privateCreator)
1450 {
1451 if (tag.IsPrivate() &&
1452 privateCreator.empty())
1453 {
1454 // This solves issue 140 (Modifying private tags with REST API
1455 // changes VR from LO to UN)
1456 // https://bitbucket.org/sjodogne/orthanc/issues/140
1457 LOG(WARNING) << "Private creator should not be empty while creating a private tag: " << tag.Format();
1458 }
1459
1460 #if DCMTK_VERSION_NUMBER >= 362
1461 DcmTag key(tag.GetGroup(), tag.GetElement());
1462 if (tag.IsPrivate())
1463 {
1464 return DcmItem::newDicomElement(key, privateCreator.c_str());
1465 }
1466 else
1467 {
1468 return DcmItem::newDicomElement(key, NULL);
1469 }
1470
1471 #else
1472 DcmTag key(tag.GetGroup(), tag.GetElement());
1473 if (tag.IsPrivate())
1474 {
1475 // https://forum.dcmtk.org/viewtopic.php?t=4527
1476 LOG(WARNING) << "You are using DCMTK <= 3.6.1: All the private tags "
1477 "are considered as having a binary value representation";
1478 key.setPrivateCreator(privateCreator.c_str());
1479 return new DcmOtherByteOtherWord(key);
1480 }
1481 else
1482 {
1483 return newDicomElement(key);
1484 }
1485 #endif
1486 }
1487
1488
1489
1490 void FromDcmtkBridge::FillElementWithString(DcmElement& element,
1491 const std::string& utf8Value,
1492 bool decodeDataUriScheme,
1493 Encoding dicomEncoding)
1494 {
1495 std::string binary;
1496 const std::string* decoded = &utf8Value;
1497
1498 if (decodeDataUriScheme &&
1499 boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY))
1500 {
1501 std::string mime;
1502 if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
1503 {
1504 throw OrthancException(ErrorCode_BadFileFormat);
1505 }
1506
1507 decoded = &binary;
1508 }
1509 else if (dicomEncoding != Encoding_Utf8)
1510 {
1511 binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding);
1512 decoded = &binary;
1513 }
1514
1515 if (IsBinaryTag(element.getTag()))
1516 {
1517 bool ok;
1518
1519 switch (element.getTag().getEVR())
1520 {
1521 case EVR_OW:
1522 if (decoded->size() % sizeof(Uint16) != 0)
1523 {
1524 LOG(ERROR) << "A tag with OW VR must have an even number of bytes";
1525 ok = false;
1526 }
1527 else
1528 {
1529 ok = element.putUint16Array((const Uint16*) decoded->c_str(), decoded->size() / sizeof(Uint16)).good();
1530 }
1531
1532 break;
1533
1534 default:
1535 ok = element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good();
1536 break;
1537 }
1538
1539 if (ok)
1540 {
1541 return;
1542 }
1543 else
1544 {
1545 throw OrthancException(ErrorCode_InternalError);
1546 }
1547 }
1548
1549 bool ok = false;
1550
1551 try
1552 {
1553 switch (element.getTag().getEVR())
1554 {
1555 // http://support.dcmtk.org/docs/dcvr_8h-source.html
1556
1557 /**
1558 * TODO.
1559 **/
1560
1561 case EVR_OB: // other byte
1562 case EVR_OW: // other word
1563 case EVR_AT: // attribute tag
1564 throw OrthancException(ErrorCode_NotImplemented);
1565
1566 case EVR_UN: // unknown value representation
1567 throw OrthancException(ErrorCode_ParameterOutOfRange);
1568
1569
1570 /**
1571 * String types.
1572 **/
1573
1574 case EVR_DS: // decimal string
1575 case EVR_IS: // integer string
1576 case EVR_AS: // age string
1577 case EVR_DA: // date string
1578 case EVR_DT: // date time string
1579 case EVR_TM: // time string
1580 case EVR_AE: // application entity title
1581 case EVR_CS: // code string
1582 case EVR_SH: // short string
1583 case EVR_LO: // long string
1584 case EVR_ST: // short text
1585 case EVR_LT: // long text
1586 case EVR_UT: // unlimited text
1587 case EVR_PN: // person name
1588 case EVR_UI: // unique identifier
1589 #if DCMTK_VERSION_NUMBER >= 361
1590 case EVR_UC: // unlimited characters
1591 case EVR_UR: // URI/URL
1592 #endif
1593 {
1594 ok = element.putString(decoded->c_str()).good();
1595 break;
1596 }
1597
1598
1599 /**
1600 * Numerical types
1601 **/
1602
1603 case EVR_SL: // signed long
1604 {
1605 ok = element.putSint32(boost::lexical_cast<Sint32>(*decoded)).good();
1606 break;
1607 }
1608
1609 case EVR_SS: // signed short
1610 {
1611 ok = element.putSint16(boost::lexical_cast<Sint16>(*decoded)).good();
1612 break;
1613 }
1614
1615 case EVR_UL: // unsigned long
1616 #if DCMTK_VERSION_NUMBER >= 362
1617 case EVR_OL: // other long (requires byte-swapping)
1618 #endif
1619 {
1620 ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
1621 break;
1622 }
1623
1624 case EVR_US: // unsigned short
1625 {
1626 ok = element.putUint16(boost::lexical_cast<Uint16>(*decoded)).good();
1627 break;
1628 }
1629
1630 case EVR_FL: // float single-precision
1631 case EVR_OF: // other float (requires byte swapping)
1632 {
1633 ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
1634 break;
1635 }
1636
1637 case EVR_FD: // float double-precision
1638 #if DCMTK_VERSION_NUMBER >= 361
1639 case EVR_OD: // other double (requires byte-swapping)
1640 #endif
1641 {
1642 ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
1643 break;
1644 }
1645
1646
1647 /**
1648 * Sequence types, should never occur at this point.
1649 **/
1650
1651 case EVR_SQ: // sequence of items
1652 {
1653 ok = false;
1654 break;
1655 }
1656
1657
1658 /**
1659 * Internal to DCMTK.
1660 **/
1661
1662 case EVR_ox: // OB or OW depending on context
1663 case EVR_xs: // SS or US depending on context
1664 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
1665 case EVR_na: // na="not applicable", for data which has no VR
1666 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
1667 case EVR_item: // used internally for items
1668 case EVR_metainfo: // used internally for meta info datasets
1669 case EVR_dataset: // used internally for datasets
1670 case EVR_fileFormat: // used internally for DICOM files
1671 case EVR_dicomDir: // used internally for DICOMDIR objects
1672 case EVR_dirRecord: // used internally for DICOMDIR records
1673 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
1674 case EVR_pixelItem: // used internally for pixel items in a compressed image
1675 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
1676 case EVR_PixelData: // used internally for uncompressed pixeld data
1677 case EVR_OverlayData: // used internally for overlay data
1678 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
1679 default:
1680 break;
1681 }
1682 }
1683 catch (boost::bad_lexical_cast&)
1684 {
1685 ok = false;
1686 }
1687
1688 if (!ok)
1689 {
1690 DicomTag tag(element.getTag().getGroup(), element.getTag().getElement());
1691 throw OrthancException(ErrorCode_BadFileFormat,
1692 "While creating a DICOM instance, tag (" + tag.Format() +
1693 ") has out-of-range value: \"" + (*decoded) + "\"");
1694 }
1695 }
1696
1697
1698 DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
1699 const Json::Value& value,
1700 bool decodeDataUriScheme,
1701 Encoding dicomEncoding,
1702 const std::string& privateCreator)
1703 {
1704 std::unique_ptr<DcmElement> element;
1705
1706 switch (value.type())
1707 {
1708 case Json::stringValue:
1709 element.reset(CreateElementForTag(tag, privateCreator));
1710 FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding);
1711 break;
1712
1713 case Json::nullValue:
1714 element.reset(CreateElementForTag(tag, privateCreator));
1715 FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding);
1716 break;
1717
1718 case Json::arrayValue:
1719 {
1720 const char* p = NULL;
1721 if (tag.IsPrivate() &&
1722 !privateCreator.empty())
1723 {
1724 p = privateCreator.c_str();
1725 }
1726
1727 DcmTag key(tag.GetGroup(), tag.GetElement(), p);
1728 if (key.getEVR() != EVR_SQ)
1729 {
1730 throw OrthancException(ErrorCode_BadParameterType,
1731 "Bad Parameter type for tag " + tag.Format());
1732 }
1733
1734 DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key);
1735 element.reset(sequence);
1736
1737 for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
1738 {
1739 std::unique_ptr<DcmItem> item(new DcmItem);
1740
1741 switch (value[i].type())
1742 {
1743 case Json::objectValue:
1744 {
1745 Json::Value::Members members = value[i].getMemberNames();
1746 for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
1747 {
1748 item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding, privateCreator));
1749 }
1750 break;
1751 }
1752
1753 case Json::arrayValue:
1754 {
1755 // Lua cannot disambiguate between an empty dictionary
1756 // and an empty array
1757 if (value[i].size() != 0)
1758 {
1759 throw OrthancException(ErrorCode_BadParameterType);
1760 }
1761 break;
1762 }
1763
1764 default:
1765 throw OrthancException(ErrorCode_BadParameterType);
1766 }
1767
1768 sequence->append(item.release());
1769 }
1770
1771 break;
1772 }
1773
1774 default:
1775 throw OrthancException(ErrorCode_BadParameterType, "Bad Parameter type for tag " + tag.Format());
1776 }
1777
1778 return element.release();
1779 }
1780
1781
1782 DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset)
1783 {
1784 DcmElement *element = NULL;
1785 if (!dataset.findAndGetElement(DCM_PixelData, element).good())
1786 {
1787 throw OrthancException(ErrorCode_BadFileFormat);
1788 }
1789
1790 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
1791
1792 E_TransferSyntax repType;
1793 const DcmRepresentationParameter *repParam = NULL;
1794 pixelData.getCurrentRepresentationKey(repType, repParam);
1795
1796 DcmPixelSequence* pixelSequence = NULL;
1797 if (!pixelData.getEncapsulatedRepresentation(repType, repParam, pixelSequence).good())
1798 {
1799 return NULL;
1800 }
1801 else
1802 {
1803 return pixelSequence;
1804 }
1805 }
1806
1807
1808 Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json,
1809 Encoding defaultEncoding)
1810 {
1811 if (json.type() != Json::objectValue)
1812 {
1813 throw OrthancException(ErrorCode_BadParameterType);
1814 }
1815
1816 Encoding encoding = defaultEncoding;
1817
1818 const Json::Value::Members tags = json.getMemberNames();
1819
1820 // Look for SpecificCharacterSet (0008,0005) in the JSON file
1821 for (size_t i = 0; i < tags.size(); i++)
1822 {
1823 DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
1824 if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
1825 {
1826 const Json::Value& value = json[tags[i]];
1827 if (value.type() != Json::stringValue ||
1828 (value.asString().length() != 0 &&
1829 !GetDicomEncoding(encoding, value.asCString())))
1830 {
1831 throw OrthancException(ErrorCode_BadRequest,
1832 "Unknown encoding while creating DICOM from JSON: " +
1833 value.toStyledString());
1834 }
1835
1836 if (value.asString().length() == 0)
1837 {
1838 return defaultEncoding;
1839 }
1840 }
1841 }
1842
1843 return encoding;
1844 }
1845
1846
1847 static void SetString(DcmDataset& target,
1848 const DcmTag& tag,
1849 const std::string& value)
1850 {
1851 if (!target.putAndInsertString(tag, value.c_str()).good())
1852 {
1853 throw OrthancException(ErrorCode_InternalError);
1854 }
1855 }
1856
1857
1858 DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8
1859 bool generateIdentifiers,
1860 bool decodeDataUriScheme,
1861 Encoding defaultEncoding,
1862 const std::string& privateCreator)
1863 {
1864 std::unique_ptr<DcmDataset> result(new DcmDataset);
1865 Encoding encoding = ExtractEncoding(json, defaultEncoding);
1866
1867 SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding));
1868
1869 const Json::Value::Members tags = json.getMemberNames();
1870
1871 bool hasPatientId = false;
1872 bool hasStudyInstanceUid = false;
1873 bool hasSeriesInstanceUid = false;
1874 bool hasSopInstanceUid = false;
1875
1876 for (size_t i = 0; i < tags.size(); i++)
1877 {
1878 DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
1879 const Json::Value& value = json[tags[i]];
1880
1881 if (tag == DICOM_TAG_PATIENT_ID)
1882 {
1883 hasPatientId = true;
1884 }
1885 else if (tag == DICOM_TAG_STUDY_INSTANCE_UID)
1886 {
1887 hasStudyInstanceUid = true;
1888 }
1889 else if (tag == DICOM_TAG_SERIES_INSTANCE_UID)
1890 {
1891 hasSeriesInstanceUid = true;
1892 }
1893 else if (tag == DICOM_TAG_SOP_INSTANCE_UID)
1894 {
1895 hasSopInstanceUid = true;
1896 }
1897
1898 if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
1899 {
1900 std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
1901 const DcmTagKey& tag = element->getTag();
1902
1903 result->findAndDeleteElement(tag);
1904
1905 DcmElement* tmp = element.release();
1906 if (!result->insert(tmp, false, false).good())
1907 {
1908 delete tmp;
1909 throw OrthancException(ErrorCode_InternalError);
1910 }
1911 }
1912 }
1913
1914 if (!hasPatientId &&
1915 generateIdentifiers)
1916 {
1917 SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient));
1918 }
1919
1920 if (!hasStudyInstanceUid &&
1921 generateIdentifiers)
1922 {
1923 SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study));
1924 }
1925
1926 if (!hasSeriesInstanceUid &&
1927 generateIdentifiers)
1928 {
1929 SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series));
1930 }
1931
1932 if (!hasSopInstanceUid &&
1933 generateIdentifiers)
1934 {
1935 SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance));
1936 }
1937
1938 return result.release();
1939 }
1940
1941
1942 DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer,
1943 size_t size)
1944 {
1945 DcmInputBufferStream is;
1946 if (size > 0)
1947 {
1948 is.setBuffer(buffer, size);
1949 }
1950 is.setEos();
1951
1952 std::unique_ptr<DcmFileFormat> result(new DcmFileFormat);
1953
1954 result->transferInit();
1955
1956 /**
1957 * New in Orthanc 1.6.0: The "size" is given as an argument to the
1958 * "read()" method. This can avoid huge memory consumption if
1959 * parsing an invalid DICOM file, which can notably been observed
1960 * by executing the integration test "test_upload_compressed" on
1961 * valgrind running Orthanc.
1962 **/
1963 if (!result->read(is, EXS_Unknown, EGL_noChange, size).good())
1964 {
1965 throw OrthancException(ErrorCode_BadFileFormat,
1966 "Cannot parse an invalid DICOM file (size: " +
1967 boost::lexical_cast<std::string>(size) + " bytes)");
1968 }
1969
1970 result->loadAllDataIntoMemory();
1971 result->transferEnd();
1972
1973 return result.release();
1974 }
1975
1976
1977 void FromDcmtkBridge::FromJson(DicomMap& target,
1978 const Json::Value& source)
1979 {
1980 if (source.type() != Json::objectValue)
1981 {
1982 throw OrthancException(ErrorCode_BadFileFormat);
1983 }
1984
1985 target.Clear();
1986
1987 Json::Value::Members members = source.getMemberNames();
1988
1989 for (size_t i = 0; i < members.size(); i++)
1990 {
1991 const Json::Value& value = source[members[i]];
1992
1993 if (value.type() != Json::stringValue)
1994 {
1995 throw OrthancException(ErrorCode_BadFileFormat);
1996 }
1997
1998 target.SetValue(ParseTag(members[i]), value.asString(), false);
1999 }
2000 }
2001
2002
2003 void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset,
2004 Encoding source,
2005 bool hasSourceCodeExtensions,
2006 Encoding target)
2007 {
2008 // Recursive exploration of a dataset to change the encoding of
2009 // each string-like element
2010
2011 if (source == target)
2012 {
2013 return;
2014 }
2015
2016 for (unsigned long i = 0; i < dataset.card(); i++)
2017 {
2018 DcmElement* element = dataset.getElement(i);
2019 if (element)
2020 {
2021 if (element->isLeaf())
2022 {
2023 char *c = NULL;
2024 if (element->isaString() &&
2025 element->getString(c).good() &&
2026 c != NULL)
2027 {
2028 std::string a = Toolbox::ConvertToUtf8(c, source, hasSourceCodeExtensions);
2029 std::string b = Toolbox::ConvertFromUtf8(a, target);
2030 element->putString(b.c_str());
2031 }
2032 }
2033 else
2034 {
2035 // "All subclasses of DcmElement except for DcmSequenceOfItems
2036 // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
2037 // etc. are not." The following dynamic_cast is thus OK.
2038 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element);
2039
2040 for (unsigned long j = 0; j < sequence.card(); j++)
2041 {
2042 ChangeStringEncoding(*sequence.getItem(j), source, hasSourceCodeExtensions, target);
2043 }
2044 }
2045 }
2046 }
2047 }
2048
2049
2050 #if ORTHANC_ENABLE_LUA == 1
2051 void FromDcmtkBridge::ExecuteToDicom(DicomMap& target,
2052 LuaFunctionCall& call)
2053 {
2054 Json::Value output;
2055 call.ExecuteToJson(output, true /* keep strings */);
2056
2057 target.Clear();
2058
2059 if (output.type() == Json::arrayValue &&
2060 output.size() == 0)
2061 {
2062 // This case happens for empty tables
2063 return;
2064 }
2065
2066 if (output.type() != Json::objectValue)
2067 {
2068 throw OrthancException(ErrorCode_LuaBadOutput,
2069 "Lua: The script must return a table");
2070 }
2071
2072 Json::Value::Members members = output.getMemberNames();
2073
2074 for (size_t i = 0; i < members.size(); i++)
2075 {
2076 if (output[members[i]].type() != Json::stringValue)
2077 {
2078 throw OrthancException(ErrorCode_LuaBadOutput,
2079 "Lua: The script must return a table "
2080 "mapping names of DICOM tags to strings");
2081 }
2082
2083 DicomTag tag(ParseTag(members[i]));
2084 target.SetValue(tag, output[members[i]].asString(), false);
2085 }
2086 }
2087 #endif
2088
2089
2090 void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target,
2091 DcmItem& dataset,
2092 const std::set<DicomTag>& ignoreTagLength)
2093 {
2094 ExtractDicomSummary(target, dataset,
2095 ORTHANC_MAXIMUM_TAG_LENGTH,
2096 GetDefaultDicomEncoding(), ignoreTagLength);
2097 }
2098
2099
2100 void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target,
2101 DcmDataset& dataset,
2102 const std::set<DicomTag>& ignoreTagLength)
2103 {
2104 ExtractDicomAsJson(target, dataset,
2105 DicomToJsonFormat_Full,
2106 DicomToJsonFlags_Default,
2107 ORTHANC_MAXIMUM_TAG_LENGTH,
2108 GetDefaultDicomEncoding(),
2109 ignoreTagLength);
2110 }
2111
2112
2113 void FromDcmtkBridge::InitializeCodecs()
2114 {
2115 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
2116 LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK";
2117 DJLSDecoderRegistration::registerCodecs();
2118 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2119 DJLSEncoderRegistration::registerCodecs();
2120 # endif
2121 #endif
2122
2123 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
2124 LOG(INFO) << "Registering JPEG codecs in DCMTK";
2125 DJDecoderRegistration::registerCodecs();
2126 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2127 DJEncoderRegistration::registerCodecs();
2128 # endif
2129 #endif
2130
2131 LOG(INFO) << "Registering RLE codecs in DCMTK";
2132 DcmRLEDecoderRegistration::registerCodecs();
2133 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2134 DcmRLEEncoderRegistration::registerCodecs();
2135 #endif
2136 }
2137
2138
2139 void FromDcmtkBridge::FinalizeCodecs()
2140 {
2141 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
2142 // Unregister JPEG-LS codecs
2143 DJLSDecoderRegistration::cleanup();
2144 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2145 DJLSEncoderRegistration::cleanup();
2146 # endif
2147 #endif
2148
2149 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
2150 // Unregister JPEG codecs
2151 DJDecoderRegistration::cleanup();
2152 # if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2153 DJEncoderRegistration::cleanup();
2154 # endif
2155 #endif
2156
2157 DcmRLEDecoderRegistration::cleanup();
2158 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2159 DcmRLEEncoderRegistration::cleanup();
2160 #endif
2161 }
2162
2163
2164
2165 // Forward declaration
2166 static void ApplyVisitorToElement(DcmElement& element,
2167 ITagVisitor& visitor,
2168 const std::vector<DicomTag>& parentTags,
2169 const std::vector<size_t>& parentIndexes,
2170 Encoding encoding,
2171 bool hasCodeExtensions);
2172
2173 static void ApplyVisitorToDataset(DcmItem& dataset,
2174 ITagVisitor& visitor,
2175 const std::vector<DicomTag>& parentTags,
2176 const std::vector<size_t>& parentIndexes,
2177 Encoding encoding,
2178 bool hasCodeExtensions)
2179 {
2180 assert(parentTags.size() == parentIndexes.size());
2181
2182 for (unsigned long i = 0; i < dataset.card(); i++)
2183 {
2184 DcmElement* element = dataset.getElement(i);
2185 if (element == NULL)
2186 {
2187 throw OrthancException(ErrorCode_InternalError);
2188 }
2189 else
2190 {
2191 ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions);
2192 }
2193 }
2194 }
2195
2196
2197 static void ApplyVisitorToLeaf(DcmElement& element,
2198 ITagVisitor& visitor,
2199 const std::vector<DicomTag>& parentTags,
2200 const std::vector<size_t>& parentIndexes,
2201 const DicomTag& tag,
2202 Encoding encoding,
2203 bool hasCodeExtensions)
2204 {
2205 // TODO - Merge this function, that is more recent, with ConvertLeafElement()
2206
2207 assert(element.isLeaf());
2208
2209 DcmEVR evr = element.getTag().getEVR();
2210
2211
2212 /**
2213 * Fix the EVR for types internal to DCMTK
2214 **/
2215
2216 if (evr == EVR_ox) // OB or OW depending on context
2217 {
2218 evr = EVR_OB;
2219 }
2220
2221 if (evr == EVR_UNKNOWN || // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
2222 evr == EVR_UNKNOWN2B) // used internally for elements with unknown VR with 2-byte length field in explicit VR
2223 {
2224 evr = EVR_UN;
2225 }
2226
2227 const ValueRepresentation vr = FromDcmtkBridge::Convert(evr);
2228
2229
2230 /**
2231 * Deal with binary data (including PixelData).
2232 **/
2233
2234 if (evr == EVR_OB || // other byte
2235 evr == EVR_OW || // other word
2236 evr == EVR_UN) // unknown value representation
2237 {
2238 Uint16* data16 = NULL;
2239 Uint8* data = NULL;
2240
2241 if (evr == EVR_OW &&
2242 element.getUint16Array(data16) == EC_Normal)
2243 {
2244 visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data16, element.getLength());
2245 }
2246 else if (evr != EVR_OW &&
2247 element.getUint8Array(data) == EC_Normal)
2248 {
2249 visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength());
2250 }
2251 else
2252 {
2253 visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
2254 }
2255
2256 return; // We're done
2257 }
2258
2259
2260 /**
2261 * Deal with plain strings (and convert them to UTF-8)
2262 **/
2263
2264 char *c = NULL;
2265 if (element.isaString() &&
2266 element.getString(c).good())
2267 {
2268 std::string utf8;
2269
2270 if (c != NULL) // This case corresponds to the empty string
2271 {
2272 if (element.getTag() == DCM_SpecificCharacterSet)
2273 {
2274 utf8.assign(c);
2275 }
2276 else
2277 {
2278 std::string s(c);
2279 utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
2280 }
2281 }
2282
2283 std::string newValue;
2284 ITagVisitor::Action action = visitor.VisitString
2285 (newValue, parentTags, parentIndexes, tag, vr, utf8);
2286
2287 switch (action)
2288 {
2289 case ITagVisitor::Action_None:
2290 break;
2291
2292 case ITagVisitor::Action_Replace:
2293 {
2294 std::string s = Toolbox::ConvertFromUtf8(newValue, encoding);
2295 if (element.putString(s.c_str()) != EC_Normal)
2296 {
2297 throw OrthancException(ErrorCode_InternalError,
2298 "Cannot replace value of tag: " + tag.Format());
2299 }
2300
2301 break;
2302 }
2303
2304 default:
2305 throw OrthancException(ErrorCode_InternalError);
2306 }
2307
2308 return; // We're done
2309 }
2310
2311
2312 try
2313 {
2314 // http://support.dcmtk.org/docs/dcvr_8h-source.html
2315 switch (evr)
2316 {
2317
2318 /**
2319 * Plain string values.
2320 **/
2321
2322 case EVR_DS: // decimal string
2323 case EVR_IS: // integer string
2324 case EVR_AS: // age string
2325 case EVR_DA: // date string
2326 case EVR_DT: // date time string
2327 case EVR_TM: // time string
2328 case EVR_AE: // application entity title
2329 case EVR_CS: // code string
2330 case EVR_SH: // short string
2331 case EVR_LO: // long string
2332 case EVR_ST: // short text
2333 case EVR_LT: // long text
2334 case EVR_UT: // unlimited text
2335 case EVR_PN: // person name
2336 case EVR_UI: // unique identifier
2337 {
2338 Uint8* data = NULL;
2339
2340 if (element.getUint8Array(data) == EC_Normal)
2341 {
2342 const Uint32 length = element.getLength();
2343 Uint32 l = 0;
2344 while (l < length &&
2345 data[l] != 0)
2346 {
2347 l++;
2348 }
2349
2350 if (l == length)
2351 {
2352 // Not a null-terminated plain string
2353 visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
2354 }
2355 else
2356 {
2357 std::string ignored;
2358 std::string s(reinterpret_cast<const char*>(data), l);
2359 ITagVisitor::Action action = visitor.VisitString
2360 (ignored, parentTags, parentIndexes, tag, vr,
2361 Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions));
2362
2363 if (action != ITagVisitor::Action_None)
2364 {
2365 LOG(WARNING) << "Cannot replace this string tag: "
2366 << FromDcmtkBridge::GetTagName(element)
2367 << " (" << tag.Format() << ")";
2368 }
2369 }
2370 }
2371 else
2372 {
2373 visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
2374 }
2375
2376 return;
2377 }
2378
2379 /**
2380 * Numeric types
2381 **/
2382
2383 case EVR_SL: // signed long
2384 {
2385 DcmSignedLong& content = dynamic_cast<DcmSignedLong&>(element);
2386
2387 std::vector<int64_t> values;
2388 values.reserve(content.getVM());
2389
2390 for (unsigned long i = 0; i < content.getVM(); i++)
2391 {
2392 Sint32 f;
2393 if (content.getSint32(f, i).good())
2394 {
2395 values.push_back(f);
2396 }
2397 }
2398
2399 visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
2400 break;
2401 }
2402
2403 case EVR_SS: // signed short
2404 {
2405 DcmSignedShort& content = dynamic_cast<DcmSignedShort&>(element);
2406
2407 std::vector<int64_t> values;
2408 values.reserve(content.getVM());
2409
2410 for (unsigned long i = 0; i < content.getVM(); i++)
2411 {
2412 Sint16 f;
2413 if (content.getSint16(f, i).good())
2414 {
2415 values.push_back(f);
2416 }
2417 }
2418
2419 visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
2420 break;
2421 }
2422
2423 case EVR_UL: // unsigned long
2424 #if DCMTK_VERSION_NUMBER >= 362
2425 case EVR_OL:
2426 #endif
2427 {
2428 DcmUnsignedLong& content = dynamic_cast<DcmUnsignedLong&>(element);
2429
2430 std::vector<int64_t> values;
2431 values.reserve(content.getVM());
2432
2433 for (unsigned long i = 0; i < content.getVM(); i++)
2434 {
2435 Uint32 f;
2436 if (content.getUint32(f, i).good())
2437 {
2438 values.push_back(f);
2439 }
2440 }
2441
2442 visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
2443 break;
2444 }
2445
2446 case EVR_US: // unsigned short
2447 {
2448 DcmUnsignedShort& content = dynamic_cast<DcmUnsignedShort&>(element);
2449
2450 std::vector<int64_t> values;
2451 values.reserve(content.getVM());
2452
2453 for (unsigned long i = 0; i < content.getVM(); i++)
2454 {
2455 Uint16 f;
2456 if (content.getUint16(f, i).good())
2457 {
2458 values.push_back(f);
2459 }
2460 }
2461
2462 visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values);
2463 break;
2464 }
2465
2466 case EVR_FL: // float single-precision
2467 case EVR_OF:
2468 {
2469 DcmFloatingPointSingle& content = dynamic_cast<DcmFloatingPointSingle&>(element);
2470
2471 std::vector<double> values;
2472 values.reserve(content.getVM());
2473
2474 for (unsigned long i = 0; i < content.getVM(); i++)
2475 {
2476 Float32 f;
2477 if (content.getFloat32(f, i).good())
2478 {
2479 values.push_back(f);
2480 }
2481 }
2482
2483 visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values);
2484 break;
2485 }
2486
2487 case EVR_FD: // float double-precision
2488 #if DCMTK_VERSION_NUMBER >= 361
2489 case EVR_OD:
2490 #endif
2491 {
2492 DcmFloatingPointDouble& content = dynamic_cast<DcmFloatingPointDouble&>(element);
2493
2494 std::vector<double> values;
2495 values.reserve(content.getVM());
2496
2497 for (unsigned long i = 0; i < content.getVM(); i++)
2498 {
2499 Float64 f;
2500 if (content.getFloat64(f, i).good())
2501 {
2502 values.push_back(f);
2503 }
2504 }
2505
2506 visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values);
2507 break;
2508 }
2509
2510
2511 /**
2512 * Attribute tag.
2513 **/
2514
2515 case EVR_AT:
2516 {
2517 DcmAttributeTag& content = dynamic_cast<DcmAttributeTag&>(element);
2518
2519 std::vector<DicomTag> values;
2520 values.reserve(content.getVM());
2521
2522 for (unsigned long i = 0; i < content.getVM(); i++)
2523 {
2524 DcmTagKey f;
2525 if (content.getTagVal(f, i).good())
2526 {
2527 DicomTag t(f.getGroup(), f.getElement());
2528 values.push_back(t);
2529 }
2530 }
2531
2532 assert(vr == ValueRepresentation_AttributeTag);
2533 visitor.VisitAttributes(parentTags, parentIndexes, tag, values);
2534 break;
2535 }
2536
2537
2538 /**
2539 * Sequence types, should never occur at this point because of
2540 * "element.isLeaf()".
2541 **/
2542
2543 case EVR_SQ: // sequence of items
2544 {
2545 return;
2546 }
2547
2548
2549 /**
2550 * Internal to DCMTK.
2551 **/
2552
2553 case EVR_xs: // SS or US depending on context
2554 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
2555 case EVR_na: // na="not applicable", for data which has no VR
2556 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
2557 case EVR_item: // used internally for items
2558 case EVR_metainfo: // used internally for meta info datasets
2559 case EVR_dataset: // used internally for datasets
2560 case EVR_fileFormat: // used internally for DICOM files
2561 case EVR_dicomDir: // used internally for DICOMDIR objects
2562 case EVR_dirRecord: // used internally for DICOMDIR records
2563 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
2564 case EVR_pixelItem: // used internally for pixel items in a compressed image
2565 case EVR_PixelData: // used internally for uncompressed pixeld data
2566 case EVR_OverlayData: // used internally for overlay data
2567 {
2568 visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr);
2569 return;
2570 }
2571
2572
2573 /**
2574 * Default case.
2575 **/
2576
2577 default:
2578 return;
2579 }
2580 }
2581 catch (boost::bad_lexical_cast&)
2582 {
2583 return;
2584 }
2585 catch (std::bad_cast&)
2586 {
2587 return;
2588 }
2589 }
2590
2591
2592 static void ApplyVisitorToElement(DcmElement& element,
2593 ITagVisitor& visitor,
2594 const std::vector<DicomTag>& parentTags,
2595 const std::vector<size_t>& parentIndexes,
2596 Encoding encoding,
2597 bool hasCodeExtensions)
2598 {
2599 assert(parentTags.size() == parentIndexes.size());
2600
2601 DicomTag tag(FromDcmtkBridge::Convert(element.getTag()));
2602
2603 if (element.isLeaf())
2604 {
2605 ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding, hasCodeExtensions);
2606 }
2607 else
2608 {
2609 // "All subclasses of DcmElement except for DcmSequenceOfItems
2610 // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
2611 // etc. are not." The following dynamic_cast is thus OK.
2612 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
2613
2614 if (sequence.card() == 0)
2615 {
2616 visitor.VisitEmptySequence(parentTags, parentIndexes, tag);
2617 }
2618 else
2619 {
2620 std::vector<DicomTag> tags = parentTags;
2621 std::vector<size_t> indexes = parentIndexes;
2622 tags.push_back(tag);
2623 indexes.push_back(0);
2624
2625 for (unsigned long i = 0; i < sequence.card(); i++)
2626 {
2627 indexes.back() = static_cast<size_t>(i);
2628 DcmItem* child = sequence.getItem(i);
2629 ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding, hasCodeExtensions);
2630 }
2631 }
2632 }
2633 }
2634
2635
2636 void FromDcmtkBridge::Apply(DcmItem& dataset,
2637 ITagVisitor& visitor,
2638 Encoding defaultEncoding)
2639 {
2640 std::vector<DicomTag> parentTags;
2641 std::vector<size_t> parentIndexes;
2642 bool hasCodeExtensions;
2643 Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
2644 ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions);
2645 }
2646
2647
2648
2649 bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target,
2650 DcmFileFormat& dicom)
2651 {
2652 if (dicom.getDataset() == NULL)
2653 {
2654 throw OrthancException(ErrorCode_InternalError);
2655 }
2656
2657 DcmDataset& dataset = *dicom.getDataset();
2658
2659 E_TransferSyntax xfer = dataset.getCurrentXfer();
2660 if (xfer == EXS_Unknown)
2661 {
2662 dataset.updateOriginalXfer();
2663 xfer = dataset.getOriginalXfer();
2664 if (xfer == EXS_Unknown)
2665 {
2666 throw OrthancException(ErrorCode_BadFileFormat,
2667 "Cannot determine the transfer syntax of the DICOM instance");
2668 }
2669 }
2670
2671 return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer);
2672 }
2673 }
2674
2675
2676 #include "./FromDcmtkBridge_TransferSyntaxes.impl.h"