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