comparison Core/DicomParsing/FromDcmtkBridge.cpp @ 2382:7284093111b0

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