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

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/DicomParsing/ParsedDicomFile.cpp@fa35465175b8
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34
35 /*=========================================================================
36
37 This file is based on portions of the following project:
38
39 Program: GDCM (Grassroots DICOM). A DICOM library
40 Module: http://gdcm.sourceforge.net/Copyright.html
41
42 Copyright (c) 2006-2011 Mathieu Malaterre
43 Copyright (c) 1993-2005 CREATIS
44 (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image)
45 All rights reserved.
46
47 Redistribution and use in source and binary forms, with or without
48 modification, are permitted provided that the following conditions are met:
49
50 * Redistributions of source code must retain the above copyright notice,
51 this list of conditions and the following disclaimer.
52
53 * Redistributions in binary form must reproduce the above copyright notice,
54 this list of conditions and the following disclaimer in the documentation
55 and/or other materials provided with the distribution.
56
57 * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any
58 contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to
59 endorse or promote products derived from this software without specific
60 prior written permission.
61
62 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
63 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
64 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
65 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
66 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
67 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
68 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
69 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
70 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
71 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
72
73 =========================================================================*/
74
75
76 #include "../PrecompiledHeaders.h"
77
78 #ifndef NOMINMAX
79 #define NOMINMAX
80 #endif
81
82 #include "ParsedDicomFile.h"
83
84 #include "FromDcmtkBridge.h"
85 #include "Internals/DicomFrameIndex.h"
86 #include "ToDcmtkBridge.h"
87
88 #include "../Images/PamReader.h"
89 #include "../Logging.h"
90 #include "../OrthancException.h"
91 #include "../Toolbox.h"
92
93 #if ORTHANC_SANDBOXED == 0
94 # include "../SystemToolbox.h"
95 #endif
96
97 #if ORTHANC_ENABLE_JPEG == 1
98 # include "../Images/JpegReader.h"
99 #endif
100
101 #if ORTHANC_ENABLE_PNG == 1
102 # include "../Images/PngReader.h"
103 #endif
104
105 #include <list>
106 #include <limits>
107
108 #include <boost/lexical_cast.hpp>
109
110 #include <dcmtk/dcmdata/dcchrstr.h>
111 #include <dcmtk/dcmdata/dcdicent.h>
112 #include <dcmtk/dcmdata/dcdict.h>
113 #include <dcmtk/dcmdata/dcfilefo.h>
114 #include <dcmtk/dcmdata/dcuid.h>
115 #include <dcmtk/dcmdata/dcmetinf.h>
116 #include <dcmtk/dcmdata/dcdeftag.h>
117
118 #include <dcmtk/dcmdata/dcvrae.h>
119 #include <dcmtk/dcmdata/dcvras.h>
120 #include <dcmtk/dcmdata/dcvrcs.h>
121 #include <dcmtk/dcmdata/dcvrda.h>
122 #include <dcmtk/dcmdata/dcvrds.h>
123 #include <dcmtk/dcmdata/dcvrdt.h>
124 #include <dcmtk/dcmdata/dcvrfd.h>
125 #include <dcmtk/dcmdata/dcvrfl.h>
126 #include <dcmtk/dcmdata/dcvris.h>
127 #include <dcmtk/dcmdata/dcvrlo.h>
128 #include <dcmtk/dcmdata/dcvrlt.h>
129 #include <dcmtk/dcmdata/dcvrpn.h>
130 #include <dcmtk/dcmdata/dcvrsh.h>
131 #include <dcmtk/dcmdata/dcvrsl.h>
132 #include <dcmtk/dcmdata/dcvrss.h>
133 #include <dcmtk/dcmdata/dcvrst.h>
134 #include <dcmtk/dcmdata/dcvrtm.h>
135 #include <dcmtk/dcmdata/dcvrui.h>
136 #include <dcmtk/dcmdata/dcvrul.h>
137 #include <dcmtk/dcmdata/dcvrus.h>
138 #include <dcmtk/dcmdata/dcvrut.h>
139 #include <dcmtk/dcmdata/dcpixel.h>
140 #include <dcmtk/dcmdata/dcpixseq.h>
141 #include <dcmtk/dcmdata/dcpxitem.h>
142
143
144 #include <boost/math/special_functions/round.hpp>
145 #include <dcmtk/dcmdata/dcostrmb.h>
146 #include <boost/algorithm/string/predicate.hpp>
147
148
149 #if DCMTK_VERSION_NUMBER <= 360
150 # define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax
151 #endif
152
153
154
155 namespace Orthanc
156 {
157 struct ParsedDicomFile::PImpl
158 {
159 std::unique_ptr<DcmFileFormat> file_;
160 std::unique_ptr<DicomFrameIndex> frameIndex_;
161 };
162
163
164 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
165 static void ParseTagAndGroup(DcmTagKey& key,
166 const std::string& tag)
167 {
168 DicomTag t = FromDcmtkBridge::ParseTag(tag);
169 key = DcmTagKey(t.GetGroup(), t.GetElement());
170 }
171
172
173 static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
174 E_TransferSyntax transferSyntax)
175 {
176 DcmPixelSequence* pixelSequence = NULL;
177 if (pixelData.getEncapsulatedRepresentation
178 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
179 {
180 return pixelSequence->card();
181 }
182 else
183 {
184 return 1;
185 }
186 }
187
188
189 static void SendPathValueForDictionary(RestApiOutput& output,
190 DcmItem& dicom)
191 {
192 Json::Value v = Json::arrayValue;
193
194 for (unsigned long i = 0; i < dicom.card(); i++)
195 {
196 DcmElement* element = dicom.getElement(i);
197 if (element)
198 {
199 char buf[16];
200 sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
201 v.append(buf);
202 }
203 }
204
205 output.AnswerJson(v);
206 }
207
208
209 static void SendSequence(RestApiOutput& output,
210 DcmSequenceOfItems& sequence)
211 {
212 // This element is a sequence
213 Json::Value v = Json::arrayValue;
214
215 for (unsigned long i = 0; i < sequence.card(); i++)
216 {
217 v.append(boost::lexical_cast<std::string>(i));
218 }
219
220 output.AnswerJson(v);
221 }
222
223
224 namespace
225 {
226 class DicomFieldStream : public IHttpStreamAnswer
227 {
228 private:
229 DcmElement& element_;
230 uint32_t length_;
231 uint32_t offset_;
232 std::string chunk_;
233 size_t chunkSize_;
234
235 public:
236 DicomFieldStream(DcmElement& element,
237 E_TransferSyntax transferSyntax) :
238 element_(element),
239 length_(element.getLength(transferSyntax)),
240 offset_(0),
241 chunkSize_(0)
242 {
243 static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB
244 chunk_.resize(CHUNK_SIZE);
245 }
246
247 virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/,
248 bool /*deflateAllowed*/)
249 ORTHANC_OVERRIDE
250 {
251 // No support for compression
252 return HttpCompression_None;
253 }
254
255 virtual bool HasContentFilename(std::string& filename) ORTHANC_OVERRIDE
256 {
257 return false;
258 }
259
260 virtual std::string GetContentType() ORTHANC_OVERRIDE
261 {
262 return EnumerationToString(MimeType_Binary);
263 }
264
265 virtual uint64_t GetContentLength() ORTHANC_OVERRIDE
266 {
267 return length_;
268 }
269
270 virtual bool ReadNextChunk() ORTHANC_OVERRIDE
271 {
272 assert(offset_ <= length_);
273
274 if (offset_ == length_)
275 {
276 return false;
277 }
278 else
279 {
280 if (length_ - offset_ < chunk_.size())
281 {
282 chunkSize_ = length_ - offset_;
283 }
284 else
285 {
286 chunkSize_ = chunk_.size();
287 }
288
289 OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_);
290
291 offset_ += chunkSize_;
292
293 if (!cond.good())
294 {
295 throw OrthancException(ErrorCode_InternalError,
296 "Error while sending a DICOM field: " +
297 std::string(cond.text()));
298 }
299
300 return true;
301 }
302 }
303
304 virtual const char *GetChunkContent() ORTHANC_OVERRIDE
305 {
306 return chunk_.c_str();
307 }
308
309 virtual size_t GetChunkSize() ORTHANC_OVERRIDE
310 {
311 return chunkSize_;
312 }
313 };
314 }
315
316
317 static bool AnswerPixelData(RestApiOutput& output,
318 DcmItem& dicom,
319 E_TransferSyntax transferSyntax,
320 const std::string* blockUri)
321 {
322 DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
323 DICOM_TAG_PIXEL_DATA.GetElement());
324
325 DcmElement *element = NULL;
326 if (!dicom.findAndGetElement(k, element).good() ||
327 element == NULL)
328 {
329 return false;
330 }
331
332 try
333 {
334 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
335 if (blockUri == NULL)
336 {
337 // The user asks how many blocks are present in this pixel data
338 unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
339
340 Json::Value result(Json::arrayValue);
341 for (unsigned int i = 0; i < blocks; i++)
342 {
343 result.append(boost::lexical_cast<std::string>(i));
344 }
345
346 output.AnswerJson(result);
347 return true;
348 }
349
350
351 unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
352
353 if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
354 {
355 DcmPixelSequence* pixelSequence = NULL;
356 if (pixelData.getEncapsulatedRepresentation
357 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
358 {
359 // This is the case for JPEG transfer syntaxes
360 if (block < pixelSequence->card())
361 {
362 DcmPixelItem* pixelItem = NULL;
363 if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
364 {
365 if (pixelItem->getLength() == 0)
366 {
367 output.AnswerBuffer(NULL, 0, MimeType_Binary);
368 return true;
369 }
370
371 Uint8* buffer = NULL;
372 if (pixelItem->getUint8Array(buffer).good() && buffer)
373 {
374 output.AnswerBuffer(buffer, pixelItem->getLength(), MimeType_Binary);
375 return true;
376 }
377 }
378 }
379 }
380 else
381 {
382 // This is the case for raw, uncompressed image buffers
383 assert(*blockUri == "0");
384 DicomFieldStream stream(*element, transferSyntax);
385 output.AnswerStream(stream);
386 }
387 }
388 }
389 catch (boost::bad_lexical_cast&)
390 {
391 // The URI entered by the user is not a number
392 }
393 catch (std::bad_cast&)
394 {
395 // This should never happen
396 }
397
398 return false;
399 }
400
401
402 static void SendPathValueForLeaf(RestApiOutput& output,
403 const std::string& tag,
404 DcmItem& dicom,
405 E_TransferSyntax transferSyntax)
406 {
407 DcmTagKey k;
408 ParseTagAndGroup(k, tag);
409
410 DcmSequenceOfItems* sequence = NULL;
411 if (dicom.findAndGetSequence(k, sequence).good() &&
412 sequence != NULL &&
413 sequence->getVR() == EVR_SQ)
414 {
415 SendSequence(output, *sequence);
416 return;
417 }
418
419 DcmElement* element = NULL;
420 if (dicom.findAndGetElement(k, element).good() &&
421 element != NULL &&
422 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags
423 element->getVR() != EVR_SQ)
424 {
425 DicomFieldStream stream(*element, transferSyntax);
426 output.AnswerStream(stream);
427 }
428 }
429 #endif
430
431
432 static inline uint16_t GetCharValue(char c)
433 {
434 if (c >= '0' && c <= '9')
435 return c - '0';
436 else if (c >= 'a' && c <= 'f')
437 return c - 'a' + 10;
438 else if (c >= 'A' && c <= 'F')
439 return c - 'A' + 10;
440 else
441 return 0;
442 }
443
444
445 static inline uint16_t GetTagValue(const char* c)
446 {
447 return ((GetCharValue(c[0]) << 12) +
448 (GetCharValue(c[1]) << 8) +
449 (GetCharValue(c[2]) << 4) +
450 GetCharValue(c[3]));
451 }
452
453
454 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
455 void ParsedDicomFile::SendPathValue(RestApiOutput& output,
456 const UriComponents& uri)
457 {
458 DcmItem* dicom = GetDcmtkObject().getDataset();
459 E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer();
460
461 // Special case: Accessing the pixel data
462 if (uri.size() == 1 ||
463 uri.size() == 2)
464 {
465 DcmTagKey tag;
466 ParseTagAndGroup(tag, uri[0]);
467
468 if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
469 tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
470 {
471 AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
472 return;
473 }
474 }
475
476 // Go down in the tag hierarchy according to the URI
477 for (size_t pos = 0; pos < uri.size() / 2; pos++)
478 {
479 size_t index;
480 try
481 {
482 index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
483 }
484 catch (boost::bad_lexical_cast&)
485 {
486 return;
487 }
488
489 DcmTagKey k;
490 DcmItem *child = NULL;
491 ParseTagAndGroup(k, uri[2 * pos]);
492 if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
493 child == NULL)
494 {
495 return;
496 }
497
498 dicom = child;
499 }
500
501 // We have reached the end of the URI
502 if (uri.size() % 2 == 0)
503 {
504 SendPathValueForDictionary(output, *dicom);
505 }
506 else
507 {
508 SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
509 }
510 }
511 #endif
512
513
514 void ParsedDicomFile::Remove(const DicomTag& tag)
515 {
516 InvalidateCache();
517
518 DcmTagKey key(tag.GetGroup(), tag.GetElement());
519 DcmElement* element = GetDcmtkObject().getDataset()->remove(key);
520 if (element != NULL)
521 {
522 delete element;
523 }
524 }
525
526
527 void ParsedDicomFile::Clear(const DicomTag& tag,
528 bool onlyIfExists)
529 {
530 if (tag.GetElement() == 0x0000)
531 {
532 // Prevent manually modifying generic group length tags: This is
533 // handled by DCMTK serialization
534 return;
535 }
536
537 InvalidateCache();
538
539 DcmItem* dicom = GetDcmtkObject().getDataset();
540 DcmTagKey key(tag.GetGroup(), tag.GetElement());
541
542 if (onlyIfExists &&
543 !dicom->tagExists(key))
544 {
545 // The tag is non-existing, do not clear it
546 }
547 else
548 {
549 if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good())
550 {
551 throw OrthancException(ErrorCode_InternalError);
552 }
553 }
554 }
555
556
557 void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep)
558 {
559 InvalidateCache();
560
561 DcmDataset& dataset = *GetDcmtkObject().getDataset();
562
563 // Loop over the dataset to detect its private tags
564 typedef std::list<DcmElement*> Tags;
565 Tags privateTags;
566
567 for (unsigned long i = 0; i < dataset.card(); i++)
568 {
569 DcmElement* element = dataset.getElement(i);
570 DcmTag tag(element->getTag());
571
572 // Is this a private tag?
573 if (tag.isPrivate())
574 {
575 bool remove = true;
576
577 // Check whether this private tag is to be kept
578 if (toKeep != NULL)
579 {
580 DicomTag tmp = FromDcmtkBridge::Convert(tag);
581 if (toKeep->find(tmp) != toKeep->end())
582 {
583 remove = false; // Keep it
584 }
585 }
586
587 if (remove)
588 {
589 privateTags.push_back(element);
590 }
591 }
592 }
593
594 // Loop over the detected private tags to remove them
595 for (Tags::iterator it = privateTags.begin();
596 it != privateTags.end(); ++it)
597 {
598 DcmElement* tmp = dataset.remove(*it);
599 if (tmp != NULL)
600 {
601 delete tmp;
602 }
603 }
604 }
605
606
607 static void InsertInternal(DcmDataset& dicom,
608 DcmElement* element)
609 {
610 OFCondition cond = dicom.insert(element, false, false);
611 if (!cond.good())
612 {
613 // This field already exists
614 delete element;
615 throw OrthancException(ErrorCode_InternalError);
616 }
617 }
618
619
620 void ParsedDicomFile::Insert(const DicomTag& tag,
621 const Json::Value& value,
622 bool decodeDataUriScheme,
623 const std::string& privateCreator)
624 {
625 if (tag.GetElement() == 0x0000)
626 {
627 // Prevent manually modifying generic group length tags: This is
628 // handled by DCMTK serialization
629 return;
630 }
631
632 if (GetDcmtkObject().getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
633 {
634 throw OrthancException(ErrorCode_AlreadyExistingTag);
635 }
636
637 if (decodeDataUriScheme &&
638 value.type() == Json::stringValue &&
639 (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
640 tag == DICOM_TAG_PIXEL_DATA))
641 {
642 if (EmbedContentInternal(value.asString()))
643 {
644 return;
645 }
646 }
647
648 InvalidateCache();
649
650 bool hasCodeExtensions;
651 Encoding encoding = DetectEncoding(hasCodeExtensions);
652 std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
653 InsertInternal(*GetDcmtkObject().getDataset(), element.release());
654 }
655
656
657 void ParsedDicomFile::ReplacePlainString(const DicomTag& tag,
658 const std::string& utf8Value)
659 {
660 if (tag.IsPrivate())
661 {
662 throw OrthancException(ErrorCode_InternalError,
663 "Cannot apply this function to private tags: " + tag.Format());
664 }
665 else
666 {
667 Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent,
668 "" /* not a private tag, so no private creator */);
669 }
670 }
671
672
673 void ParsedDicomFile::SetIfAbsent(const DicomTag& tag,
674 const std::string& utf8Value)
675 {
676 std::string currentValue;
677 if (!GetTagValue(currentValue, tag))
678 {
679 ReplacePlainString(tag, utf8Value);
680 }
681 }
682
683
684 static bool CanReplaceProceed(DcmDataset& dicom,
685 const DcmTagKey& tag,
686 DicomReplaceMode mode)
687 {
688 if (dicom.findAndDeleteElement(tag).good())
689 {
690 // This tag was existing, it has been deleted
691 return true;
692 }
693 else
694 {
695 // This tag was absent, act wrt. the specified "mode"
696 switch (mode)
697 {
698 case DicomReplaceMode_InsertIfAbsent:
699 return true;
700
701 case DicomReplaceMode_ThrowIfAbsent:
702 throw OrthancException(ErrorCode_InexistentItem);
703
704 case DicomReplaceMode_IgnoreIfAbsent:
705 return false;
706
707 default:
708 throw OrthancException(ErrorCode_ParameterOutOfRange);
709 }
710 }
711 }
712
713
714 void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
715 const std::string& utf8Value,
716 bool decodeDataUriScheme)
717 {
718 if (tag != DICOM_TAG_SOP_CLASS_UID &&
719 tag != DICOM_TAG_SOP_INSTANCE_UID)
720 {
721 return;
722 }
723
724 std::string binary;
725 const std::string* decoded = &utf8Value;
726
727 if (decodeDataUriScheme &&
728 boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY))
729 {
730 std::string mime;
731 if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
732 {
733 throw OrthancException(ErrorCode_BadFileFormat);
734 }
735
736 decoded = &binary;
737 }
738 else
739 {
740 bool hasCodeExtensions;
741 Encoding encoding = DetectEncoding(hasCodeExtensions);
742 if (encoding != Encoding_Utf8)
743 {
744 binary = Toolbox::ConvertFromUtf8(utf8Value, encoding);
745 decoded = &binary;
746 }
747 }
748
749 /**
750 * dcmodify will automatically correct 'Media Storage SOP Class
751 * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
752 * you make changes to the related tags in the dataset ('SOP Class
753 * UID' and 'SOP Instance UID') via insert or modify mode
754 * options. You can disable this behaviour by using the -nmu
755 * option.
756 **/
757
758 if (tag == DICOM_TAG_SOP_CLASS_UID)
759 {
760 ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded);
761 }
762
763 if (tag == DICOM_TAG_SOP_INSTANCE_UID)
764 {
765 ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded);
766 }
767 }
768
769
770 void ParsedDicomFile::Replace(const DicomTag& tag,
771 const std::string& utf8Value,
772 bool decodeDataUriScheme,
773 DicomReplaceMode mode,
774 const std::string& privateCreator)
775 {
776 if (tag.GetElement() == 0x0000)
777 {
778 // Prevent manually modifying generic group length tags: This is
779 // handled by DCMTK serialization
780 return;
781 }
782
783 InvalidateCache();
784
785 DcmDataset& dicom = *GetDcmtkObject().getDataset();
786 if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
787 {
788 // Either the tag was previously existing (and now removed), or
789 // the replace mode was set to "InsertIfAbsent"
790
791 if (decodeDataUriScheme &&
792 (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
793 tag == DICOM_TAG_PIXEL_DATA))
794 {
795 if (EmbedContentInternal(utf8Value))
796 {
797 return;
798 }
799 }
800
801 std::unique_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator));
802
803 if (!utf8Value.empty())
804 {
805 bool hasCodeExtensions;
806 Encoding encoding = DetectEncoding(hasCodeExtensions);
807 FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding);
808 }
809
810 InsertInternal(dicom, element.release());
811 UpdateStorageUid(tag, utf8Value, false);
812 }
813 }
814
815
816 void ParsedDicomFile::Replace(const DicomTag& tag,
817 const Json::Value& value,
818 bool decodeDataUriScheme,
819 DicomReplaceMode mode,
820 const std::string& privateCreator)
821 {
822 if (tag.GetElement() == 0x0000)
823 {
824 // Prevent manually modifying generic group length tags: This is
825 // handled by DCMTK serialization
826 return;
827 }
828
829 InvalidateCache();
830
831 DcmDataset& dicom = *GetDcmtkObject().getDataset();
832 if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
833 {
834 // Either the tag was previously existing (and now removed), or
835 // the replace mode was set to "InsertIfAbsent"
836
837 if (decodeDataUriScheme &&
838 value.type() == Json::stringValue &&
839 (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
840 tag == DICOM_TAG_PIXEL_DATA))
841 {
842 if (EmbedContentInternal(value.asString()))
843 {
844 return;
845 }
846 }
847
848 bool hasCodeExtensions;
849 Encoding encoding = DetectEncoding(hasCodeExtensions);
850 InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
851
852 if (tag == DICOM_TAG_SOP_CLASS_UID ||
853 tag == DICOM_TAG_SOP_INSTANCE_UID)
854 {
855 if (value.type() != Json::stringValue)
856 {
857 throw OrthancException(ErrorCode_BadParameterType);
858 }
859
860 UpdateStorageUid(tag, value.asString(), decodeDataUriScheme);
861 }
862 }
863 }
864
865
866 #if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
867 void ParsedDicomFile::Answer(RestApiOutput& output)
868 {
869 std::string serialized;
870 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *GetDcmtkObject().getDataset()))
871 {
872 output.AnswerBuffer(serialized, MimeType_Dicom);
873 }
874 }
875 #endif
876
877
878 bool ParsedDicomFile::GetTagValue(std::string& value,
879 const DicomTag& tag)
880 {
881 DcmTagKey k(tag.GetGroup(), tag.GetElement());
882 DcmDataset& dataset = *GetDcmtkObject().getDataset();
883
884 if (tag.IsPrivate() ||
885 FromDcmtkBridge::IsUnknownTag(tag) ||
886 tag == DICOM_TAG_PIXEL_DATA ||
887 tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
888 {
889 const Uint8* data = NULL; // This is freed in the destructor of the dataset
890 long unsigned int count = 0;
891
892 if (dataset.findAndGetUint8Array(k, data, &count).good())
893 {
894 if (count > 0)
895 {
896 assert(data != NULL);
897 value.assign(reinterpret_cast<const char*>(data), count);
898 }
899 else
900 {
901 value.clear();
902 }
903
904 return true;
905 }
906 else
907 {
908 return false;
909 }
910 }
911 else
912 {
913 DcmElement* element = NULL;
914 if (!dataset.findAndGetElement(k, element).good() ||
915 element == NULL)
916 {
917 return false;
918 }
919
920 bool hasCodeExtensions;
921 Encoding encoding = DetectEncoding(hasCodeExtensions);
922
923 std::set<DicomTag> tmp;
924 std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
925 (*element, DicomToJsonFlags_Default,
926 0, encoding, hasCodeExtensions, tmp));
927
928 if (v.get() == NULL ||
929 v->IsNull())
930 {
931 value = "";
932 }
933 else
934 {
935 // TODO v->IsBinary()
936 value = v->GetContent();
937 }
938
939 return true;
940 }
941 }
942
943
944 DicomInstanceHasher ParsedDicomFile::GetHasher()
945 {
946 std::string patientId, studyUid, seriesUid, instanceUid;
947
948 if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID))
949 {
950 /**
951 * If "PatientID" is absent, be tolerant by considering it
952 * equals the empty string, then proceed. In Orthanc <= 1.5.6,
953 * an exception "Bad file format" was generated.
954 * https://groups.google.com/d/msg/orthanc-users/aphG_h1AHVg/rfOTtTPTAgAJ
955 * https://hg.orthanc-server.com/orthanc/rev/4c45e018bd3de3cfa21d6efc6734673aaaee4435
956 **/
957 patientId.clear();
958 }
959
960 if (!GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
961 !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
962 !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
963 {
964 throw OrthancException(ErrorCode_BadFileFormat, "missing StudyInstanceUID, SeriesInstanceUID or SOPInstanceUID");
965 }
966
967 return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
968 }
969
970
971 void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
972 {
973 FromDcmtkBridge::SaveToMemoryBuffer(buffer, *GetDcmtkObject().getDataset());
974 }
975
976
977 #if ORTHANC_SANDBOXED == 0
978 void ParsedDicomFile::SaveToFile(const std::string& path)
979 {
980 // TODO Avoid using a temporary memory buffer, write directly on disk
981 std::string content;
982 SaveToMemoryBuffer(content);
983 SystemToolbox::WriteFile(content, path);
984 }
985 #endif
986
987
988 ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl)
989 {
990 pimpl_->file_.reset(new DcmFileFormat);
991
992 if (createIdentifiers)
993 {
994 ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
995 ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
996 ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
997 ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
998 }
999 }
1000
1001
1002 void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source,
1003 Encoding defaultEncoding,
1004 bool permissive,
1005 const std::string& defaultPrivateCreator,
1006 const std::map<uint16_t, std::string>& privateCreators)
1007 {
1008 pimpl_->file_.reset(new DcmFileFormat);
1009 InvalidateCache();
1010
1011 const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET);
1012
1013 if (tmp == NULL)
1014 {
1015 SetEncoding(defaultEncoding);
1016 }
1017 else if (tmp->IsBinary())
1018 {
1019 throw OrthancException(ErrorCode_ParameterOutOfRange,
1020 "Invalid binary string in the SpecificCharacterSet (0008,0005) tag");
1021 }
1022 else if (tmp->IsNull() ||
1023 tmp->GetContent().empty())
1024 {
1025 SetEncoding(defaultEncoding);
1026 }
1027 else
1028 {
1029 Encoding encoding;
1030
1031 if (GetDicomEncoding(encoding, tmp->GetContent().c_str()))
1032 {
1033 SetEncoding(encoding);
1034 }
1035 else
1036 {
1037 throw OrthancException(ErrorCode_ParameterOutOfRange,
1038 "Unsupported value for the SpecificCharacterSet (0008,0005) tag: \"" +
1039 tmp->GetContent() + "\"");
1040 }
1041 }
1042
1043 for (DicomMap::Content::const_iterator
1044 it = source.content_.begin(); it != source.content_.end(); ++it)
1045 {
1046 if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET &&
1047 !it->second->IsNull())
1048 {
1049 try
1050 {
1051 // Same as "ReplacePlainString()", but with support for private creator
1052 const std::string& utf8Value = it->second->GetContent();
1053
1054 std::map<uint16_t, std::string>::const_iterator found = privateCreators.find(it->first.GetGroup());
1055
1056 if (it->first.IsPrivate() &&
1057 found != privateCreators.end())
1058 {
1059 Replace(it->first, utf8Value, false, DicomReplaceMode_InsertIfAbsent, found->second);
1060 }
1061 else
1062 {
1063 Replace(it->first, utf8Value, false, DicomReplaceMode_InsertIfAbsent, defaultPrivateCreator);
1064 }
1065 }
1066 catch (OrthancException&)
1067 {
1068 if (!permissive)
1069 {
1070 throw;
1071 }
1072 }
1073 }
1074 }
1075 }
1076
1077 ParsedDicomFile::ParsedDicomFile(const DicomMap& map,
1078 Encoding defaultEncoding,
1079 bool permissive) :
1080 pimpl_(new PImpl)
1081 {
1082 std::map<uint16_t, std::string> noPrivateCreators;
1083 CreateFromDicomMap(map, defaultEncoding, permissive, "" /* no default private creator */, noPrivateCreators);
1084 }
1085
1086
1087 ParsedDicomFile::ParsedDicomFile(const DicomMap& map,
1088 Encoding defaultEncoding,
1089 bool permissive,
1090 const std::string& defaultPrivateCreator,
1091 const std::map<uint16_t, std::string>& privateCreators) :
1092 pimpl_(new PImpl)
1093 {
1094 CreateFromDicomMap(map, defaultEncoding, permissive, defaultPrivateCreator, privateCreators);
1095 }
1096
1097
1098 ParsedDicomFile::ParsedDicomFile(const void* content,
1099 size_t size) : pimpl_(new PImpl)
1100 {
1101 pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size));
1102 }
1103
1104 ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl)
1105 {
1106 if (content.size() == 0)
1107 {
1108 pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0));
1109 }
1110 else
1111 {
1112 pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size()));
1113 }
1114 }
1115
1116
1117 ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other,
1118 bool keepSopInstanceUid) :
1119 pimpl_(new PImpl)
1120 {
1121 pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.GetDcmtkObject().clone()));
1122
1123 if (!keepSopInstanceUid)
1124 {
1125 // Create a new instance-level identifier
1126 ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
1127 }
1128 }
1129
1130
1131 ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl)
1132 {
1133 pimpl_->file_.reset(new DcmFileFormat(&dicom));
1134 }
1135
1136
1137 ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl)
1138 {
1139 pimpl_->file_.reset(new DcmFileFormat(dicom));
1140 }
1141
1142
1143 ParsedDicomFile::ParsedDicomFile(DcmFileFormat* dicom) : pimpl_(new PImpl)
1144 {
1145 pimpl_->file_.reset(dicom); // No cloning
1146 }
1147
1148
1149 DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
1150 {
1151 if (pimpl_->file_.get() == NULL)
1152 {
1153 throw OrthancException(ErrorCode_BadSequenceOfCalls,
1154 "ReleaseDcmtkObject() was called");
1155 }
1156 else
1157 {
1158 return *pimpl_->file_;
1159 }
1160 }
1161
1162
1163 DcmFileFormat* ParsedDicomFile::ReleaseDcmtkObject()
1164 {
1165 if (pimpl_->file_.get() == NULL)
1166 {
1167 throw OrthancException(ErrorCode_BadSequenceOfCalls,
1168 "ReleaseDcmtkObject() was called");
1169 }
1170 else
1171 {
1172 pimpl_->frameIndex_.reset(NULL);
1173 return pimpl_->file_.release();
1174 }
1175 }
1176
1177
1178 ParsedDicomFile* ParsedDicomFile::Clone(bool keepSopInstanceUid)
1179 {
1180 return new ParsedDicomFile(*this, keepSopInstanceUid);
1181 }
1182
1183
1184 bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme)
1185 {
1186 std::string mimeString, content;
1187 if (!Toolbox::DecodeDataUriScheme(mimeString, content, dataUriScheme))
1188 {
1189 return false;
1190 }
1191
1192 Toolbox::ToLowerCase(mimeString);
1193 MimeType mime = StringToMimeType(mimeString);
1194
1195 switch (mime)
1196 {
1197 case MimeType_Png:
1198 #if ORTHANC_ENABLE_PNG == 1
1199 EmbedImage(mime, content);
1200 break;
1201 #else
1202 throw OrthancException(ErrorCode_NotImplemented,
1203 "Orthanc was compiled without support of PNG");
1204 #endif
1205
1206 case MimeType_Jpeg:
1207 #if ORTHANC_ENABLE_JPEG == 1
1208 EmbedImage(mime, content);
1209 break;
1210 #else
1211 throw OrthancException(ErrorCode_NotImplemented,
1212 "Orthanc was compiled without support of JPEG");
1213 #endif
1214
1215 case MimeType_Pam:
1216 EmbedImage(mime, content);
1217 break;
1218
1219 case MimeType_Pdf:
1220 EmbedPdf(content);
1221 break;
1222
1223 default:
1224 throw OrthancException(ErrorCode_NotImplemented,
1225 "Unsupported MIME type for the content of a new DICOM file: " +
1226 std::string(EnumerationToString(mime)));
1227 }
1228
1229 return true;
1230 }
1231
1232
1233 void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
1234 {
1235 if (!EmbedContentInternal(dataUriScheme))
1236 {
1237 throw OrthancException(ErrorCode_BadFileFormat);
1238 }
1239 }
1240
1241
1242 void ParsedDicomFile::EmbedImage(MimeType mime,
1243 const std::string& content)
1244 {
1245 switch (mime)
1246 {
1247
1248 #if ORTHANC_ENABLE_JPEG == 1
1249 case MimeType_Jpeg:
1250 {
1251 JpegReader reader;
1252 reader.ReadFromMemory(content);
1253 EmbedImage(reader);
1254 break;
1255 }
1256 #endif
1257
1258 #if ORTHANC_ENABLE_PNG == 1
1259 case MimeType_Png:
1260 {
1261 PngReader reader;
1262 reader.ReadFromMemory(content);
1263 EmbedImage(reader);
1264 break;
1265 }
1266 #endif
1267
1268 case MimeType_Pam:
1269 {
1270 PamReader reader;
1271 reader.ReadFromMemory(content);
1272 EmbedImage(reader);
1273 break;
1274 }
1275
1276 default:
1277 throw OrthancException(ErrorCode_NotImplemented);
1278 }
1279 }
1280
1281
1282 void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor)
1283 {
1284 if (accessor.GetFormat() != PixelFormat_Grayscale8 &&
1285 accessor.GetFormat() != PixelFormat_Grayscale16 &&
1286 accessor.GetFormat() != PixelFormat_SignedGrayscale16 &&
1287 accessor.GetFormat() != PixelFormat_RGB24 &&
1288 accessor.GetFormat() != PixelFormat_RGBA32)
1289 {
1290 throw OrthancException(ErrorCode_NotImplemented);
1291 }
1292
1293 InvalidateCache();
1294
1295 if (accessor.GetFormat() == PixelFormat_RGBA32)
1296 {
1297 LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
1298 }
1299
1300 // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
1301
1302 Remove(DICOM_TAG_PIXEL_DATA);
1303 ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
1304 ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
1305 ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
1306
1307 // The "Number of frames" must only be present in multi-frame images
1308 //ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1");
1309
1310 if (accessor.GetFormat() == PixelFormat_SignedGrayscale16)
1311 {
1312 ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1");
1313 }
1314 else
1315 {
1316 ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels
1317 }
1318
1319 unsigned int bytesPerPixel = 0;
1320
1321 switch (accessor.GetFormat())
1322 {
1323 case PixelFormat_Grayscale8:
1324 // By default, grayscale images are MONOCHROME2
1325 SetIfAbsent(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
1326
1327 ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
1328 ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
1329 ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
1330 bytesPerPixel = 1;
1331 break;
1332
1333 case PixelFormat_RGB24:
1334 case PixelFormat_RGBA32:
1335 ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
1336 ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
1337 ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
1338 ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
1339 ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
1340 bytesPerPixel = 3;
1341
1342 // "Planar configuration" must only present if "Samples per
1343 // Pixel" is greater than 1
1344 ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved
1345
1346 break;
1347
1348 case PixelFormat_Grayscale16:
1349 case PixelFormat_SignedGrayscale16:
1350 // By default, grayscale images are MONOCHROME2
1351 SetIfAbsent(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
1352
1353 ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16");
1354 ReplacePlainString(DICOM_TAG_BITS_STORED, "16");
1355 ReplacePlainString(DICOM_TAG_HIGH_BIT, "15");
1356 bytesPerPixel = 2;
1357 break;
1358
1359 default:
1360 throw OrthancException(ErrorCode_NotImplemented);
1361 }
1362
1363 assert(bytesPerPixel != 0);
1364
1365 DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(),
1366 DICOM_TAG_PIXEL_DATA.GetElement());
1367
1368 std::unique_ptr<DcmPixelData> pixels(new DcmPixelData(key));
1369
1370 unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
1371 Uint8* target = NULL;
1372 pixels->createUint8Array(accessor.GetHeight() * pitch, target);
1373
1374 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
1375 {
1376 switch (accessor.GetFormat())
1377 {
1378 case PixelFormat_RGB24:
1379 case PixelFormat_Grayscale8:
1380 case PixelFormat_Grayscale16:
1381 case PixelFormat_SignedGrayscale16:
1382 {
1383 memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch);
1384 target += pitch;
1385 break;
1386 }
1387
1388 case PixelFormat_RGBA32:
1389 {
1390 // The alpha channel is not supported by the DICOM standard
1391 const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
1392 for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4)
1393 {
1394 target[0] = source[0];
1395 target[1] = source[1];
1396 target[2] = source[2];
1397 }
1398
1399 break;
1400 }
1401
1402 default:
1403 throw OrthancException(ErrorCode_NotImplemented);
1404 }
1405 }
1406
1407 if (!GetDcmtkObject().getDataset()->insert(pixels.release(), false, false).good())
1408 {
1409 throw OrthancException(ErrorCode_InternalError);
1410 }
1411 }
1412
1413
1414 Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const
1415 {
1416 return FromDcmtkBridge::DetectEncoding(hasCodeExtensions,
1417 *GetDcmtkObject().getDataset(),
1418 GetDefaultDicomEncoding());
1419 }
1420
1421
1422 void ParsedDicomFile::SetEncoding(Encoding encoding)
1423 {
1424 if (encoding == Encoding_Windows1251)
1425 {
1426 // This Cyrillic codepage is not officially supported by the
1427 // DICOM standard. Do not set the SpecificCharacterSet tag.
1428 return;
1429 }
1430
1431 std::string s = GetDicomSpecificCharacterSet(encoding);
1432 ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s);
1433 }
1434
1435 void ParsedDicomFile::DatasetToJson(Json::Value& target,
1436 DicomToJsonFormat format,
1437 DicomToJsonFlags flags,
1438 unsigned int maxStringLength)
1439 {
1440 std::set<DicomTag> ignoreTagLength;
1441 FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(),
1442 format, flags, maxStringLength,
1443 GetDefaultDicomEncoding(), ignoreTagLength);
1444 }
1445
1446
1447 void ParsedDicomFile::DatasetToJson(Json::Value& target,
1448 DicomToJsonFormat format,
1449 DicomToJsonFlags flags,
1450 unsigned int maxStringLength,
1451 const std::set<DicomTag>& ignoreTagLength)
1452 {
1453 FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(),
1454 format, flags, maxStringLength,
1455 GetDefaultDicomEncoding(), ignoreTagLength);
1456 }
1457
1458
1459 void ParsedDicomFile::DatasetToJson(Json::Value& target,
1460 const std::set<DicomTag>& ignoreTagLength)
1461 {
1462 FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength);
1463 }
1464
1465
1466 void ParsedDicomFile::DatasetToJson(Json::Value& target)
1467 {
1468 const std::set<DicomTag> ignoreTagLength;
1469 FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength);
1470 }
1471
1472
1473 void ParsedDicomFile::HeaderToJson(Json::Value& target,
1474 DicomToJsonFormat format)
1475 {
1476 FromDcmtkBridge::ExtractHeaderAsJson(target, *GetDcmtkObject().getMetaInfo(), format, DicomToJsonFlags_None, 0);
1477 }
1478
1479
1480 bool ParsedDicomFile::HasTag(const DicomTag& tag) const
1481 {
1482 DcmTag key(tag.GetGroup(), tag.GetElement());
1483 return GetDcmtkObject().getDataset()->tagExists(key);
1484 }
1485
1486
1487 void ParsedDicomFile::EmbedPdf(const std::string& pdf)
1488 {
1489 if (pdf.size() < 5 || // (*)
1490 strncmp("%PDF-", pdf.c_str(), 5) != 0)
1491 {
1492 throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file");
1493 }
1494
1495 InvalidateCache();
1496
1497 ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
1498 ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT");
1499 ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
1500 ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF);
1501 //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
1502
1503 std::unique_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
1504
1505 size_t s = pdf.size();
1506 if (s & 1)
1507 {
1508 // The size of the buffer must be even
1509 s += 1;
1510 }
1511
1512 Uint8* bytes = NULL;
1513 OFCondition result = element->createUint8Array(s, bytes);
1514 if (!result.good() || bytes == NULL)
1515 {
1516 throw OrthancException(ErrorCode_NotEnoughMemory);
1517 }
1518
1519 // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
1520 bytes[s - 1] = 0;
1521
1522 memcpy(bytes, pdf.c_str(), pdf.size());
1523
1524 DcmPolymorphOBOW* obj = element.release();
1525 result = GetDcmtkObject().getDataset()->insert(obj);
1526
1527 if (!result.good())
1528 {
1529 delete obj;
1530 throw OrthancException(ErrorCode_NotEnoughMemory);
1531 }
1532 }
1533
1534
1535 bool ParsedDicomFile::ExtractPdf(std::string& pdf)
1536 {
1537 std::string sop, mime;
1538
1539 if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) ||
1540 !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) ||
1541 sop != UID_EncapsulatedPDFStorage ||
1542 mime != MIME_PDF)
1543 {
1544 return false;
1545 }
1546
1547 if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT))
1548 {
1549 return false;
1550 }
1551
1552 // Strip the possible pad byte at the end of file, because the
1553 // encapsulated documents must always have an even length. The PDF
1554 // format expects files to end with %%EOF followed by CR/LF. If
1555 // the last character of the file is not a CR or LF, we assume it
1556 // is a pad byte and remove it.
1557 if (pdf.size() > 0)
1558 {
1559 char last = *pdf.rbegin();
1560
1561 if (last != 10 && last != 13)
1562 {
1563 pdf.resize(pdf.size() - 1);
1564 }
1565 }
1566
1567 return true;
1568 }
1569
1570
1571 ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json,
1572 DicomFromJsonFlags flags,
1573 const std::string& privateCreator)
1574 {
1575 const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false;
1576 const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false;
1577
1578 std::unique_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
1579 result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding()));
1580
1581 const Json::Value::Members tags = json.getMemberNames();
1582
1583 for (size_t i = 0; i < tags.size(); i++)
1584 {
1585 DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
1586 const Json::Value& value = json[tags[i]];
1587
1588 if (tag == DICOM_TAG_PIXEL_DATA ||
1589 tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
1590 {
1591 if (value.type() != Json::stringValue)
1592 {
1593 throw OrthancException(ErrorCode_BadRequest);
1594 }
1595 else
1596 {
1597 result->EmbedContent(value.asString());
1598 }
1599 }
1600 else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
1601 {
1602 result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent, privateCreator);
1603 }
1604 }
1605
1606 return result.release();
1607 }
1608
1609
1610 void ParsedDicomFile::GetRawFrame(std::string& target,
1611 MimeType& mime,
1612 unsigned int frameId)
1613 {
1614 if (pimpl_->frameIndex_.get() == NULL)
1615 {
1616 assert(pimpl_->file_ != NULL &&
1617 GetDcmtkObject().getDataset() != NULL);
1618 pimpl_->frameIndex_.reset(new DicomFrameIndex(*GetDcmtkObject().getDataset()));
1619 }
1620
1621 pimpl_->frameIndex_->GetRawFrame(target, frameId);
1622
1623 E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer();
1624 switch (transferSyntax)
1625 {
1626 case EXS_JPEGProcess1:
1627 mime = MimeType_Jpeg;
1628 break;
1629
1630 case EXS_JPEG2000LosslessOnly:
1631 case EXS_JPEG2000:
1632 mime = MimeType_Jpeg2000;
1633 break;
1634
1635 default:
1636 mime = MimeType_Binary;
1637 break;
1638 }
1639 }
1640
1641
1642 void ParsedDicomFile::InvalidateCache()
1643 {
1644 pimpl_->frameIndex_.reset(NULL);
1645 }
1646
1647
1648 unsigned int ParsedDicomFile::GetFramesCount() const
1649 {
1650 assert(pimpl_->file_ != NULL &&
1651 GetDcmtkObject().getDataset() != NULL);
1652 return DicomFrameIndex::GetFramesCount(*GetDcmtkObject().getDataset());
1653 }
1654
1655
1656 void ParsedDicomFile::ChangeEncoding(Encoding target)
1657 {
1658 bool hasCodeExtensions;
1659 Encoding source = DetectEncoding(hasCodeExtensions);
1660
1661 if (source != target) // Avoid unnecessary conversion
1662 {
1663 ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target));
1664 FromDcmtkBridge::ChangeStringEncoding(*GetDcmtkObject().getDataset(), source, hasCodeExtensions, target);
1665 }
1666 }
1667
1668
1669 void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const
1670 {
1671 FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset());
1672 }
1673
1674
1675 void ParsedDicomFile::ExtractDicomSummary(DicomMap& target,
1676 const std::set<DicomTag>& ignoreTagLength) const
1677 {
1678 FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset(), ignoreTagLength);
1679 }
1680
1681
1682 bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
1683 {
1684 #if 0
1685 // This was the implementation in Orthanc <= 1.6.1
1686
1687 // TODO - Shouldn't "dataset.getCurrentXfer()" be used instead of
1688 // using the meta header?
1689 const char* value = NULL;
1690
1691 if (GetDcmtkObject().getMetaInfo() != NULL &&
1692 GetDcmtkObject().getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() &&
1693 value != NULL)
1694 {
1695 result.assign(value);
1696 return true;
1697 }
1698 else
1699 {
1700 return false;
1701 }
1702 #else
1703 DicomTransferSyntax s;
1704 if (FromDcmtkBridge::LookupOrthancTransferSyntax(s, GetDcmtkObject()))
1705 {
1706 result.assign(GetTransferSyntaxUid(s));
1707 return true;
1708 }
1709 else
1710 {
1711 return false;
1712 }
1713 #endif
1714 }
1715
1716
1717 bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const
1718 {
1719 DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(),
1720 DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement());
1721
1722 DcmDataset& dataset = *GetDcmtkObject().getDataset();
1723
1724 const char *c = NULL;
1725 if (dataset.findAndGetString(k, c).good() &&
1726 c != NULL)
1727 {
1728 result = StringToPhotometricInterpretation(c);
1729 return true;
1730 }
1731 else
1732 {
1733 return false;
1734 }
1735 }
1736
1737
1738 void ParsedDicomFile::Apply(ITagVisitor& visitor)
1739 {
1740 FromDcmtkBridge::Apply(*GetDcmtkObject().getDataset(), visitor, GetDefaultDicomEncoding());
1741 }
1742 }