comparison Core/DicomParsing/DicomWebJsonVisitor.cpp @ 3202:ef4d86d05503

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 06 Feb 2019 15:21:32 +0100
parents
children 810772486249
comparison
equal deleted inserted replaced
3201:b69fe409cb4d 3202:ef4d86d05503
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-2019 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../PrecompiledHeaders.h"
35 #include "DicomWebJsonVisitor.h"
36
37 #include "../OrthancException.h"
38 #include "../Toolbox.h"
39 #include "FromDcmtkBridge.h"
40
41 #include <boost/math/special_functions/round.hpp>
42 #include <boost/lexical_cast.hpp>
43
44
45 static const char* const KEY_ALPHABETIC = "Alphabetic";
46 static const char* const KEY_BULK_DATA_URI = "BulkDataURI";
47 static const char* const KEY_INLINE_BINARY = "InlineBinary";
48 static const char* const KEY_SQ = "SQ";
49 static const char* const KEY_VALUE = "Value";
50 static const char* const KEY_VR = "vr";
51
52
53 namespace Orthanc
54 {
55 #if ORTHANC_ENABLE_PUGIXML == 1
56 static void ExploreXmlDataset(pugi::xml_node& target,
57 const Json::Value& source)
58 {
59 assert(source.type() == Json::objectValue);
60
61 Json::Value::Members members = source.getMemberNames();
62 for (size_t i = 0; i < members.size(); i++)
63 {
64 const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]);
65 const Json::Value& content = source[members[i]];
66
67 assert(content.type() == Json::objectValue &&
68 content.isMember("vr") &&
69 content["vr"].type() == Json::stringValue);
70 const std::string vr = content["vr"].asString();
71
72 const std::string keyword = FromDcmtkBridge::GetTagName(tag, "");
73
74 pugi::xml_node node = target.append_child("DicomAttribute");
75 node.append_attribute("tag").set_value(members[i].c_str());
76 node.append_attribute("vr").set_value(vr.c_str());
77
78 if (keyword != std::string(DcmTag_ERROR_TagName))
79 {
80 node.append_attribute("keyword").set_value(keyword.c_str());
81 }
82
83 if (content.isMember(KEY_VALUE))
84 {
85 assert(content[KEY_VALUE].type() == Json::arrayValue);
86
87 for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++)
88 {
89 std::string number = boost::lexical_cast<std::string>(j + 1);
90
91 if (vr == "SQ")
92 {
93 if (content[KEY_VALUE][j].type() == Json::objectValue)
94 {
95 pugi::xml_node child = node.append_child("Item");
96 child.append_attribute("number").set_value(number.c_str());
97 ExploreXmlDataset(child, content[KEY_VALUE][j]);
98 }
99 }
100 if (vr == "PN")
101 {
102 if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) &&
103 content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue)
104 {
105 std::vector<std::string> tokens;
106 Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^');
107
108 pugi::xml_node child = node.append_child("PersonName");
109 child.append_attribute("number").set_value(number.c_str());
110
111 pugi::xml_node name = child.append_child(KEY_ALPHABETIC);
112
113 if (tokens.size() >= 1)
114 {
115 name.append_child("FamilyName").text() = tokens[0].c_str();
116 }
117
118 if (tokens.size() >= 2)
119 {
120 name.append_child("GivenName").text() = tokens[1].c_str();
121 }
122
123 if (tokens.size() >= 3)
124 {
125 name.append_child("MiddleName").text() = tokens[2].c_str();
126 }
127
128 if (tokens.size() >= 4)
129 {
130 name.append_child("NamePrefix").text() = tokens[3].c_str();
131 }
132
133 if (tokens.size() >= 5)
134 {
135 name.append_child("NameSuffix").text() = tokens[4].c_str();
136 }
137 }
138 }
139 else
140 {
141 pugi::xml_node child = node.append_child("Value");
142 child.append_attribute("number").set_value(number.c_str());
143
144 switch (content[KEY_VALUE][j].type())
145 {
146 case Json::stringValue:
147 child.text() = content[KEY_VALUE][j].asCString();
148 break;
149
150 case Json::realValue:
151 child.text() = content[KEY_VALUE][j].asFloat();
152 break;
153
154 case Json::intValue:
155 child.text() = content[KEY_VALUE][j].asInt();
156 break;
157
158 case Json::uintValue:
159 child.text() = content[KEY_VALUE][j].asUInt();
160 break;
161
162 default:
163 break;
164 }
165 }
166 }
167 }
168 else if (content.isMember(KEY_BULK_DATA_URI) &&
169 content[KEY_BULK_DATA_URI].type() == Json::stringValue)
170 {
171 pugi::xml_node child = node.append_child("BulkData");
172 child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString());
173 }
174 else if (content.isMember(KEY_INLINE_BINARY) &&
175 content[KEY_INLINE_BINARY].type() == Json::stringValue)
176 {
177 pugi::xml_node child = node.append_child("InlineBinary");
178 child.text() = content[KEY_INLINE_BINARY].asCString();
179 }
180 }
181 }
182 #endif
183
184
185 #if ORTHANC_ENABLE_PUGIXML == 1
186 static void DicomWebJsonToXml(pugi::xml_document& target,
187 const Json::Value& source)
188 {
189 pugi::xml_node root = target.append_child("NativeDicomModel");
190 root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
191 root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
192 root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance");
193
194 ExploreXmlDataset(root, source);
195
196 pugi::xml_node decl = target.prepend_child(pugi::node_declaration);
197 decl.append_attribute("version").set_value("1.0");
198 decl.append_attribute("encoding").set_value("utf-8");
199 }
200 #endif
201
202
203 std::string DicomWebJsonVisitor::FormatTag(const DicomTag& tag)
204 {
205 char buf[16];
206 sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement());
207 return std::string(buf);
208 }
209
210
211 Json::Value& DicomWebJsonVisitor::CreateNode(const std::vector<DicomTag>& parentTags,
212 const std::vector<size_t>& parentIndexes,
213 const DicomTag& tag)
214 {
215 assert(parentTags.size() == parentIndexes.size());
216
217 Json::Value* node = &result_;
218
219 for (size_t i = 0; i < parentTags.size(); i++)
220 {
221 std::string t = FormatTag(parentTags[i]);
222
223 if (!node->isMember(t))
224 {
225 Json::Value item = Json::objectValue;
226 item[KEY_VR] = KEY_SQ;
227 item[KEY_VALUE] = Json::arrayValue;
228 item[KEY_VALUE].append(Json::objectValue);
229 (*node) [t] = item;
230
231 node = &(*node)[t][KEY_VALUE][0];
232 }
233 else if ((*node) [t].type() != Json::objectValue ||
234 !(*node) [t].isMember(KEY_VR) ||
235 (*node) [t][KEY_VR].type() != Json::stringValue ||
236 (*node) [t][KEY_VR].asString() != KEY_SQ ||
237 !(*node) [t].isMember(KEY_VALUE) ||
238 (*node) [t][KEY_VALUE].type() != Json::arrayValue)
239 {
240 throw OrthancException(ErrorCode_InternalError);
241 }
242 else
243 {
244 size_t currentSize = (*node) [t][KEY_VALUE].size();
245
246 if (parentIndexes[i] < currentSize)
247 {
248 // The node already exists
249 }
250 else if (parentIndexes[i] == currentSize)
251 {
252 (*node) [t][KEY_VALUE].append(Json::objectValue);
253 }
254 else
255 {
256 throw OrthancException(ErrorCode_InternalError);
257 }
258
259 node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])];
260 }
261 }
262
263 assert(node->type() == Json::objectValue);
264
265 std::string t = FormatTag(tag);
266 if (node->isMember(t))
267 {
268 throw OrthancException(ErrorCode_InternalError);
269 }
270 else
271 {
272 (*node) [t] = Json::objectValue;
273 return (*node) [t];
274 }
275 }
276
277
278 Json::Value DicomWebJsonVisitor::FormatInteger(int64_t value)
279 {
280 if (value < 0)
281 {
282 return Json::Value(static_cast<int32_t>(value));
283 }
284 else
285 {
286 return Json::Value(static_cast<uint32_t>(value));
287 }
288 }
289
290
291 Json::Value DicomWebJsonVisitor::FormatDouble(double value)
292 {
293 long long a = boost::math::llround<double>(value);
294
295 double d = fabs(value - static_cast<double>(a));
296
297 if (d <= std::numeric_limits<double>::epsilon() * 100.0)
298 {
299 return FormatInteger(a);
300 }
301 else
302 {
303 return Json::Value(value);
304 }
305 }
306
307
308 #if ORTHANC_ENABLE_PUGIXML == 1
309 void DicomWebJsonVisitor::FormatXml(pugi::xml_document& target) const
310 {
311 DicomWebJsonToXml(target, result_);
312 }
313 #endif
314
315
316 void DicomWebJsonVisitor::VisitEmptySequence(const std::vector<DicomTag>& parentTags,
317 const std::vector<size_t>& parentIndexes,
318 const DicomTag& tag)
319 {
320 if (tag.GetElement() != 0x0000)
321 {
322 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
323 node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence);
324 }
325 }
326
327
328 void DicomWebJsonVisitor::VisitBinary(const std::vector<DicomTag>& parentTags,
329 const std::vector<size_t>& parentIndexes,
330 const DicomTag& tag,
331 ValueRepresentation vr,
332 const void* data,
333 size_t size)
334 {
335 assert(vr == ValueRepresentation_OtherByte ||
336 vr == ValueRepresentation_OtherDouble ||
337 vr == ValueRepresentation_OtherFloat ||
338 vr == ValueRepresentation_OtherLong ||
339 vr == ValueRepresentation_OtherWord ||
340 vr == ValueRepresentation_Unknown);
341
342 if (tag.GetElement() != 0x0000)
343 {
344 BinaryMode mode;
345 std::string bulkDataUri;
346
347 if (formatter_ == NULL)
348 {
349 mode = BinaryMode_InlineBinary;
350 }
351 else
352 {
353 mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr);
354 }
355
356 if (mode != BinaryMode_Ignore)
357 {
358 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
359 node[KEY_VR] = EnumerationToString(vr);
360
361 switch (mode)
362 {
363 case BinaryMode_BulkDataUri:
364 node[KEY_BULK_DATA_URI] = bulkDataUri;
365 break;
366
367 case BinaryMode_InlineBinary:
368 {
369 std::string tmp(static_cast<const char*>(data), size);
370
371 std::string base64;
372 Toolbox::EncodeBase64(base64, tmp);
373
374 node[KEY_INLINE_BINARY] = base64;
375 break;
376 }
377
378 default:
379 throw OrthancException(ErrorCode_ParameterOutOfRange);
380 }
381 }
382 }
383 }
384
385
386 void DicomWebJsonVisitor::VisitIntegers(const std::vector<DicomTag>& parentTags,
387 const std::vector<size_t>& parentIndexes,
388 const DicomTag& tag,
389 ValueRepresentation vr,
390 const std::vector<int64_t>& values)
391 {
392 if (tag.GetElement() != 0x0000 &&
393 vr != ValueRepresentation_NotSupported)
394 {
395 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
396 node[KEY_VR] = EnumerationToString(vr);
397
398 if (!values.empty())
399 {
400 Json::Value content = Json::arrayValue;
401 for (size_t i = 0; i < values.size(); i++)
402 {
403 content.append(FormatInteger(values[i]));
404 }
405
406 node[KEY_VALUE] = content;
407 }
408 }
409 }
410
411 void DicomWebJsonVisitor::VisitDoubles(const std::vector<DicomTag>& parentTags,
412 const std::vector<size_t>& parentIndexes,
413 const DicomTag& tag,
414 ValueRepresentation vr,
415 const std::vector<double>& values)
416 {
417 if (tag.GetElement() != 0x0000 &&
418 vr != ValueRepresentation_NotSupported)
419 {
420 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
421 node[KEY_VR] = EnumerationToString(vr);
422
423 if (!values.empty())
424 {
425 Json::Value content = Json::arrayValue;
426 for (size_t i = 0; i < values.size(); i++)
427 {
428 content.append(FormatDouble(values[i]));
429 }
430
431 node[KEY_VALUE] = content;
432 }
433 }
434 }
435
436
437 void DicomWebJsonVisitor::VisitAttributes(const std::vector<DicomTag>& parentTags,
438 const std::vector<size_t>& parentIndexes,
439 const DicomTag& tag,
440 const std::vector<DicomTag>& values)
441 {
442 if (tag.GetElement() != 0x0000)
443 {
444 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
445 node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag);
446
447 if (!values.empty())
448 {
449 Json::Value content = Json::arrayValue;
450 for (size_t i = 0; i < values.size(); i++)
451 {
452 content.append(FormatTag(values[i]));
453 }
454
455 node[KEY_VALUE] = content;
456 }
457 }
458 }
459
460
461 ITagVisitor::Action
462 DicomWebJsonVisitor::VisitString(std::string& newValue,
463 const std::vector<DicomTag>& parentTags,
464 const std::vector<size_t>& parentIndexes,
465 const DicomTag& tag,
466 ValueRepresentation vr,
467 const std::string& value)
468 {
469 if (tag.GetElement() == 0x0000 ||
470 vr == ValueRepresentation_NotSupported)
471 {
472 return Action_None;
473 }
474 else
475 {
476 Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
477 node[KEY_VR] = EnumerationToString(vr);
478
479 if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
480 {
481 // TODO - The JSON file has an UTF-8 encoding, thus DCMTK
482 // replaces the specific character set with "ISO_IR 192"
483 // (UNICODE UTF-8). It is unclear whether the source
484 // character set should be kept: We thus mimic DCMTK.
485 node[KEY_VALUE].append("ISO_IR 192");
486 }
487 else
488 {
489 std::string truncated;
490
491 if (!value.empty() &&
492 value[value.size() - 1] == '\0')
493 {
494 truncated = value.substr(0, value.size() - 1);
495 }
496 else
497 {
498 truncated = value;
499 }
500
501 if (!truncated.empty())
502 {
503 std::vector<std::string> tokens;
504 Toolbox::TokenizeString(tokens, truncated, '\\');
505
506 node[KEY_VALUE] = Json::arrayValue;
507 for (size_t i = 0; i < tokens.size(); i++)
508 {
509 try
510 {
511 switch (vr)
512 {
513 case ValueRepresentation_PersonName:
514 {
515 Json::Value value = Json::objectValue;
516 if (!tokens[i].empty())
517 {
518 value[KEY_ALPHABETIC] = tokens[i];
519 }
520 node[KEY_VALUE].append(value);
521 break;
522 }
523
524 case ValueRepresentation_IntegerString:
525 if (tokens[i].empty())
526 {
527 node[KEY_VALUE].append(Json::nullValue);
528 }
529 else
530 {
531 int64_t value = boost::lexical_cast<int64_t>(tokens[i]);
532 node[KEY_VALUE].append(FormatInteger(value));
533 }
534
535 break;
536
537 case ValueRepresentation_DecimalString:
538 if (tokens[i].empty())
539 {
540 node[KEY_VALUE].append(Json::nullValue);
541 }
542 else
543 {
544 double value = boost::lexical_cast<double>(tokens[i]);
545 node[KEY_VALUE].append(FormatDouble(value));
546 }
547 break;
548
549 default:
550 if (tokens[i].empty())
551 {
552 node[KEY_VALUE].append(Json::nullValue);
553 }
554 else
555 {
556 node[KEY_VALUE].append(tokens[i]);
557 }
558
559 break;
560 }
561 }
562 catch (boost::bad_lexical_cast&)
563 {
564 throw OrthancException(ErrorCode_BadFileFormat);
565 }
566 }
567 }
568 }
569 }
570
571 return Action_None;
572 }
573 }